I'm trying to bake a CALayer into portrait-mode video (on export) using an AVMutableComposition, an AVMutableVideoComposition and a AVVideoCompositionCoreAnimationTool on iOS 4.3. This all works in landscape. If I capture video in portrait, however, the AVVideoCompositionCoreAnimationTool is ignoring the transform on the video track. That is, for portrait-mode video, I am setting AVMutableCompositionTrack.preferredTransform to the preferredTransform value from the original asset video track. As long as I don't use a AVVideoCompositionCoreAnimationTool, this works, and the video comes out in portrait mode. As soon as I add a AVVideoCompositionCoreAnimationTool and CALayer, however, the file comes out in landscape. (The CALayer appears correctly, but the video behind it is on its side, and the aspect ratio of the file is off). I've tried applying the transform to the CALayer, and setting a transform in the ACVideoComposition. Neither of these change the orientation of the file produced (it is still 480x369, not 360x480). Is there a way to render portrait-mode video with AVVideoCompositionCoreAnimationTool?
First I set up a AVMutableComposition and AVMutableVideoComposition
AVMutableComposition *composition = [AVMutableComposition composition];
AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVURLAsset *videoAsset = [AVURLAsset URLAssetWithURL:url options:nil];
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, [videoAsset duration]);
AVAssetTrack *clipVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
CGSize videoSize = CGSizeApplyAffineTransform(clipVideoTrack.naturalSize, clipVideoTrack.preferredTransform);
videoSize.width = fabs(videoSize.width);
videoSize.height = fabs(videoSize.height);
CMTime titleDuration = CMTimeMakeWithSeconds(5, 600);
CMTimeRange titleRange = CMTimeRangeMake(kCMTimeZero, titleDuration);
[compositionVideoTrack insertTimeRange:titleRange ofTrack:nil atTime:kCMTimeZero error:nil];
[compositionVideoTrack insertTimeRange:timeRange ofTrack:clipVideoTrack atTime:titleDuration error:nil];
compositionVideoTrack.preferredTransform = clipVideoTrack.preferredTransform;
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
passThroughInstruction.timeRange = CMTimeRangeMake(kCMTimeZero, [composition duration]);
AVAssetTrack *videoTrack = [[composition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
videoComposition.instructions = [NSArray arrayWithObject:passThroughInstruction];
videoComposition.frameDuration = CMTimeMake(1, 30);
videoComposition.renderSize = videoSize;
videoComposition.renderScale = 1.0;
And a CALayer with a title
CALayer *animationLayer = [CALayer layer];
animationLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
CATextLayer *titleLayer = [CATextLayer layer];
titleLayer.string = [effect valueForKey:@"title"];
titleLayer.font = [effect valueForKey:@"font"];
titleLayer.fontSize = 30;
titleLayer.alignmentMode = kCAAlignmentCenter;
titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6);
[animationLayer addSublayer:titleLayer];
titleLayer.anchorPoint = CGPointMake(0.5, 0.5);
titleLayer.position = CGPointMake(CGRectGetMidX(layer.bounds), CGRectGetMidY(layer.bounds));
CABasicAnimation *fadeAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeAnimation.fromValue = [NSNumber numberWithFloat:1.0];
fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];
fadeAnimation.additive = NO;
fadeAnimation.removedOnCompletion = NO;
fadeAnimation.beginTime = 3.5;
fadeAnimation.duration = 1.0;
fadeAnimation.fillMode = kCAFillModeBoth;
[titleLayer addAnimation:fadeAnimation forKey:nil];
Finally I add the CALayer to the AVMutableVideoComposition
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
parentLayer.anchorPoint = CGPointMake(0, 0);
parentLayer.position = CGPointMake(0, 0);
videoLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height);
[parentLayer addSublayer:videoLayer];
videoLayer.anchorPoint = CGPointMake(0.5, 0.5);
videoLayer.position = CGPointMake(CGRectGetMidX(parentLayer.bounds), CGRectGetMidY(parentLayer.bounds));
[parentLayer addSublayer:layer];
animationLayer.anchorPoint = CGPointMake(0.5, 0.5);
animationLayer.position = CGPointMake(CGRectGetMidX(parentLayer.bounds), CGRectGetMidY(parentLayer.bounds));
videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
And export!
AVAssetExportSession *exportSession = [[[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetMediumQuality] autorelease];
exportSession.videoComposition = videoComposition;
NSURL *segmentFileURL = //some local URL
exportSession.outputFileType = @"com.apple.quicktime-movie";
exportSession.outputURL = segmentFileURL;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
switch ([exportSession status]) {
case AVAssetExportSessionStatusFailed:
Log(@"Export failed: %@", [exportSession error]);
break;
case AVAssetExportSessionStatusCancelled:
Log(@"Export canceled");
break;
case AVAssetExportSessionStatusCompleted:
Log(@"Export done");
break;
}
}];
This code works in landscape mode, and also in portrait if I remove the line videoComposition.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];