Search code examples
swiftavfoundationcropimage-resizing

How to resize, crop and export AVAsset in Swift on OS X?


I'm trying to resize and crop AVAsset and export it to a movie file in Swift on OS X.

I am using AVAssetExportSession to export AVAsset and it works without problem if videoComposition is NOT set to the AVAssetExportSession. The following code exports the asset without problem, though it doesn't resize and crop since the videoComposition line is commented out.

self.asset = AVAsset( URL: validURL )!

if let exportSession = AVAssetExportSession( asset: self.asset,
                                        presetName: AVAssetExportPresetAppleProRes422LPCM ) {
        exportSession.outputURL = exportURL
        exportSession.outputFileType = AVFileTypeQuickTimeMovie
//      exportSession.videoComposition = self.videoComposition   // *** problematic line
        exportSession.timeRange = CMTimeRangeMake( kCMTimeZero, self.asset.duration )

        exportSession.exportAsynchronouslyWithCompletionHandler( { () -> Void in

            print( "[export completed]" )       
        })
}

But if I set videoComposition to AVAssetExportSession to resize and crop the asset by removing the comment, the exprted file only have the first frame of the resized and cropped video, and the second and later frames are all black frames, while audio is exported without problem.

The videoComposition I set is as follows. (The render size and affine transform are fixed in this example to simplify the code)

var videoComposition: AVVideoComposition {
    //---------------
    //  composition
    //---------------
    let composition = AVMutableVideoComposition()
    composition.renderSize = NSMakeSize( 720, 480 )  // fixed size in this example
    composition.frameDuration = self.asset.duration

    //---------------
    //  instruction
    //---------------
    let instruction = AVMutableVideoCompositionInstruction()
    instruction.timeRange = CMTimeRangeMake( kCMTimeZero, self.asset.duration )

    //-------------------------
    //  transform instruction
    //-------------------------
    let videoTracks = self.asset.tracksWithMediaType( AVMediaTypeVideo )
    let assetTrack = videoTracks[0]
    let layerInstruction = AVMutableVideoCompositionLayerInstruction( assetTrack: assetTrack )

    let transform = CGAffineTransformMake( 1.5,  // fixed transform in this example
        0.0,
        0.0,
        2.0,
        -100.0,
        -100.0 )

    layerInstruction.setTransformRampFromStartTransform( transform,
                                         toEndTransform: transform,
                                              timeRange: CMTimeRangeMake( kCMTimeZero, self.asset.duration ) )

    instruction.layerInstructions = [ layerInstruction ]
    composition.instructions = [ instruction ]

    return composition
}

How can I avoid the second and later frame being black and successfully export all frames resized and cropped?


Solution

  • The frameDuration property of your composition is the time interval of each frame - that is, the frames per second of your video. You have currently set this to the length of your entire asset track, so one frame lasts the whole length of the track.

    The following code, for example, would set your composition to a frame rate of 30 frames per second:

    composition.frameDuration = CMTimeMake(1, 30)
    

    Ideally, you would use the frame rate of your video track (AVAssetTrack has a nominalFrameRate property) as follows:

    composition.frameDuration = CMTimeMake(1, assetTrack.nominalFrameRate)