Add a text overlay with AVMutableVideoComposition to a specific timerange
Asked Answered
I

2

7

I have found a few examples that show how to add a text overlay on a video.

Ray's Tutorial - http://www.raywenderlich.com/30200/avfoundation-tutorial-adding-overlays-and-animations-to-videos

This SO answer - How can I add a watermark in a captured video on iOS

There were a few others that I referenced as well. My code looks almost identical to that answer and I've been trying to tweak it to get the text overlay to only show up for a second or two at the beginning of the video. Any help on how I could accomplish that?

Here is my code. This works as is to export the video with the overlay being shown for the entire duration.

if(vA) {
    videoCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    [videoCompositionTrack insertTimeRange:videoTimerange ofTrack:[[vA tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] atTime:kCMTimeZero error:&error];
    [videoCompositionTrack setPreferredTransform:[[[vA tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0] preferredTransform]];
    if(error)
        NSLog(@"%@", error);
}

if(aA) {
    audioCompositionTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    [audioCompositionTrack insertTimeRange:audioTimerange ofTrack:[[aA tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0] atTime:kCMTimeZero error:&error];
    if(error)
        NSLog(@"%@", error);
}

CGSize videoSize = [videoCompositionTrack naturalSize];

UIImage *myImage = [UIImage imageNamed:@"cover.png"];
CALayer *aLayer = [CALayer layer];
aLayer.contents = (id)myImage.CGImage;
aLayer.frame = CGRectMake(5, 25, 100, 56);
aLayer.opacity = 0.7;

CATextLayer *titleLayer = [CATextLayer layer];
titleLayer.string = titleText;
titleLayer.fontSize = 18;
titleLayer.foregroundColor = titleColor.CGColor;
titleLayer.alignmentMode = kCAAlignmentCenter;
titleLayer.frame = CGRectMake(20, 10, videoSize.width - 40, 20);
[titleLayer displayIfNeeded];

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];
if(titleText && [titleText length] > 0) {
    [parentLayer addSublayer:titleLayer];
}

AVMutableVideoComposition *videoComp = [AVMutableVideoComposition videoComposition];
videoComp.renderSize = videoSize;
videoComp.frameDuration = CMTimeMake(1, 30);
videoComp.animationTool = [AVVideoCompositionCoreAnimationTool videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];

AVAssetTrack *videoTrack = [[mixComposition tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
[instruction setTimeRange:CMTimeRangeMake(kCMTimeZero, [mixComposition duration])];
instruction.layerInstructions = [NSArray arrayWithObject:layerInstruction];
videoComp.instructions = [NSArray arrayWithObject:instruction];

AVAssetExportSession *_assetExport = [[AVAssetExportSession alloc] initWithAsset:mixComposition presetName:AVAssetExportPresetMediumQuality];
_assetExport.videoComposition = videoComp;
_assetExport.outputFileType = AVFileTypeMPEG4;
_assetExport.outputURL = outputFileUrl;

[_assetExport exportAsynchronouslyWithCompletionHandler:^{
    AVAssetExportSessionStatus status = [_assetExport status];
    switch (status) {
        case AVAssetExportSessionStatusFailed:
            NSLog(@"Export Failed");
            NSLog(@"Export Error: %@", [_assetExport.error localizedDescription]);
            NSLog(@"Export Error Reason: %@", [_assetExport.error localizedFailureReason]);
            break;
        case AVAssetExportSessionStatusCompleted:
            NSLog(@"Export Completed");
            [self performSelectorOnMainThread:@selector(updateProgressIndicator:) withObject:[NSNumber numberWithFloat:2] waitUntilDone:YES];
            break;
        case AVAssetExportSessionStatusUnknown:
            NSLog(@"Export Unknown");
            break;
        case AVAssetExportSessionStatusExporting:
            NSLog(@"Export Exporting");
            break;
        case AVAssetExportSessionStatusWaiting:
            NSLog(@"Export Waiting");
            break;
    }
}];
Idaho answered 10/2, 2014 at 17:57 Comment(0)
I
10

I figured out what I needed to do. It wasn't anything special really. It just required a better understanding of what was all possible.

Basically all I had to do was add a basic opacity animation to the layer with text in it.

// My original code for creating the text layer
CATextLayer *titleLayer = [CATextLayer layer];
.
.
.
[titleLayer displayIfNeeded];

// the code for the opacity animation which then removes the text
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
[animation setDuration:0];
[animation setFromValue:[NSNumber numberWithFloat:1.0]];
[animation setToValue:[NSNumber numberWithFloat:0.0]];
[animation setBeginTime:1];
[animation setRemovedOnCompletion:NO];
[animation setFillMode:kCAFillModeForwards];
[titleLayer addAnimation:animation forKey:@"animateOpacity"];
Idaho answered 11/2, 2014 at 15:5 Comment(3)
You couldn't set the alpha value directly on the titleLayer? Why did you need to use CABasicAnimation?Fayola
@scott F How to provide specific Time range?Photo
@PayalManiyar, by setting the animation beginTime & durationMizzen
A
6

We should add two opacity animations for Video overlay at particular time rang. First animation for show overlay at start duration and second animation for hide overlay at end duration. We should take care to set fill mode of animation.

Sample code of Video overlay for particular time Rang.

            let textLayer = CATextLayer()
            textLayer.opacity = 0.0
            .
            .
            textLayer.frame = CGRect(x: 0.0, y: 0.0, width: 150.0, height:100.0)
            textLayer.string = "Overlay Text"

            let startVisible = CABasicAnimation.init(keyPath:"opacity")
            startVisible.duration = 0.1    // for fade in duration
            startVisible.repeatCount = 1
            startVisible.fromValue = 0.0  
            startVisible.toValue = 1.0
            startVisible.beginTime = overlay.startTime.seconds // overlay time range start duration
            startVisible.isRemovedOnCompletion = false 
            startVisible.fillMode = kCAFillModeForwards 
            textLayer.add(startVisible, forKey: "startAnimation")

            let endVisible = CABasicAnimation.init(keyPath:"opacity")
            endVisible.duration = 0.1
            endVisible.repeatCount = 1
            endVisible.fromValue = 1.0
            endVisible.toValue = 0.0
            endVisible.beginTime = overlay.endTime.seconds
            endVisible.fillMode = kCAFillModeForwards
            endVisible.isRemovedOnCompletion = false
            textLayer.add(endVisible, forKey: "endAnimation")
Advisory answered 10/9, 2018 at 10:16 Comment(1)
Perfect, that's exactly what I needed, it works well. Thanks for sharingUltima

© 2022 - 2024 — McMap. All rights reserved.