AVFoundationを使った動画編集(2つの動画をフェードで重ねる)

[AVFoundationを使った動画編集(簡単なフェード処理)](https://qiita.com/croquette0212/items/59b637ba0050aaa220e8)の応用で、二つの動画を重ねてみようと思います。

# 素材を二つ読み込む
```swift 
let srcAsset1 = AVURLAsset(url: url1)
let srcAsset2 = AVURLAsset(url: url2)
```

まずは素材となる映像を二つ読み込みます。

今回は各素材の冒頭5秒を切り出して合成していくので、5秒以上の長さの動画ファイルを二つ指定してください。

# 出力用のCompositionと出力用ビデオトラックを2つ作る
```swift 
let composition = AVMutableComposition()

// 出力用VideoTrackを二つ作る
guard
    let videoTrack1 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
    let videoTrack2 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
    debugPrint("Failed to add video tracks")
    return
}
```

出力用のCompositionを用意します。映像素材を重ねて使う場合、出力用のビデオトラックは素材毎に必要になるので、今回はビデオトラックを二つ用意します。

ここでは、

* videoTrack1: asset1の映像用のトラック
* videoTrack2: asset2の映像用のトラック

として使います。

# それぞれの元動画から、冒頭の5秒を切り出して出力用のビデオトラックにコピーする

出力用の各トラックに、各元素材の冒頭5秒を切り出してコピーしていきます。

```swift 
private func copyVideoTrack(from asset: AVAsset, to track: AVMutableCompositionTrack, seconds: TimeInterval) throws {
    let srcVideoTrack = asset.tracks(withMediaType: .video)[0]
    let range = CMTimeRange(start: .zero, end: CMTime(seconds: seconds, preferredTimescale: srcVideoTrack.naturalTimeScale))
    try track.insertTimeRange(range, of: srcVideoTrack, at: .zero)
}
```

読みやすさのために、Assetから冒頭のn秒を切り出して出力用のビデオトラックにコピーする処理をメソッドにしておきます。

```swift
// それぞれのトラックから最初の5秒を取り出す
do {
    try copyVideoTrack(from: srcAsset1, to: videoTrack1, seconds: 5.0)
    try copyVideoTrack(from: srcAsset2, to: videoTrack2, seconds: 5.0)
} catch let error {
    debugPrint(error)
    return
}
```

作成したメソッドを利用して、srcAsset1の冒頭5秒をvideoTrack1に、srcAsset2の冒頭5秒をvideoTrack2にコピーします。

# 各出力用ビデオトラック用のLayerInstructionを用意する
```swift 
// 出力用の各ビデオトラックに対するlayerInstruction
let layerInstruction1 = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack1)
let layerInstruction2 = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack2)

// VideoTrack1の透明度を0.0秒-5.0秒の間で1.0から0.0に変化させる(フェードアウト)
layerInstruction1.setOpacityRamp(fromStartOpacity: 1.0, toEndOpacity: 0.0, timeRange: CMTimeRange(start: .zero, end: CMTime(seconds: 5.0, preferredTimescale: videoTrack1.naturalTimeScale)))
```

出力用の各ビデオトラック用のLayerInstructionを作成します。layerInstruction1がvideoTrack1用、layerInstruction2がvideoTrack2用となります。

今回は、1個目の素材(srcAsset1 / videoTrack1)のOpacityを5秒かけて1から0に下げていくように指示します。2個目の素材については特に変化はさせません。

なお、元素材の解像度が異なる場合、ここで各ビデオトラックの描画位置・拡大倍率などを調整する必要がありますが、わかりやすさのため今回は省略します。

# Instructionを用意する
```swift 
// VideoCompositionに対するinstruction
let instruction = AVMutableVideoCompositionInstruction()
instruction.layerInstructions = [layerInstruction1, layerInstruction2]

// instructionは元映像全体に適用されるようにする
instruction.timeRange = videoTrack1.timeRange
``` 

出力用のVideoCompositionに対するInstructionを作成します。

`layerInstructions`に先ほどの`layerInstruction1`(1つ目の素材/ビデオトラック用)、`layerInstruction2`(2つ目の素材/ビデオトラック用)の順で指定します。この順番は重要です。`layerInstructions`は`top-to-bottom`の順で指定し、先に指定された`layerInstruction`の内容が上に描画されるようになります。今回は2つ目のビデオトラックの上に1つ目のビデオトラックを描画したいので、この順番で指定します。

今回は`videoTrack1`も`videoTrack2`も同じ5秒分の映像が含まれているので、どちらを指定しても同じですが`timeRange`には、`videoTrack1`の`timeRange`を指定しています。ビデオトラックの長さが異なる場合は、出力したい内容に合わせて指定内容を調整する必要があります。

# VideoCompositionを用意する
```swift 
// VideoComposition
let videoComposition = AVMutableVideoComposition()
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.renderSize = videoTrack1.naturalSize
videoComposition.instructions = [instruction]
```

最後にCompositionを用意します。`fps`は固定で`1/30`を指定しています。解像度は`videoTrack1`のものを指定しています。元素材の解像度が異なる場合、出力映像の解像度をどちらに合わせるか、あるいは全く別の値にするか、は目的次第となるので、ここでは省略します。

# プレビューする
```swift 
let playerItem = AVPlayerItem(asset: composition)
playerItem.videoComposition = videoComposition

let player = AVPlayer(playerItem: playerItem)
playerView.player = player
```

作成したCompositionの内容は、前回の[AVFoundationを使った動画編集(簡単なフェード処理)](https://qiita.com/croquette0212/items/59b637ba0050aaa220e8)同様、AVPlayerViewを使ってプレビューしたり、AVAssetExportSessionでファイルに書き出したりすることができます。