AVMutableVideoComposition повернутое видео, снятое в портретном режиме

Я использовал приведенный ниже код, чтобы добавить наложение изображения поверх видео, а затем экспортировать новое сгенерированное видео в каталог документов. Но странно, видео поворачивается на 90 градусов.

- (void)buildTransitionComposition:(AVMutableComposition *)composition andVideoComposition:(AVMutableVideoComposition *)videoComposition
{
    CMTime nextClipStartTime = kCMTimeZero;
    NSInteger i;

    // Make transitionDuration no greater than half the shortest clip duration.
    CMTime transitionDuration = self.transitionDuration;
    for (i = 0; i < [_clips count]; i++ ) {
        NSValue *clipTimeRange = [_clipTimeRanges objectAtIndex:i];
        if (clipTimeRange) {
            CMTime halfClipDuration = [clipTimeRange CMTimeRangeValue].duration;
            halfClipDuration.timescale *= 2; // You can halve a rational by doubling its denominator.
            transitionDuration = CMTimeMinimum(transitionDuration, halfClipDuration);
        }
    }

    // Add two video tracks and two audio tracks.
    AVMutableCompositionTrack *compositionVideoTracks[2];
    AVMutableCompositionTrack *compositionAudioTracks[2];
    compositionVideoTracks[0] = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    compositionVideoTracks[1] = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    compositionAudioTracks[0] = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    compositionAudioTracks[1] = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];

    CMTimeRange *passThroughTimeRanges = alloca(sizeof(CMTimeRange) * [_clips count]);
    CMTimeRange *transitionTimeRanges = alloca(sizeof(CMTimeRange) * [_clips count]);

    // Place clips into alternating video & audio tracks in composition, overlapped by transitionDuration.
    for (i = 0; i < [_clips count]; i++ ) {
        NSInteger alternatingIndex = i % 2; // alternating targets: 0, 1, 0, 1, ...
        AVURLAsset *asset = [_clips objectAtIndex:i];
        NSValue *clipTimeRange = [_clipTimeRanges objectAtIndex:i];
        CMTimeRange timeRangeInAsset;
        if (clipTimeRange)
            timeRangeInAsset = [clipTimeRange CMTimeRangeValue];
        else
            timeRangeInAsset = CMTimeRangeMake(kCMTimeZero, [asset duration]);

        AVAssetTrack *clipVideoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
        [compositionVideoTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipVideoTrack atTime:nextClipStartTime error:nil];

        /*
        CGAffineTransform t = clipVideoTrack.preferredTransform;
        NSLog(@"Transform1 : %@",t);
        */
        AVAssetTrack *clipAudioTrack = [[asset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
        [compositionAudioTracks[alternatingIndex] insertTimeRange:timeRangeInAsset ofTrack:clipAudioTrack atTime:nextClipStartTime error:nil];

        // Remember the time range in which this clip should pass through.
        // Every clip after the first begins with a transition.
        // Every clip before the last ends with a transition.
        // Exclude those transitions from the pass through time ranges.
        passThroughTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, timeRangeInAsset.duration);
        if (i > 0) {
            passThroughTimeRanges[i].start = CMTimeAdd(passThroughTimeRanges[i].start, transitionDuration);
            passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
        }
        if (i+1 < [_clips count]) {
            passThroughTimeRanges[i].duration = CMTimeSubtract(passThroughTimeRanges[i].duration, transitionDuration);
        }

        // The end of this clip will overlap the start of the next by transitionDuration.
        // (Note: this arithmetic falls apart if timeRangeInAsset.duration < 2 * transitionDuration.)
        nextClipStartTime = CMTimeAdd(nextClipStartTime, timeRangeInAsset.duration);
        nextClipStartTime = CMTimeSubtract(nextClipStartTime, transitionDuration);

        // Remember the time range for the transition to the next item.
        transitionTimeRanges[i] = CMTimeRangeMake(nextClipStartTime, transitionDuration);
    }

    // Set up the video composition if we are to perform crossfade or push transitions between clips.
    NSMutableArray *instructions = [NSMutableArray array];

    // Cycle between "pass through A", "transition from A to B", "pass through B", "transition from B to A".
    for (i = 0; i < [_clips count]; i++ ) {
        NSInteger alternatingIndex = i % 2; // alternating targets

        // Pass through clip i.
        AVMutableVideoCompositionInstruction *passThroughInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
        passThroughInstruction.timeRange = passThroughTimeRanges[i];
        AVMutableVideoCompositionLayerInstruction *passThroughLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];
        /*
        CGAffineTransform rotationTransform = CGAffineTransformMakeRotation(M_PI_2);
        CGAffineTransform rotateTranslate = CGAffineTransformTranslate(rotationTransform,320,0);
        [passThroughLayer setTransform:rotateTranslate atTime:kCMTimeZero];
         */
        passThroughInstruction.layerInstructions = [NSArray arrayWithObject:passThroughLayer];
        [instructions addObject:passThroughInstruction];

        if (i+1 < [_clips count]) {
            // Add transition from clip i to clip i+1.

            AVMutableVideoCompositionInstruction *transitionInstruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
            transitionInstruction.timeRange = transitionTimeRanges[i];
            AVMutableVideoCompositionLayerInstruction *fromLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[alternatingIndex]];
            AVMutableVideoCompositionLayerInstruction *toLayer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTracks[1-alternatingIndex]];

            if (self.transitionType == SimpleEditorTransitionTypeCrossFade) {
                // Fade out the fromLayer by setting a ramp from 1.0 to 0.0.
                [fromLayer setOpacityRampFromStartOpacity:1.0 toEndOpacity:0.0 timeRange:transitionTimeRanges[i]];
            }
            else if (self.transitionType == SimpleEditorTransitionTypePush) {
                // Set a transform ramp on fromLayer from identity to all the way left of the screen.
                [fromLayer setTransformRampFromStartTransform:CGAffineTransformIdentity toEndTransform:CGAffineTransformMakeTranslation(-composition.naturalSize.width, 0.0) timeRange:transitionTimeRanges[i]];
                // Set a transform ramp on toLayer from all the way right of the screen to identity.
                [toLayer setTransformRampFromStartTransform:CGAffineTransformMakeTranslation(+composition.naturalSize.width, 0.0) toEndTransform:CGAffineTransformIdentity timeRange:transitionTimeRanges[i]];
            }

            transitionInstruction.layerInstructions = [NSArray arrayWithObjects:fromLayer, toLayer, nil];
            [instructions addObject:transitionInstruction];
        }
    }

    videoComposition.instructions = instructions;


}

Пожалуйста, помогите, так как я не могу экспортировать портретное видео в правильном режиме. Любая помощь приветствуется. Спасибо.


person Dhruv    schedule 27.08.2012    source источник


Ответы (6)


По умолчанию, когда вы экспортируете видео с помощью AVAssetExportSession, видео будет повернуто от исходной ориентации. Вы должны применить его преобразование, чтобы установить его точную ориентацию. Пожалуйста, попробуйте ниже код, чтобы сделать то же самое.

- (AVMutableVideoCompositionLayerInstruction *)layerInstructionAfterFixingOrientationForAsset:(AVAsset *)inAsset 
                                                                                     forTrack:(AVMutableCompositionTrack *)inTrack
                                                                                       atTime:(CMTime)inTime
{
    //FIXING ORIENTATION//
    AVMutableVideoCompositionLayerInstruction *videolayerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:inTrack];
    AVAssetTrack *videoAssetTrack = [[inAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
    UIImageOrientation videoAssetOrientation_  = UIImageOrientationUp;
    BOOL  isVideoAssetPortrait_  = NO;
    CGAffineTransform videoTransform = videoAssetTrack.preferredTransform;

    if(videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0)  {videoAssetOrientation_= UIImageOrientationRight; isVideoAssetPortrait_ = YES;}
    if(videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0)  {videoAssetOrientation_ =  UIImageOrientationLeft; isVideoAssetPortrait_ = YES;}
    if(videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0)   {videoAssetOrientation_ =  UIImageOrientationUp;}
    if(videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {videoAssetOrientation_ = UIImageOrientationDown;}

    CGFloat FirstAssetScaleToFitRatio = 320.0 / videoAssetTrack.naturalSize.width;

    if(isVideoAssetPortrait_) {
        FirstAssetScaleToFitRatio = 320.0/videoAssetTrack.naturalSize.height;
        CGAffineTransform FirstAssetScaleFactor = CGAffineTransformMakeScale(FirstAssetScaleToFitRatio,FirstAssetScaleToFitRatio);
        [videolayerInstruction setTransform:CGAffineTransformConcat(videoAssetTrack.preferredTransform, FirstAssetScaleFactor) atTime:kCMTimeZero];
    }else{
        CGAffineTransform FirstAssetScaleFactor = CGAffineTransformMakeScale(FirstAssetScaleToFitRatio,FirstAssetScaleToFitRatio);
        [videolayerInstruction setTransform:CGAffineTransformConcat(CGAffineTransformConcat(videoAssetTrack.preferredTransform, FirstAssetScaleFactor),CGAffineTransformMakeTranslation(0, 160)) atTime:kCMTimeZero];
    }
    [videolayerInstruction setOpacity:0.0 atTime:inTime];
    return videolayerInstruction;
}

Я надеюсь, что это поможет вам.

AVAssetTrack *assetTrack = [[inAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

AVMutableCompositionTrack *mutableTrack = [mergeComposition mutableTrackCompatibleWithTrack:assetTrack];

AVMutableVideoCompositionLayerInstruction *assetInstruction = [self layerInstructionAfterFixingOrientationForAsset:inAsset forTrack:myLocalVideoTrack atTime:videoTotalDuration];

Выше приведен код для вызова упомянутого метода, где inAsset — ваш видеоресурс, а videoTotalDuration — общая продолжительность вашего видео, а CMTime.mergeComposition — объект класса AVMutableComposition.

Надеюсь, это поможет.

РЕДАКТИРОВАТЬ: это не какой-либо метод или событие обратного вызова, вы должны вызывать его ожидаемо с требуемыми параметрами, как указано выше.

person BornCoder    schedule 27.08.2012
comment
Я попробовал этот метод, но он не вызывается во время выполнения. Не могли бы вы уточнить его использование, немного больше. Спасибо. :) - person Dhruv; 27.08.2012
comment
Настала очередь моего видео полностью черное. Не могли бы вы помочь мне, почему? - person itsji10dra; 14.07.2015
comment
было бы неплохо, если бы вы могли дать для этого код swift 3. - person Mr.Ghamkhar; 08.05.2017
comment
как он может соответствовать всем размерам экрана? ты написал только 320 - person Ofir Malachi; 12.07.2017
comment
похоже, что основной слой всегда ландшафтный stackoverflow.com/questions/45127420/ - person Ofir Malachi; 16.07.2017
comment
@Mr.Ghamkhar Вы нашли быструю версию этого кода? - person ava; 01.07.2018
comment
@ava ну, это было давно. я думаю, что нашел другое решение сам - person Mr.Ghamkhar; 02.07.2018
comment
@Mr.Ghamkhar спасибо за ответ. Я нашел решение. - person ava; 02.07.2018
comment
Почему это был принятый ответ, если он не работает? У меня тоже полный черный экран. Так же, если на аве нашел решение, почему бы не выложить сюда? - person LilMoke; 22.10.2019

Вот немного более простой способ, если вы просто хотите сохранить исходное вращение.

// Grab the source track from AVURLAsset for example.
AVAssetTrack *assetVideoTrack = [asset tracksWithMediaType:AVMediaTypeVideo].lastObject;

// Grab the composition video track from AVMutableComposition you already made.
AVMutableCompositionTrack *compositionVideoTrack = [composition tracksWithMediaType:AVMediaTypeVideo].lastObject;

// Apply the original transform.    
if (assetVideoTrack && compositionVideoTrack) {
   [compositionVideoTrack setPreferredTransform:assetVideoTrack.preferredTransform];
}

// Export...
person dizy    schedule 24.02.2014
comment
Я потратил на это шесть часов! Не могу поверить, что это так просто. Жаль, что я не прочитал этот пост раньше. - person etayluz; 07.04.2015
comment
если я хочу объединить несколько видео в одно, то как я могу получить исходную ориентацию для каждого видео в моем финальном видео ?? - person pigeon_39; 16.04.2017
comment
почему-то у меня это не работает :( stackoverflow.com/questions/45127420/ - person Ofir Malachi; 16.07.2017
comment
Дизи, это только в том случае, если я использую только AVMutableCompositionTrack, без LayerInstruction, верно? - person Roi Mulia; 25.07.2017

Используйте приведенный ниже метод, чтобы установить правильную ориентацию в соответствии с ориентацией видеоресурса в AVMutableVideoComposition.

-(AVMutableVideoComposition *) getVideoComposition:(AVAsset *)asset
{
  AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];
  AVMutableComposition *composition = [AVMutableComposition composition];
  AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
  CGSize videoSize = videoTrack.naturalSize;
  BOOL isPortrait_ = [self isVideoPortrait:asset];
  if(isPortrait_) {
      NSLog(@"video is portrait ");
      videoSize = CGSizeMake(videoSize.height, videoSize.width);
  }
  composition.naturalSize     = videoSize;
  videoComposition.renderSize = videoSize;
  // videoComposition.renderSize = videoTrack.naturalSize; //
  videoComposition.frameDuration = CMTimeMakeWithSeconds( 1 / videoTrack.nominalFrameRate, 600);

  AVMutableCompositionTrack *compositionVideoTrack;
  compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
  [compositionVideoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, asset.duration) ofTrack:videoTrack atTime:kCMTimeZero error:nil];
  AVMutableVideoCompositionLayerInstruction *layerInst;
  layerInst = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];
  [layerInst setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
  AVMutableVideoCompositionInstruction *inst = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
  inst.timeRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
  inst.layerInstructions = [NSArray arrayWithObject:layerInst];
  videoComposition.instructions = [NSArray arrayWithObject:inst];
  return videoComposition;
}


-(BOOL) isVideoPortrait:(AVAsset *)asset
{
  BOOL isPortrait = FALSE;
  NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeVideo];
  if([tracks    count] > 0) {
    AVAssetTrack *videoTrack = [tracks objectAtIndex:0];

    CGAffineTransform t = videoTrack.preferredTransform;
    // Portrait
    if(t.a == 0 && t.b == 1.0 && t.c == -1.0 && t.d == 0)
    {
        isPortrait = YES;
    }
    // PortraitUpsideDown
    if(t.a == 0 && t.b == -1.0 && t.c == 1.0 && t.d == 0)  {

        isPortrait = YES;
    }
    // LandscapeRight
    if(t.a == 1.0 && t.b == 0 && t.c == 0 && t.d == 1.0)
    {
        isPortrait = NO;
    }
    // LandscapeLeft
    if(t.a == -1.0 && t.b == 0 && t.c == 0 && t.d == -1.0)
    {
        isPortrait = NO;
    }
   }
  return isPortrait;
}
person Paresh Navadiya    schedule 03.05.2013

Это код для Swift 4, который отлично работает для объединения видео в наилучшей ориентации.

let mainComposition = AVMutableComposition()
let compositionVideoTrack = mainComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
let soundtrackTrack = mainComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
var insertTime = kCMTimeZero

for videoAsset in arrayVideos {
    try! compositionVideoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: videoAsset.tracks(withMediaType: .video)[0], at: insertTime)
    var assetVideoTrack = (videoAsset.tracks(withMediaType: AVMediaType.video)).last as! AVAssetTrack
    var compositionVideoTrack = (mainComposition.tracks(withMediaType: AVMediaType.video)).last as! AVMutableCompositionTrack
    compositionVideoTrack.preferredTransform = assetVideoTrack.preferredTransform
    
    try! soundtrackTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: videoAsset.tracks(withMediaType: .audio)[0], at: insertTime)
        
        insertTime = CMTimeAdd(insertTime, videoAsset.duration)  
    }
    
    let outputFileURL = URL(fileURLWithPath: NSTemporaryDirectory() + "merge1uupo.MOV")

    let fileManager = FileManager()
    //fileManager.removeItemIfExisted(outputFileURL)
    let exporter = AVAssetExportSession(asset: mainComposition, presetName: AVAssetExportPresetHighestQuality)
    
    exporter?.outputURL = outputFileURL
    exporter?.outputFileType = AVFileType.mov
    exporter?.shouldOptimizeForNetworkUse = true
    
    exporter?.exportAsynchronously {
        DispatchQueue.main.async {
          
        // Do what you want at the end    
        }
}
person Mohamed Dev    schedule 01.05.2018
comment
Я получаю Невозможно назначить свойство: 'preferredTransform' является свойством только для получения - person Chewie The Chorkie; 06.08.2018
comment
то же самое здесь, в чем дело? - person Yaroslav Dukal; 23.11.2018

В быстром головокружительном ответе .. это работает для меня

  var assetVideoTrack = (sourceAsset.tracksWithMediaType(AVMediaTypeVideo)).last as! AVAssetTrack

  var compositionVideoTrack = (composition.tracksWithMediaType(AVMediaTypeVideo)).last as! AVMutableCompositionTrack

  if (assetVideoTrack.playable && compositionVideoTrack.playable) {

       compositionVideoTrack.preferredTransform = assetVideoTrack.preferredTransform
   }
person Community    schedule 09.10.2015
comment
Как вы начинаете композицию? - person Hong Zhou; 27.08.2016
comment
PreferredTransform является свойством getOnly и не может быть изменен..? - person Yaroslav Dukal; 23.11.2018

Свифт 2:


do {
            let paths = NSSearchPathForDirectoriesInDomains(
                NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
            let documentsDirectory: AnyObject = paths[0]
            //this will be changed to accommodate dynamic videos
            let dataPath = documentsDirectory.stringByAppendingPathComponent(videoFileName+".MOV")
            let videoAsset = AVURLAsset(URL: NSURL(fileURLWithPath: dataPath), options: nil)
            let imgGenerator = AVAssetImageGenerator(asset: videoAsset)
            imgGenerator.appliesPreferredTrackTransform = true
            let cgImage = try imgGenerator.copyCGImageAtTime(CMTimeMake(0, 1), actualTime: nil)
            let uiImage = UIImage(CGImage: cgImage)

            videoThumb.image = uiImage
        } catch let err as NSError {
            print("Error generating thumbnail: \(err)")
        }
person Trevor Jordet    schedule 01.04.2016
comment
imgGenerator.appliesPreferredTrackTransform = true - person Trevor Jordet; 01.04.2016