アプリなどを開発するブログ

React Native / Swift / Ruby on Railsなどの学習メモ。


AVFoundationで動画をリサイズ + 反転する

前回の続きで、動画を反転させる処理を加えました。
というのも、iPhoneiPadって、撮影時のデバイスの向きによっては
書きだした際に反転してしまったりするのです。

撮影 => 書出し まではよくても、AVAssetExportSessionを使って変換した際に
たぶんVideoOrientationの値を判定して書出し、みたいなことはやってくれないので、
自分で反転、回転を行う必要があります。

以下コード。  

// 書き出した動画をリサイズ + 回転
+(void)exportVideoFile:(AVAsset *)_asset toPath:(NSString *)filePath
{
    AVAsset* asset = _asset;

    // -----------------------------------------------------------------
    //  タイムラインを用意する
    // -----------------------------------------------------------------

    // 映像と音声を編集するための入れ物を用意する(ビデオ編集ソフトでいうタイムライン?)
    AVMutableVideoComposition* videoComposition = [AVMutableVideoComposition videoComposition];
    videoComposition.renderSize = CGSizeMake(VIDEO_SIZE, VIDEO_SIZE);   // 動画のサイズを決める
    videoComposition.frameDuration = CMTimeMake(1, VIDEO_FRAME_RATE);   // 動画のフレームレートを決める

    // 動画に変換などの処理を加えるための構造体
    AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    instruction.timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(VIDEO_SECONDS, VIDEO_FRAME_RATE) );




    // -----------------------------------------------------------------
    //  縮小、回転などの変換を行う
    // -----------------------------------------------------------------

    // 渡されたAssetの中からビデオトラックを抜き出す
    AVAssetTrack *videoTrack = [[asset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0];

    // ビデオトラックを変換するためのLayerInstruction を抜き出す
    AVMutableVideoCompositionLayerInstruction* transformer = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:videoTrack];

    CGAffineTransform finalTransform;

    float shortMember = MIN(videoTrack.naturalSize.height, videoTrack.naturalSize.width);
    float longMember = MAX(videoTrack.naturalSize.height, videoTrack.naturalSize.width);
    float scaleRate = VIDEO_SIZE / shortMember;
    float surplus = (longMember*scaleRate - VIDEO_SIZE)/2;

    CGAffineTransform shrinkTransform = CGAffineTransformMakeScale(scaleRate, -scaleRate);
    CGAffineTransform translateToCenter = CGAffineTransformMakeTranslation(-surplus , VIDEO_SIZE);
    finalTransform = CGAffineTransformConcat( shrinkTransform, translateToCenter);
    [transformer setTransform:finalTransform atTime:kCMTimeZero];




    // -----------------------------------------------------------------
    //  変換を適用する
    // -----------------------------------------------------------------
    instruction.layerInstructions = [NSArray arrayWithObject:transformer];
    videoComposition.instructions = [NSArray arrayWithObject:instruction];



    // -----------------------------------------------------------------
    //  動画に書き出す
    // -----------------------------------------------------------------
    AVAssetExportSession *exportSession;
    exportSession = [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
    exportSession.videoComposition = videoComposition;

    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
    exportSession.outputURL = [NSURL fileURLWithPath:filePath];
    exportSession.outputFileType = AVFileTypeMPEG4;
    [exportSession exportAsynchronouslyWithCompletionHandler:^
    {
        switch ([exportSession status])
        {
            case AVAssetExportSessionStatusFailed:
            {
                NSString *msg = [NSString stringWithFormat:@"動画を保存するのに十分な空き容量があるか確認して下さい。 %@", [exportSession.error localizedDescription]];
                [CSCommon showAlert:@"動画の書き出しに失敗しました" withMessage:msg];
                [[NSNotificationCenter defaultCenter] postNotificationName:CSVideoFileExporterDidFailExport object:nil];
                break;
            }

            case AVAssetExportSessionStatusCancelled:
            {
                NSString *msg = [NSString stringWithFormat:@"動画を保存するのに十分な空き容量があるか確認して下さい。 %@", [exportSession.error localizedDescription]];
                [CSCommon showAlert:@"動画の書き出しがキャンセルされました" withMessage:msg];
                [[NSNotificationCenter defaultCenter] postNotificationName:CSVideoFileExporterDidFailExport object:nil];
                break;
            }

            case AVAssetExportSessionStatusCompleted :
            {
                [[NSNotificationCenter defaultCenter] postNotificationName:CSVideoFileExporterDidFinishExport object:nil];
                break;
            }
        }
    }];
}