7
votes

I am assembling a bunch of video clips filmed on the iPhone in portrait mode. To assemble them I am taking straightforward approach as follows:

AVURLAsset to get hold of the different videos then shoving these into an AVMutableCompositionTrack and then putting this into an AVMutableComposition which I'm exporting to file with AVAssetExportSession

My problem is that when I come to display the video in a UIWebView, it is appearing in landscape mode. However, if I view any of the component views they appear in Portrait view. Does anyone know how to sort out the orientation. I tried messing around with the AVMutableComposition naturalSize changing the width and height around but that just made my people look short and fat! (whilst on their side)

Thanks in advance for any thoughts / suggestions

Tudor

3
anyone even know how original orientation is stored?Tudor
There's an originalTransform property on the asset track that you can query that is set in certain circumstances.Hunter

3 Answers

15
votes

If all you want is preserve you video orientation you may want to assign AVMutableCompositionTrack prefferedTransition value from AVAssetTrack like this:

AVAssetTrack *videoAssetTrack= [[videoAsset tracksWithMediaType:AVMediaTypeVideo] lastObject];

AVMutableCompositionTrack *videoCompositionTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
[videoCompositionTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration) ofTrack:videoAssetTrack atTime:kCMTimeZero error:&error];

videoCompositionTrack.preferredTransform = videoAssetTrack.preferredTransform;
12
votes

Setup a video composition that will handle rotate + scale:

AVMutableVideoComposition* videoComposition = [[AVMutableVideoComposition videoComposition]retain];
videoComposition.renderSize = CGSizeMake(320, 240);
videoComposition.frameDuration = CMTimeMake(1, 30);

AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(60, 30) );

AVMutableVideoCompositionLayerInstruction* rotator = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:[[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]];
CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation( 0,-320);    
CGAffineTransform rotateBy90Degrees = CGAffineTransformMakeRotation( M_PI_2);
CGAffineTransform shrinkWidth = CGAffineTransformMakeScale(0.66, 1); // needed because Apple does a "stretch" by default - really, we should find and undo apple's stretch - I suspect it'll be a CALayer defaultTransform, or UIView property causing this
CGAffineTransform finalTransform = CGAffineTransformConcat( shrinkWidth, CGAffineTransformConcat(translateToCenter, rotateBy90Degrees) );
[rotator setTransform:finalTransform atTime:kCMTimeZero];

instruction.layerInstructions = [NSArray arrayWithObject: rotator];
videoComposition.instructions = [NSArray arrayWithObject: instruction];
11
votes

Here's some code that adjusted the rotation using the preferred transform:

    // our composition, with one video and one audio track
AVMutableComposition* composition = [AVMutableComposition composition];
AVMutableCompositionTrack *videoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
AVMutableCompositionTrack *audioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

// loop through all the clips, adding them to our track and inserting composition instructions
NSDictionary* options = @{ AVURLAssetPreferPreciseDurationAndTimingKey: @YES };
CMTime insertionPoint = kCMTimeZero;
NSMutableArray* instructions = [NSMutableArray array];
NSError* error = nil;
for (NSURL* outputFileURL in self.movieFileURLs) {
    AVURLAsset *asset = [AVURLAsset URLAssetWithURL:outputFileURL options:options];

    // insert this clip's video track into our composition
    NSArray *videoAssetTracks = [asset tracksWithMediaType:AVMediaTypeVideo];
    AVAssetTrack *videoAssetTrack = (videoAssetTracks.count > 0 ? [videoAssetTracks objectAtIndex:0] : nil);
    [videoTrack insertTimeRange:CMTimeRangeFromTimeToTime(kCMTimeZero, asset.duration) ofTrack:videoAssetTrack atTime:insertionPoint error:&error];

    // create a layer instruction at the start of this clip to apply the preferred transform to correct orientation issues
    AVMutableVideoCompositionLayerInstruction *instruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoAssetTrack];
    [instruction setTransform:videoAssetTrack.preferredTransform atTime:kCMTimeZero];

    // create the composition instructions for the range of this clip
    AVMutableVideoCompositionInstruction * videoTrackInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    videoTrackInstruction.timeRange = CMTimeRangeMake(insertionPoint, asset.duration);
    videoTrackInstruction.layerInstructions = @[instruction];
    [instructions addObject:videoTrackInstruction];

    // insert this clip's audio track into our composition
    NSArray *audioAssetTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
    AVAssetTrack *audioAssetTrack = (audioAssetTracks.count > 0 ? [audioAssetTracks objectAtIndex:0] : nil);
    [audioTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:audioAssetTrack atTime:insertionPoint error:nil];

    // advance the insertion point to the end of the clip
    insertionPoint = CMTimeAdd(insertionPoint, asset.duration);
}

// create our video composition which will be assigned to the player item
AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
videoComposition.instructions = instructions;
videoComposition.frameDuration = CMTimeMake(1, videoTrack.naturalTimeScale);
videoComposition.renderSize = videoTrack.naturalSize;
_videoComposition = videoComposition;