10

I've recorded a video with the front facing camera and the output is mirrored...

I've tried using AVMutablecomposition and layerinstructions to flip the video but no luck.

Googling and searching Stack Overflow has been fruitless so I bet a simple, straight forward example of how to do this is something that would benefit many.

Muhammed Refaat
  • 8,914
  • 14
  • 83
  • 118
PinkFloydRocks
  • 808
  • 3
  • 14
  • 29
  • If you are using AVCaptureConnection to record, I'd correct the issue there, by setting the Video Orientation using `setVideoOrientation` – Lefteris Mar 29 '16 at 18:15

4 Answers4

18

Theres no indication on what you are using to record the video, ill assume AVCaptureSession + AVCaptureVideoDataOutput

lazy var videoFileOutput: AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
let v = videoFileOutput.connectionWithMediaType(AVMediaTypeVideo)
v.videoOrientation = .Portrait
v.videoMirrored = true
Sean Lintern
  • 3,103
  • 22
  • 28
  • Could you provide more context on how this could be used? I had been using let videoRecorded = outputURL! as URL which does not seem to be compatible here –  Jan 05 '19 at 05:09
7

You can use -[AVMutableVideoCompositionLayerInstruction setTransform:atTime:]

CGAffineTransform transform = CGAffineTransformMakeTranslation(self.config.videoSize, 0);
transform = CGAffineTransformScale(transform, -1.0, 1.0);
[videoCompositionLayerInstruction setTransform:transform atTime:videoTime];

// then append video tracks
// [compositionTrack insertTimeRange:timeRange ofTrack:track atTime:atTime error:&error];

// apply instructions
videoCompositionInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, composition.duration);
videoCompositionInstruction.layerInstructions = @[videoCompositionLayerInstruction];

videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.renderSize = CGSizeMake(self.config.videoSize, self.config.videoSize);
videoComposition.frameDuration = CMTimeMake(1, self.config.videoFrameRate);
videoComposition.instructions = @[videoCompositionInstruction];

https://github.com/ElfSundae/AVDemo/tree/ef2ca437d0d8dcb3dd41c5a272c8754a29d8a936/AVSimpleEditoriOS

Export composition:

AVAssetExportSession *exportSession = [AVAssetExportSession exportSessionWithAsset:composition presetName:presetName];
exportSession.outputFileType = AVFileTypeMPEG4;
exportSession.outputURL = outputURL;
exportSession.shouldOptimizeForNetworkUse = YES;
// videoComposition contains transform instructions for video tracks
exportSession.videoComposition = videoComposition;
// audioMix contains background music for audio tracks
exportSession.audioMix = audioMix;

[exportSession exportAsynchronouslyWithCompletionHandler:^{
    AVAssetExportSessionStatus status = exportSession.status;
    if (status != AVAssetExportSessionStatusCompleted) {
        // exportSession.error
    } else {
        // exportSession.outputURL
    }
}];
Elf Sundae
  • 1,575
  • 16
  • 23
  • Thanks for your example - and how would you export this composition to a video? – PinkFloydRocks Mar 29 '16 at 18:38
  • @PinkFloydRocks I use AVAssetExportSession, the answer has been updated. – Elf Sundae Mar 29 '16 at 23:46
  • CGAffineTransformMakeTranslation(self.config.videoSize, 0); . It needs a CGFloat value, not a CGSize value. Don't know how you got this code to work and post it here. –  Aug 25 '17 at 17:48
1

After you get your output transform your video

    func mirrorVideo(inputURL: URL, completion: @escaping (_ outputURL : URL?) -> ())
{
    let videoAsset: AVAsset = AVAsset( url: inputURL )
    let clipVideoTrack = videoAsset.tracks( withMediaType: AVMediaType.video ).first! as AVAssetTrack

    let composition = AVMutableComposition()
    composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: CMPersistentTrackID())

    let videoComposition = AVMutableVideoComposition()
    videoComposition.renderSize = CGSize(width: clipVideoTrack.naturalSize.height, height: clipVideoTrack.naturalSize.width)
    videoComposition.frameDuration = CMTimeMake(1, 30)

    let transformer = AVMutableVideoCompositionLayerInstruction(assetTrack: clipVideoTrack)

    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30))
    var transform:CGAffineTransform = CGAffineTransform(scaleX: -1.0, y: 1.0)
    transform = transform.translatedBy(x: -clipVideoTrack.naturalSize.width, y: 0.0)
    transform = transform.rotated(by: CGFloat(Double.pi/2))
    transform = transform.translatedBy(x: 0.0, y: -clipVideoTrack.naturalSize.width)

    transformer.setTransform(transform, at: kCMTimeZero)

    instruction.layerInstructions = [transformer]
    videoComposition.instructions = [instruction]

    // Export

    let exportSession = AVAssetExportSession(asset: videoAsset, presetName: AVAssetExportPreset640x480)!
    let fileName = UniqueIDGenerator.generate().appending(".mp4")
    let filePath = documentsURL.appendingPathComponent(fileName)
    let croppedOutputFileUrl = filePath
    exportSession.outputURL = croppedOutputFileUrl
    exportSession.outputFileType = AVFileType.mp4
    exportSession.videoComposition = videoComposition
    exportSession.exportAsynchronously {
        if exportSession.status == .completed {
            DispatchQueue.main.async(execute: {
                completion(croppedOutputFileUrl)
            })
            return
        } else if exportSession.status == .failed {
            print("Export failed - \(String(describing: exportSession.error))")
        }

        completion(nil)
        return
    }
}
Yalcin Ozdemir
  • 524
  • 3
  • 7
  • This looks like a wall of code without any explanation. Would be better if you could have detailed it a bit. – atereshkov Dec 23 '19 at 14:02
1

Swift 5. AVCaptureSession:

let movieFileOutput = AVCaptureMovieFileOutput()
let connection = movieFileOutput.connection(with: .video)

if connection?.isVideoMirroringSupported ?? false {
    connection?.isVideoMirrored = true
}

Same for PhotoOutput.

atereshkov
  • 4,311
  • 1
  • 38
  • 49