AV Foundationを使った動画編集(Composition)

[前回](http://qiita.com/croquette0212/items/c92d7c0cf651f41c4b2e)に引き続き。`Asset`を理解したら、次は`Composition`の理解。

`AVAsset`のサブクラスには`AVURLAsset`と`AVComposition`の二つがある。
動画編集では必然的に`Composition`を使うことになるので、まず基礎から。

# CompositionとTrackとAsset

| 名称 | 説明 |
| --- | --- |
| Composition | 一つの映像作品 / プロジェクト |
| Track | Compositionを構成する映像/音声トラック |
| Asset | 各トラックの上にのせる素材 |

ここらへんは世の中の動画編集ソフトと基本的に同じ考え方なので、もしピンとこなかったら`Premiere`辺りを使ってみて感覚をつかんでおくとわかりやすい。なお、ややこしいが`Composition`も`Asset`として扱えるので注意。

## 読み込んだAssetのトラックを調べてみる

`AVAsset`の`tracks`プロパティを見ると、そのAssetに含まれるトラックの情報を見れる。

```swift:読み込んだファイルのトラックを見てみる
let asset = AVURLAsset(url: url)
debugPrint(asset.tracks)
```

大抵の動画ファイルは、2トラック(映像トラック1 + 音声トラック1)から構成される。

```:結果
[<AVAssetTrack: 0x600000018080, trackID = 1, mediaType = soun>, <AVAssetTrack: 0x6000000181e0, trackID = 2, mediaType = vide>]
```

`tracks`の代わりに、`tracks(withMediaType:)`を使うとタイプを限定したトラックのリストを取得できる。

```swift:videoタイプのトラックのみ取得
debugPrint(asset.tracks(withMediaType: .video))
```

# Compositionを作る
ファイルからAssetをロードする際には`AVURLAsset`を使ったが、ゼロからCompositionを作る場合は`AVComposition`のサブクラスである`AVMutableComposition`を使う。

```swift:AVMutableCompositionを作る
let composition = AVMutableComposition()
```

## 映像トラックを追加する
`AVMutableComposition`生成直後はトラックがない空のコンポジションとなっているので、映像トラックを一つ追加する。

```swift:編集可能な映像トラックを追加
guard let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
    debugPrint("Failed to add video track.")
    return
}
```

`addMutableTrack(withMediaType:, preferredTrackID:)`を呼ぶと、新しいトラックがCompositionに追加される。

* 今回は映像トラックなので`.video`を指定したが、`.audio`を指定すると音声トラックが追加される。
* `preferredTrackID`に値を指定すると、任意のトラックIDをつけることができるが、特に指定する必要がない時は`kCMPersistentTrackID_Invalid`を指定すると、自動的にユニークなIDをつけてくれる。(参考: https://developer.apple.com/documentation/avfoundation/avmutablecomposition/1387601-addmutabletrack)

## 映像トラックにAssetの映像を追加する
`AVMutableCompositionTrack`には、`insertTimeRange:ofTrack:atTime:error:`メソッドで他のAssetのトラックの内容を追加できる。

```swift:映像トラックにAssetの映像トラックの内容を追加
// 素材Assetの1個目のVideoトラックを使う
let srcVideoTrack = asset.tracks(withMediaType: .video)[0]
do {
    // 0秒のタイミングに、映像全体を追加する
    try videoTrack.insertTimeRange(srcVideoTrack.timeRange, of: srcVideoTrack, at: .zero)
} catch let error {
    debugPrint(error)
}
```

大体メソッド名通りなので説明不要かもしれないが、

* TimeRange - 元素材トラックの方のどの範囲(0:30.000〜0:35.000 とか)を使うかを[CMTimeRange](https://developer.apple.com/documentation/coremedia/cmtimerange)で指定する。今回は元素材トラック全体を指定した。
* ofTrack - 元素材トラックを指定
* atTime - 貼付ける先のトラックのどの位置に貼付けるか

例えば、元素材から最初の5秒間の映像(videoトラックのみ)をコピーしたい場合はこのように書ける。

```swift:最初の5秒間だけを元素材からとってくる
let first5Seconds = CMTimeRange(start: .zero, end: CMTime(seconds: 5.0, preferredTimescale: srcVideoTrack.naturalTimeScale))
try videoTrack.insertTimeRange(first5Seconds, of: srcVideoTrack, at: .zero)
```

`timescale`は特別な理由がなければ元素材に合わせておきたいので、元素材の[naturalTimeScale](https://developer.apple.com/documentation/avfoundation/avmutablecompositiontrack/1389030-naturaltimescale)を使用している。

# CompositionをAVPlayerViewで再生する
作成した`Composition(Asset)`は、`AVPlayer`および`AVPlayerView`を使って画面で再生させることができる。

```swift 
let player = AVPlayer(playerItem: AVPlayerItem(asset: composition))
playerView.player = player  // playerViewはStoryboardに設置したAVPlayerView
```

ファイルに書き出してから内容を確認するのが面倒な場合や、プレビュー機能を作るときはこの仕組みも活用したい。

# Compositionの書き出し
`AVMutableComposition`は`AVAsset`のサブクラスなので、[AVAssetExportSession](https://developer.apple.com/documentation/avfoundation/avassetexportsession)を使ったファイルへの書き出しができる。

```swift:ファイルに書き出す
guard let session = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetPassthrough) else {
    debugPrint("Failed to prepare session")
    return
}
session.outputURL = url
session.outputFileType = .mp4
session.exportAsynchronously {
    switch session.status {
    case .completed:
        debugPrint("completed")
    case .failed:
        debugPrint("error: \(session.error!.localizedDescription)")
    default:
        break
    }
}
```

* 利用可能な`PresetName`は`AVAssetExportSession.allExportPresets()`で確認することができる。