1

I'm am working on an App that adds dynamic text (like subtitles) to a video. There are a number of similar questions, but none really have a working answer. I had high hopes for using "ContentAnimate" instead of "contents" as the animationKeyPath based on Synchronising image, text, and positioning with CoreAnimation but that didn't work for me.

I can get the text to show on the video as an overlay, but the animation doesn't work - I always see the original base text from the CATextLayer. Here is how I set up the subtitle and overlay:

        // 1 - Set up the text layer
        CATextLayer *subtitle1Text = [[CATextLayer alloc] init];
        [subtitle1Text setFont:@"Helvetica-Bold"];
        //[subtitle1Text setString:[NSString stringWithFormat:@"%@: %@",  bookmark.creatorMonkeyName, bookmark.info]];
        [subtitle1Text setString:@"Place holder"];

        [subtitle1Text setFontSize:36];
        [subtitle1Text setFrame:CGRectMake(0, 0, videoSize.width, 100)];
        [subtitle1Text setAlignmentMode:kCAAlignmentCenter];
        [subtitle1Text setForegroundColor:[[UIColor whiteColor] CGColor]];


        // 2 - The  overlay
        CALayer *overlayLayer = [CALayer layer];
        [overlayLayer addSublayer:subtitle1Text];
        overlayLayer.frame = CGRectMake(0, 0, videoSize.width, videoSize.height);
        [overlayLayer setMasksToBounds:YES];
        overlayLayer.hidden = YES;

        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:overlayLayer];
        videoComp.renderSize = videoSize;
        videoComp.frameDuration = CMTimeMake(1, (int32_t) fps);

Here is how I add the animation:

            CABasicAnimation *showCommentAnimation2 = [CABasicAnimation animationWithKeyPath:@"contents"];

            [CATransaction begin];
            [CATransaction setDisableActions:YES];

            overlayLayer.hidden = NO;
            showCommentAnimation2.fromValue = @"Hello";
            showCommentAnimation2.toValue = @"Goodbye";
            showCommentAnimation2.beginTime = bookmark.relativeTimeStamp;
            showCommentAnimation2.duration = commentTimeDisplayed;
            [subtitle1Text addAnimation:showCommentAnimation2 forKey:[@(i) stringValue]]; //"i" is an iterator, This should allow multiple animations of the same type
            [CATransaction setDisableActions:NO];
            [CATransaction commit];

A subtitle pops up and disappears - but I always get the text "place holder" never "Hello" or "Goodbye".

I export the video to the camera roll using:

        videoComp.animationTool = [AVVideoCompositionCoreAnimationTool
                                   videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
    _assetExport.videoComposition = videoComp;
    _assetExport.outputFileType = @"public.mpeg-4";
    _assetExport.outputURL = outputFileUrl;
    [_assetExport exportAsynchronouslyWithCompletionHandler:
     ^(void ) {
         if (AVAssetExportSessionStatusCompleted == _assetExport.status) {
             DLog(@"AVAssetExportSessionStatusCompleted");
             ALAssetsLibrary *library = [[[ALAssetsLibrary alloc] init] autorelease];
             DLog(@"About to copy video to camera roll");
             //move video to camera roll

             [library writeVideoAtPathToSavedPhotosAlbum:outputFileUrl completionBlock:^(NSURL *assetURL, NSError *error) {
                 [self removeWaitingScreen];
                 dispatch_async(dispatch_get_main_queue(), ^(void){
                     MPMoviePlayerViewController* theMovie = [[MPMoviePlayerViewController alloc] initWithContentURL: assetURL];
                     [_viewController presentMoviePlayerViewControllerAnimated:theMovie];

                 });
             }];
         } else{
             // a failure may happen because of an event out of your control
             // for example, an interruption like a phone call comming in
             // make sure and handle this case appropriately
             DLog(@"Error - Export Session Status: %ld", (long)_assetExport.status);
             dispatch_async(dispatch_get_main_queue(), ^(void){
                 [self removeWaitingScreen];
                 [[[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", NSStringFromClass([self class]))
                                              message:NSLocalizedString(@"Video Creation Failed", nil)
                                             delegate:nil
                                    cancelButtonTitle:NSLocalizedString(@"OK", nil)
                                    otherButtonTitles: nil] autorelease] show];
             });
         }

   [videoComp release];
   }];

}];

Can anyone help?

If there is a better way to burn subtitles into a video, I am open to suggestions.

Thanks.

Community
  • 1
  • 1
JacobU
  • 11
  • 4

2 Answers2

1

An animation with key path contents can't be used with string values (@"Hello" and @"Goodbye"). The contents property of a layer holds an image, set manually or drawn by the layer itself.

Simply setting the text layer's string property should be enough to cause an implicit animation. However, it sounds like you need an explicit animation in order to control the beginTime. The way to do this is with CATransition. See here for an example.

jtbandes
  • 115,675
  • 35
  • 233
  • 266
  • OK. I'll assume that the same holds true (no string values for the contents property) for CATextLayer as well. I don't understand how CATransition helps - it is for transition between two layers, and I can't use it to set a property in the layer. – JacobU Sep 07 '15 at 10:20
  • CATransition causes the system to snapshot the layer, and animates between snapshots. So you just add a transition and then modify the layer to your liking. Glad you got it working though :) – jtbandes Sep 07 '15 at 18:19
0

Thanks to jtbandes I figured it out.

Step 1: Create an animation that changes the hidden property for that layer at the right time:

CAKeyframeAnimation *showCommentAnimation = [CAKeyframeAnimation animationWithKeyPath:@"hidden"];
        [showCommentAnimation setValue:@"hidestring" forKey:@"MyAnimationType"];
        showCommentAnimation.values = @[@(NO),@(YES)];
        showCommentAnimation.keyTimes = @[@0.0, @1.0];
        showCommentAnimation.calculationMode = kCAAnimationDiscrete;
        showCommentAnimation.removedOnCompletion = NO;
        showCommentAnimation.delegate = self;

Step 2: Create a separate CATextLayer for each comment

CATextLayer *subtitle1Text = [[CATextLayer alloc] init];

change the text for that layer to the appropriate subtitle:

[subtitle1Text setString:@"Actual subtitle for that layer"];

Step 3: add the animation with the appropriate times:

showCommentAnimation.beginTime = subtitle.relativeTimeStamp;
[subtitle1Text addAnimation:showCommentAnimation forKey:[@"setstring" stringByAppendingString:[@(i) stringValue]]]; //use showCommentAnimation to set hidden propert
JacobU
  • 11
  • 4