Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions example/src/views/CaptureButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ const _CaptureButton: React.FC<Props> = ({
console.log('calling startRecording()...')
camera.current.startRecording({
flash: flash,
width: 1024, // Output width after crop
height: 1024, // Output height after crop
crop: {
left: 0.1, // Start crop at 10% from left
top: 0.1, // Start crop at 10% from top
width: 0.9, // Crop to 90% width
height: 0.9 // Crop to 90% height
},
onRecordingError: (error) => {
console.error('Recording failed!', error)
onStoppedRecording()
Expand Down
21 changes: 16 additions & 5 deletions package/ios/Core/CameraSession+Video.swift
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,24 @@ extension CameraSession {
// Orientation is relative to our current output orientation
let orientation = self.outputOrientation.relativeTo(orientation: videoOutput.orientation)

// Create crop rect if needed
var cropRect: CGRect?
if let crop = options.crop {
cropRect = CGRect(x: crop.x,
y: crop.y,
width: crop.width,
height: crop.height)
VisionLogger.log(level: .info, message: "Creating recording with crop rect: \(cropRect!)")
}

// Create RecordingSession for the temp file
let recordingSession = try RecordingSession(url: options.path,
fileType: options.fileType,
metadataProvider: self.metadataProvider,
clock: self.captureSession.clock,
orientation: orientation,
completion: onFinish)
fileType: options.fileType,
metadataProvider: self.metadataProvider,
clock: self.captureSession.clock,
orientation: orientation,
cropRect: cropRect,
completion: onFinish)

// Init Audio + Activate Audio Session (optional)
if enableAudio,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ extension AVCaptureVideoDataOutput {
guard var settings else {
throw CameraError.capture(.createRecorderError(message: "Failed to get video settings!"))
}

// Apply custom width/height if provided
if let width = options.width, let height = options.height {
VisionLogger.log(level: .info, message: "Setting custom video dimensions: \(width)x\(height)")
settings[AVVideoWidthKey] = NSNumber(value: width)
settings[AVVideoHeightKey] = NSNumber(value: height)
}

if let bitRateOverride = options.bitRateOverride {
// Convert from Mbps -> bps
Expand Down
49 changes: 48 additions & 1 deletion package/ios/Core/RecordingSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ final class RecordingSession {
private var audioTrack: Track?
private let completionHandler: (RecordingSession, AVAssetWriter.Status, Error?) -> Void
private var isFinishing = false

// Crop properties
private var cropRect: CGRect?
private var inputSize: CGSize?

private let lock = DispatchSemaphore(value: 1)

Expand Down Expand Up @@ -69,7 +73,10 @@ final class RecordingSession {
metadataProvider: MetadataProvider,
clock: CMClock,
orientation: Orientation,
cropRect: CGRect? = nil,
completion: @escaping (RecordingSession, AVAssetWriter.Status, Error?) -> Void) throws {

self.cropRect = cropRect
completionHandler = completion
self.clock = clock
videoOrientation = orientation
Expand Down Expand Up @@ -107,9 +114,49 @@ final class RecordingSession {
guard assetWriter.canApply(outputSettings: settings, forMediaType: .video) else {
throw CameraError.capture(.createRecorderError(message: "The given output settings are not supported!"))
}

// Store the input size for later use in crop calculations
if let width = settings[AVVideoWidthKey] as? NSNumber,
let height = settings[AVVideoHeightKey] as? NSNumber {
inputSize = CGSize(width: width.doubleValue, height: height.doubleValue)
}

VisionLogger.log(level: .info, message: "Initializing Video AssetWriter with settings: \(settings.description)")
let videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: settings)

// Create video writer input with initial settings
var videoWriter: AVAssetWriterInput

if let cropRect = cropRect, let inputSize = inputSize {
// Calculate the actual crop rectangle based on input size
let renderSize = inputSize
let cropRect = CGRect(x: cropRect.origin.x * renderSize.width,
y: cropRect.origin.y * renderSize.height,
width: cropRect.width * renderSize.width,
height: cropRect.height * renderSize.height)

// Create a transform to apply the crop
let transform = CGAffineTransform(
translationX: -cropRect.origin.x,
y: -cropRect.origin.y
)

// Create a new settings dictionary with the cropped size
var croppedSettings = settings
croppedSettings[AVVideoWidthKey] = NSNumber(value: Int(cropRect.width))
croppedSettings[AVVideoHeightKey] = NSNumber(value: Int(cropRect.height))

// Create the video writer with cropped settings
videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: croppedSettings)

// Apply the transform (combine with orientation transform)
videoWriter.transform = transform.concatenating(videoOrientation.affineTransform)

VisionLogger.log(level: .info, message: "Applied video crop: \(cropRect)")
} else {
// No crop, use original settings
videoWriter = AVAssetWriterInput(mediaType: .video, outputSettings: settings)
}

videoWriter.expectsMediaDataInRealTime = true
videoWriter.transform = videoOrientation.affineTransform
assetWriter.add(videoWriter)
Expand Down
34 changes: 34 additions & 0 deletions package/ios/Core/Types/RecordVideoOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ struct RecordVideoOptions {
* or set via bitRate, in Megabits per second (Mbps)
*/
var bitRateMultiplier: Double?
var width: Int?
var height: Int?

struct CropRect {
var x: CGFloat
var y: CGFloat
var width: CGFloat
var height: CGFloat

static let `default` = CropRect(x: 0, y: 0, width: 1, height: 1)
}
var crop: CropRect?

init(fromJSValue dictionary: NSDictionary, bitRateOverride: Double? = nil, bitRateMultiplier: Double? = nil) throws {
// File Type (.mov or .mp4)
Expand All @@ -41,6 +53,28 @@ struct RecordVideoOptions {
self.bitRateOverride = bitRateOverride
// BitRate Multiplier
self.bitRateMultiplier = bitRateMultiplier
// Width
if let width = dictionary["width"] as? NSNumber {
self.width = width.intValue
}
// Height
if let height = dictionary["height"] as? NSNumber {
self.height = height.intValue
}
// Parse crop region if provided
if let cropDict = dictionary["crop"] as? [String: Any] {
let x = cropDict["left"] as? NSNumber ?? 0
let y = cropDict["top"] as? NSNumber ?? 0
let width = cropDict["width"] as? NSNumber ?? 1
let height = cropDict["height"] as? NSNumber ?? 1

self.crop = CropRect(
x: CGFloat(truncating: x),
y: CGFloat(truncating: y),
width: CGFloat(truncating: width),
height: CGFloat(truncating: height)
)
}
// Custom Path
let fileExtension = fileType.descriptor ?? "mov"
if let customPath = dictionary["path"] as? String {
Expand Down
37 changes: 37 additions & 0 deletions package/src/types/VideoFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,43 @@ export interface RecordVideoOptions {
* Specifies the output file type to record videos into.
*/
fileType?: 'mov' | 'mp4'
/**
* The width of the video in pixels.
* If not specified, the native sensor resolution is used.
*/
width?: number
/**
* The height of the video in pixels.
* If not specified, the native sensor resolution is used.
*/
height?: number
/**
* The crop region of the video.
* All values are in the range 0 to 1, relative to the video dimensions.
* @default { left: 0, top: 0, width: 1, height: 1 }
*/
crop?: {
/**
* The x-coordinate of the top-left corner of the crop region (0-1)
* @default 0
*/
left?: number
/**
* The y-coordinate of the top-left corner of the crop region (0-1)
* @default 0
*/
top?: number
/**
* The width of the crop region (0-1)
* @default 1
*/
width?: number
/**
* The height of the crop region (0-1)
* @default 1
*/
height?: number
}
/**
* A custom `path` where the video will be saved to.
*
Expand Down