Video rotation issue when merging videos: iOS

We were working on one program where we had to merge two videos. Program was divided into two primary task:

  1. Export video from multiple images
  2. Merge that exported video with another video from device

Our problem occurred during second task. We did merge the videos but the video output was not satisfactory. Why? Whenever we merged our video made up of images with second video, then second video always turned up rotated in final output. Our both video input were naturally portrait but second video always turned up in landscape. It bugged us for hours. We did improvised our algorithm and was able to somehow preserve the natural rotation of all videos. It was quite fun working on this module and faced lots of different issues:

  1. Video rotation
  2. Incorrect render size of output video
  3. Translating the position of each video

Here is the summary on what we performed:

  1. Set the render size to 320 * 480; because we wanted our output video to be of that size only. That was our requirement.
  2. Kept the 30 fps.
  3. We scaled each video to fit into our render size, maintaining aspect ratio and rotation.
  4. Translated each video to the centre of render screen for consistency.

Lets have a look at code:

- (void)paddyMergerVideoVersion :(NSMutableArray *)videoPathArray
{
    AVMutableComposition *composition = [AVMutableComposition composition];
    
    
    AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
    
    videoComposition.frameDuration = CMTimeMake(1,30);
    
    videoComposition.renderScale = 1.0;
    
    AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    
    AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];
    
    float time = 0;
    
    for (int i = 0; i < videoPathArray.count; i++) {
        
        AVURLAsset *sourceAsset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:[videoPathArray objectAtIndex:i]] options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:AVURLAssetPreferPreciseDurationAndTimingKey]];
        
        NSError *error = nil;
        
        BOOL ok = NO;
        AVAssetTrack *sourceVideoTrack = [[sourceAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
        
        
        CGSize temp = CGSizeApplyAffineTransform(sourceVideoTrack.naturalSize, sourceVideoTrack.preferredTransform);
        CGSize size = CGSizeMake(fabsf(temp.width), fabsf(temp.height));
        CGAffineTransform transform = sourceVideoTrack.preferredTransform;
        
        videoComposition.renderSize = CGSizeMake(320, 480);
        
        if (size.width > size.height) {
            
            [layerInstruction setTransform:transform atTime:CMTimeMakeWithSeconds(time, 30)];
        }
        else {
            
            
            float s = 320.0/480.0;
            
            
            CGAffineTransform new = CGAffineTransformConcat(transform, CGAffineTransformMakeScale(s,s));
            
            
            float x = (320 - size.width*s)/2;
            float y = (480 - size.height*s)/2;
            
            CGAffineTransform newer = CGAffineTransformConcat(new, CGAffineTransformMakeTranslation(x, y));
            
            [layerInstruction setTransform:newer atTime:CMTimeMakeWithSeconds(time, 30)];
        }
        
        
        
        ok = [compositionVideoTrack insertTimeRange:sourceVideoTrack.timeRange ofTrack:sourceVideoTrack atTime:[composition duration] error:&error];
        
        if (!ok) {
            // Deal with the error.
            NSLog(@"something went wrong");
        }      
        
        time += CMTimeGetSeconds(sourceVideoTrack.timeRange.duration);
        
    }
    
    
    
    instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
    instruction.timeRange = compositionVideoTrack.timeRange;
    
    
    videoComposition.instructions = [NSArray arrayWithObject:instruction];
    
    
    //----------get lost--------
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *myPathDocs =[documentsDirectory stringByAppendingPathComponent:@"mergeVideo.mov"];
    
    NSURL *url = [NSURL fileURLWithPath:myPathDocs];
    
    AVAssetExportSession *exporter = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
    exporter.outputURL=url;
    [exporter setVideoComposition:videoComposition];
    exporter.outputFileType = AVFileTypeQuickTimeMovie;
    
    [exporter exportAsynchronouslyWithCompletionHandler:^
     {
         dispatch_async(dispatch_get_main_queue(), ^{
             [self exportDidFinish:exporter];
         });
     }];
}

Finally export it!!

- (void)exportDidFinish:(AVAssetExportSession*)session
{
    NSURL *outputURL = session.outputURL;
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:outputURL]) {
        [library writeVideoAtPathToSavedPhotosAlbum:outputURL
                                    completionBlock:^(NSURL *assetURL, NSError *error){
                                        dispatch_async(dispatch_get_main_queue(), ^{
                                            if (error) {
                                                NSLog(@"writeVideoToAssestsLibrary failed: %@", error);
                                            }else{
                                                NSLog(@"Writing3");
                                            }
                                            
                                        });
                                        
                                    }];
    }
}

One Comment

  • It’s not working for more than two videos.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>