iOSにおいて、カメラで撮影した写真を加工・編集することはそれほど難しくはない。
しかし、動画であるならどうだろう。
AVFoundationについて詳しく調べなければならず、また必要となるコードもそれなりに長くなる。

とはいえ、抑えるべきところを抑えてしまえば、後はなんとかなるものだ。
今回の記事では動画の指定した時間の範囲を切る出すコードを解説する。

処理のイメージはこのようになる。
movie_cut_ss1
元の動画の長さが10秒だとして、その動画の2~5秒の部分を切り出すものとする。 
 
最初に全てのコードをお見せしよう。
const int kVideoFPS = 30;

- (void)cutMovie
{
    // 1
    NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    NSString *inputPath = [[path stringByAppendingPathComponent:@"portrait1"] stringByAppendingPathExtension:@"mov"];
    NSString *outputPath = [[path stringByAppendingPathComponent:@"result"] stringByAppendingPathExtension:@"mov"];
    Float64 startTime = 2;
    Float64 duration = 3;
    
    // 2
    AVMutableComposition *composition = [AVMutableComposition composition];
    AVMutableCompositionTrack *compositionVideoTrack = [composition addMutableTrackWithMediaType:AVMediaTypeVideo preferredTrackID:kCMPersistentTrackID_Invalid];
    AVMutableCompositionTrack *compositionAudioTrack = [composition addMutableTrackWithMediaType:AVMediaTypeAudio preferredTrackID:kCMPersistentTrackID_Invalid];
    NSError *error;
    
    // 3
    NSURL *inputURL = [NSURL fileURLWithPath:inputPath];
    AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:inputURL options:nil];
    
    // 4
    if (startTime >= CMTimeGetSeconds(asset.duration) || startTime < 0 || duration <= 0)
        return;
    CMTime rangeStart = CMTimeMakeWithSeconds(startTime, kVideoFPS);
    CMTime rangeDuration = CMTimeMakeWithSeconds(duration, kVideoFPS);
    CMTimeRange inputRange = CMTimeRangeMake(kCMTimeZero, asset.duration);
    CMTimeRange outputRange = CMTimeRangeMake(rangeStart, rangeDuration);

    // 5
    AVAssetTrack *videoTrack = [asset tracksWithMediaType:AVMediaTypeVideo][0];
    AVAssetTrack *audioTrack = [asset tracksWithMediaType:AVMediaTypeAudio][0];

    // 6
    [compositionVideoTrack insertTimeRange:outputRange ofTrack:videoTrack atTime:kCMTimeZero error:&error];
    [compositionAudioTrack insertTimeRange:outputRange ofTrack:audioTrack atTime:kCMTimeZero error:&error];

    // 7
    AVMutableVideoCompositionInstruction *instruction = [AVMutableVideoCompositionInstruction videoCompositionInstruction];
    instruction.timeRange = inputRange;

    // 8
    AVMutableVideoCompositionLayerInstruction *layerInstruction = [AVMutableVideoCompositionLayerInstruction videoCompositionLayerInstructionWithAssetTrack:compositionVideoTrack];

    // 9
    CGSize videoSize = videoTrack.naturalSize;
    CGAffineTransform transform = videoTrack.preferredTransform;;
    if (transform.a == 0 && transform.d == 0 && (transform.b == 1.0 || transform.b == -1.0) && (transform.c == 1.0 || transform.c == -1.0))
    {
        videoSize = CGSizeMake(videoSize.height, videoSize.width);
    }

    // 10
    [layerInstruction setTransform:transform atTime:kCMTimeZero];
    instruction.layerInstructions = @[layerInstruction];

    // 11
    AVMutableVideoComposition *videoComposition = [AVMutableVideoComposition videoComposition];
    videoComposition.renderSize = videoSize;
    videoComposition.instructions = @[instruction];
    videoComposition.frameDuration = CMTimeMake(1, kVideoFPS);

    // 12
    NSFileManager *fm = [NSFileManager defaultManager];
    if ([fm fileExistsAtPath:outputPath])
    {
        [fm removeItemAtPath:outputPath error:&error];
    }

    // 13
    AVAssetExportSession *session = [[AVAssetExportSession alloc] initWithAsset:composition presetName:AVAssetExportPresetHighestQuality];
    session.outputURL = [NSURL fileURLWithPath:outputPath];
    session.outputFileType = AVFileTypeQuickTimeMovie;
    session.videoComposition = videoComposition;
    
    // 14
    [session exportAsynchronouslyWithCompletionHandler:^{
        if (session.status == AVAssetExportSessionStatusCompleted)
        {
            NSLog(@"output complete!");
        }
        else
        {
            NSLog(@"output error! : %@", session.error);
        }
    }];
}
100行近いというだけでうんざりしてしまうかもしれない。
しかし、今回の処理を行うためには最低限必要なコードばかりだ。
順を追って説明していこう。

1.ここは特に説明はいらないだろう。
元の動画と出力する動画のパスを設定し、切り出す範囲の値をわかりやすい形で設定する。
今回は2~5秒の部分を切り出すのでstartTimeは2、durationは3としておく。

2.動画処理の基礎中の基礎となる変数だ。
AVMutableCompositionというのは動画処理の枠組みとも言えるもので、この中に色々な設定を組み込むことで様々な動画処理が可能となる。
AVMutableCompositionTrackは出力する動画のトラックに設定する枠組みのようなものだ。
今回はビデオトラック用とオーディオトラック用の二つを用意した。

トラックのイメージが湧きにくい人はAdobe Premiereのような動画編集ソフトを想像してもらうとわかりやすいかもしれない。
movie_cut_ss2
このタイムライン上のビデオ1や、オーディオ1のようなものだ。

3.元動画のパスを元にアセットを作成する。

4.切り出す範囲を設定する。
開始時間やデュレーションはfloatのままでは使えないので、まずCMTimeのインスタンスを作成する。
kVideoFPSはコードの頭に設定した定数だ。
今回は一般的な動画のフレームレートである30にしてある。

CMTimeRangeは時間の範囲を示す構造体だ。
ここで元動画と出力する動画の範囲を正しく指定しておかなければならない。
元動画の長さは先ほど作成したassetインスタンスから参照できる。

5.assetからビデオトラックとオーディオトラックを抜き出す。
配列の0番目の値を参照しているが、これはおまじないのようなものだと考えておこう。

6.出力するビデオとオーディオのトラックを設定する。
ここで行っていることを簡単に言うと
「元動画のビデオ(オーディオ)トラックのoutputRangeの範囲を、出力する動画の頭(0秒)に差し込む」
ということだ。
わかりやすくするためにイメージ図を用意した。
movie_cut_ss3

7.AVMutableVideoCompositionInstructionインスタンスを作成する。
これは元動画に対する様々な処理を設定する枠組みのようなものだ。

8.AVMutableVideoCompositionLayerInstructionインスタンスを作成する。
これはビデオトラックをどう処理するかを決定するためのものだ。

9.出力する動画の縦横サイズを決定する。
videoSizeは元動画の縦横サイズを使うわけだが、ここではちょっと不思議な処理をしている。
これを説明するにはiOSにインストールされているカメラアプリの仕様に言及しないといけない。

今回元となっている動画はカメラアプリで縦画面で撮影したものだ。
しかし、実際に保存された動画は「横向きの動画を回転して縦向きにみせる動画」として保存されているようだ。
回転の情報は二次元の行列で保持されているので、それを判別しているのがこのif文だ。

10.変換行列をlayerInstructionに設定する。
今回は元の動画をそのまま出力するだけなので、特に行列には手を加えずに設定している。
もし、移動や回転等の処理を加えたかったら、事前にtransformに手を加えてやればいい。

11.AVMutableVideoCompositionインスタンスを作成する。
これは最終的に出力するビデオの設定を行う。
frameDurationは文字通り、1フレームあたりの長さだ。

12.出力するファイルと同名のファイルがある場合に削除する。
これをしないと出力に失敗するので忘れずに行うこと。

13.出力するためのセッションであるAVAssetExportSessionインスタンスを作成する。
presetNameはAVAssetExportPresetHighestQuality以外にも色々あるので、状況に応じて使い分けたい。
また、videoCompositionをしっかり代入しておくことも忘れないこと。
これを忘れると向きのおかしな動画が出力されてしまう。

14.いよいよ最終出力だ。
この処理は非同期で行われ、ある程度の時間がかかるので、実際のアプリではインジケータを表示する等して、ユーザにフリーズしているのではないことを知らせるのが良いだろう。

以上で、処理は完了だ。
実際に動かして試してみて欲しい。

参考文献
AV Foundation Programming Guide: Editing

関連記事
[iOS] 動画を加工・編集する(2) エラーコード-11841に気をつける
[iOS] 動画を加工・編集する(3) 動画に音を合成する
[iOS] 動画を加工・編集する(4) 動画をクリッピングする
[iOS] 動画を加工・編集する(5) 動画を拡大縮小する
[iOS] 動画を加工・編集する(6) 複数の動画を連結する