AVAsset incorrect transform after adding watermark

I am trying to create a new video file by merging a video and image together (Like a watermark).

I can get it to work to a certain extent, however the video gets messed up unless I increase the width to equal the height.

I think there must be an issue with the transform somewhere.

This is how the video turns out:

If I set the width to the same as the height, the video looks fine (But with black bars).


Can anyone shed some light on this please?


Here is my code:

- (void)mergeVideoUrl:(NSURL *)videoURL andOverlayImage:(UIImage *)overlayImage
{
    // Create an AVURLAsset
    AVURLAsset* videoAsset = [[AVURLAsset alloc]initWithURL:videoURL options:nil];
    AVMutableComposition* mixComposition = [AVMutableComposition composition];

    AVMutableCompositionTrack *compositionVideoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVAssetTrack *clipVideoTrack = [[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                                   ofTrack:clipVideoTrack
                                    atTime:kCMTimeZero
                                     error:nil];

    [compositionVideoTrack setPreferredTransform:[[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] preferredTransform]];

    CGSize videoSize = [clipVideoTrack naturalSize];

    // Get our image layer
    CALayer *aLayer = [CALayer layer];
    aLayer.contents = (id)overlayImage.CGImage;
    aLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
    aLayer.opacity = 1.0;

    // Sort the layer order
    CALayer *parentLayer = [CALayer layer];
    CALayer *videoLayer = [CALayer layer];
    parentLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
    videoLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
    [parentLayer addSublayer:videoLayer];
    [parentLayer addSublayer:aLayer];

    // Create the composition and add the instructions to insert the layer
    AVMutableVideoComposition* videoComp = [AVMutableVideoComposition videoComposition];
    videoComp.renderSize = videoSize;
    videoComp.frameDuration = CMTimeMake(1, 30);
    videoComp.animationTool = [AVVideoCompositionCoreAnimationTool      videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

    // Instruction
    AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    instruction.timeRange = CMTimeRangeMake(kCMTimeZero, [mixComposition duration]);
    AVAssetTrack *videoTrack = [[mixComposition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    AVMutableVideoCompositionLayerInstruction* layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
    instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
    videoComp.instructions = [NSArray arrayWithObject: instruction];

    // Export
    AVAssetExportSession *assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetHighestQuality];
    assetExport.videoComposition = videoComp;

    NSString* videoName = @"mynewwatermarkedvideo.mov";

    NSString *exportPath = [NSTemporaryDirectory() stringByAppendingPathComponent:videoName];
    NSURL    *exportUrl = [NSURL fileURLWithPath:exportPath];

    if ([[NSFileManager defaultManager] fileExistsAtPath:exportPath])
    {
        [[NSFileManager defaultManager] removeItemAtPath:exportPath error:nil];
    }

    assetExport.outputFileType = AVFileTypeQuickTimeMovie;
    assetExport.outputURL = exportUrl;
    assetExport.shouldOptimizeForNetworkUse = YES;

    [assetExport exportAsynchronouslyWithCompletionHandler:^(void)
     {
         //FINALIZATION CODE HERE
         NSLog(@"DONE: %@", exportUrl);
     }];
}

Replies

I've wasted a week on this 😟 surely someone has added a watermark image to video?


If I use a video that is 320x568 it works fine. If I use one that is 376x668 it comes out all messed up, even though the resulting video has the same dimensions as the input video.


The video get's messed up by adding this composition:

[AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

The trick is to use an appropriate set of CGAffineTransform instructions in an AVMutableCompositionInstruction to translate the size/location/rotation of your source video to that of the destination. This does require a bit of fiddling with transforms, and note also that they are not applied commutatively, so its wise to take your time, apply very small transform values (in the case of rotation, for example) to be sure that you see each step of the AffineTransform as it is applied to your Composition.