How can I add overlay text on a video, then re-encode it?
Asked Answered
R

3

8

I want to edit video from my iOS application. I want some text on the source video for language subtitles. I then want to save video with that text overlaid. text not just only display purpose. but when i open edited video it show updated video.

Is this possible in an iOS application? If so, how?

Rifling answered 17/4, 2012 at 11:27 Comment(2)
Don't ask us to reply to you on your blog. That's not how this site works. I've removed this part of your question.Shrive
Can we add dynamic texts on videos like names of actors that comes in movies one by one.Clichy
I
11
- (void)addAnimation
{       
    NSString *filePath = [[NSBundle mainBundle] pathForResource:videoName ofType:ext];

    AVURLAsset* videoAsset = [[AVURLAsset alloc]initWithURL:[NSURL fileURLWithPath:filePath]  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];

    UIImage *myImage = [UIImage imageNamed:@"29.png"];
    CALayer *aLayer = [CALayer layer];
    aLayer.contents = (id)myImage.CGImage;
    aLayer.frame = CGRectMake(videoSize.width - 65, videoSize.height - 75, 57, 57);
    aLayer.opacity = 0.65;
    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];

    CATextLayer *titleLayer = [CATextLayer layer];
    titleLayer.string = @"Text goes here";
    titleLayer.font = CFBridgingRetain(@"Helvetica");
    titleLayer.fontSize = videoSize.height / 6;
    //?? titleLayer.shadowOpacity = 0.5;
    titleLayer.alignmentMode = kCAAlignmentCenter;
    titleLayer.bounds = CGRectMake(0, 0, videoSize.width, videoSize.height / 6); //You may need to adjust this for proper display
    [parentLayer addSublayer:titleLayer]; //ONLY IF WE ADDED TEXT

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

    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];

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

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString* VideoName = [NSString stringWithFormat:@"%@/mynewwatermarkedvideo.mp4",documentsDirectory];


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

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

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

    //[strRecordedFilename setString: exportPath];

    [assetExport exportAsynchronouslyWithCompletionHandler:
     ^(void ) {
         dispatch_async(dispatch_get_main_queue(), ^{
             [self exportDidFinish:assetExport];
         });
     }
     ];
}

-(void)exportDidFinish:(AVAssetExportSession*)session
{
    NSURL *exportUrl = session.outputURL;
    ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];

    if ([library videoAtPathIsCompatibleWithSavedPhotosAlbum:exportUrl])
    {
        [library writeVideoAtPathToSavedPhotosAlbum:exportUrl completionBlock:^(NSURL *assetURL, NSError *error)
         {
             dispatch_async(dispatch_get_main_queue(), ^{
                 if (error) {
                     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Video Saving Failed"
                                                                    delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
                     [alert show];
                 } else {
                     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Video Saved" message:@"Saved To Photo Album"
                                                                    delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
                     [alert show];
                 }
             });
         }];

    }
    NSLog(@"Completed");
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"AlertView" message:@"Video is edited successfully." delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
    [alert show];
}
Indubitability answered 9/3, 2013 at 7:8 Comment(12)
@Chaitali Jain i have issue in your code like Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[AVAssetExportSession setVideoComposition:] video composition must have a positive renderSize'Hallel
can we edit using the changing text in the video like movies in which names of actor comes one by one.Clichy
You can use this to add the credits to a movie, but this method burns the text onto the video frames. You can't change them afterwards.Retainer
This code does not work in iOS 8.2 or higher .please give me any suggestion for this...ThanksGrivation
can you please suggest me how to set proper frame for CATextLayer ?#31780560Heelpost
I'm getting Text is Blurry when i write text on image then image merge with video.Can you help me?Surakarta
can we rotate this text in any dimensions? if yes , please let me know.Ferrel
Why output video is getting saved in landscape mode only?Vitamin
I found some issue in your code:- Sound is remove in exiting Video.Also output video orientation is wrong with existing video. Please give me solutionMechellemechlin
@mitulmarsonia I have same issue of 'Text is Blurry' can you find the solution?Showroom
@Showroom No i did not get. :(Surakarta
@mitulmarsonia Do you have any other idea to do this?Showroom
R
2

One way is to create your text overlay as a CoreAnimation CATextLayer, attach it to an AVAssetExportSession's videoComposition, then export your video. The resulting video will have the overlay rendered onto it.

This brings some benefits:

  1. you don't have to stop at CATextLayer - you can construct CALayer trees containing CAGradientLayer, CAShapeLayer, whatever.
  2. being Core Animation layers, many of their properties are animatable, so you get smooth, iOS-style animations in your video for free.

Sounds great, right? There is one little side effect: depending on the export preset you use, your video will inevitably be re-encoded at a constant framerate - for me it was 30fps. To keep file sizes small, I'd deliberately lowered my framerate by omitting redundant frames, so for the sake of a static banner, this was a dealbreaker for me.

There is some Apple sample code called AVEditDemo that demonstrates this feature, among other things. There are instructions for finding it here.

Retainer answered 18/4, 2012 at 9:39 Comment(5)
So... what did you do to get your "static banner" if CoreAnimation was a dealbreaker?Cosmotron
I rendered the banner onto the YUV frames and then passed them onto the encoder (AVAssetWriter).Retainer
can we edit using the changing text in the video like movies in which names of actor comes one by one.Clichy
@RhythmicFistman Can you please suggest me how to set exact frame of CATextlayer see my question #31780560Heelpost
@Clichy have you found solution to add text in movie like movies in which names of actor comes one by one?Gainor
M
1

Using Chaitali Jain code the new videos will be saved without audio. Is there someone, who has an idea on this issue? Thanks!

Monet answered 5/10, 2014 at 8:35 Comment(1)
She didn't added audio track. AVMutableCompositionTrack *compositionAudioTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid]; Add this and audio also savedWan

© 2022 - 2024 — McMap. All rights reserved.