AVFoundationを使った動画編集(再生速度を変える)

[AVFoundationを使った動画編集(簡単なフェード処理)](https://qiita.com/croquette0212/items/59b637ba0050aaa220e8)の続きで、映像の再生速度を変更して早送りにしたりスローモーションにしたりしてみます。

# 出力用のCompositionとTrackの準備
```swift
let srcAsset = AVURLAsset(url: url1)

let composition = AVMutableComposition()

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

CompositionとTrackの準備については、前回の内容と同じです。

# 再生速度を指定して映像を追加するメソッドを作る
```swift
private func copyVideoTrack(of asset: AVAsset,
                            start: TimeInterval,
                            end: TimeInterval,
                            to track: AVMutableCompositionTrack,
                            duration: TimeInterval,
                            at: TimeInterval) throws {
    let srcVideoTrack = asset.tracks(withMediaType: .video)[0]
    let timeScale = srcVideoTrack.naturalTimeScale
    
    // 元の映像の切り出す範囲
    let srcTimeRange = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: timeScale), end: CMTime(seconds: end, preferredTimescale: timeScale))
    
    // 出力先トラックでの挿入位置(再生開始時刻)
    let insertAt = CMTime(seconds: at, preferredTimescale: timeScale)
    
    // 一旦そのままの長さで挿入する
    try track.insertTimeRange(srcTimeRange, of: srcVideoTrack, at: insertAt)

    // 再生時間(再生速度)を変更する
    track.scaleTimeRange(CMTimeRange(start: insertAt, duration: srcTimeRange.duration), toDuration: CMTime(seconds: duration, preferredTimescale: timeScale))
}
```

元の映像から、出力用の映像トラックに映像を貼り付けるメソッドを用意します。

元映像の再生速度と貼り付け先の再生速度が変わる関係で若干ややこしいので細かくみていきます。

```swift 
let srcTimeRange = CMTimeRange(start: CMTime(seconds: start, preferredTimescale: timeScale), end: CMTime(seconds: end, preferredTimescale: timeScale))
```

まず、元の映像から切り出す範囲を決定します。`start`と`end`という二つの引数で開始時刻、終了時刻を秒で指定します。`timescale`は元映像の`timescale`で統一します。

```swift 
let insertAt = CMTime(seconds: at, preferredTimescale: timeScale)
```

次に、出力先トラックの挿入位置を決定します。

```swift 
try track.insertTimeRange(srcTimeRange, of: srcVideoTrack, at: insertAt)
```

切り出す範囲と挿入位置が決まったら、一度元のスケール(再生速度)のままトラックに映像を挿入します。

```swift 
track.scaleTimeRange(CMTimeRange(start: insertAt, duration: srcTimeRange.duration), toDuration: CMTime(seconds: duration, preferredTimescale: timeScale))
```

その後、[scaleTimeRange](https://developer.apple.com/documentation/avfoundation/avmutablecomposition/1390549-scaletimerange)メソッドを使って挿入した箇所のスケール(再生速度)を変更します。

最初の引数(`CMTimeRange(start: insertAt, duration: srcTimeRange.duration)`)は、出力用トラックにおける再生範囲です。元映像における再生範囲ではないので注意しましょう。ここでは映像を挿入した位置(`insertAt`)から元映像における切り出し範囲の長さ(`srcTimeRange.duration`)分を再生速度変更の対象として指定します。

次の引数(`toDuration: CMTime(seconds: duration, preferredTimescale: timeScale)`)では、対象となる範囲を何秒に圧縮/引き延ばすかを指定します。ここでは、`duration`という引数で指定した秒数になるように指定しています。もし元映像から再生速度の倍率で指定したい場合は[CMTimeMultiplyByFloat64(_:multiplier:)](https://developer.apple.com/documentation/coremedia/1400833-cmtimemultiplybyfloat64)などを使って、元の映像の長さから計算しても良いと思います。

# Trackに映像を切り貼りしていく
```swift
// それぞれのトラックから最初の5秒を取り出す
do {
    try copyVideoTrack(of: srcAsset, start: 0.0, end: 1.0, to: videoTrack, duration: 1, at: 0.0)
    try copyVideoTrack(of: srcAsset, start: 1.0, end: 14.0, to: videoTrack, duration: 1.0, at: 1.0)
    try copyVideoTrack(of: srcAsset, start: 14.0, end: 15.0, to: videoTrack, duration: 1.0, at: 2.0)
} catch let error {
    debugPrint(error)
    return
}
```

上で用意したメソッドを使って、元映像から出力用トラックに映像を切り貼りしていきます。上のコードでは、

1. 元映像の最初の1秒を等倍速で貼り付け
2. 元映像の1秒〜14秒までの13秒間を1秒間に圧縮して貼り付け
3. 元映像の14秒〜15秒までの1秒間を等倍速で貼り付け

という形で、再生速度を変えながら元映像の15秒分を3秒に圧縮して追加しています。

# Instructionの設定とプレビュー
```swift
// 出力用のビデオトラックに対するlayerInstruction(今回は何もしない)
let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)

// VideoCompositionに対するinstruction
let instruction = AVMutableVideoCompositionInstruction()
instruction.layerInstructions = [layerInstruction]

// instructionはビデオトラック全体に適用されるようにする
instruction.timeRange = videoTrack.timeRange

// VideoComposition
let videoComposition = AVMutableVideoComposition()
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.renderSize = videoTrack.naturalSize
videoComposition.instructions = [instruction]

let playerItem = AVPlayerItem(asset: composition)
playerItem.videoComposition = videoComposition

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

InstructionやAVPlayerを使ったプレビューについては、[AVFoundationを使った動画編集(簡単なフェード処理)](https://qiita.com/croquette0212/items/59b637ba0050aaa220e8)と同じなので説明を省略します。

# Trouble Shooting
## AVPlayerViewでは再生速度が変わっているが、AVAssetExportSessionで書き出すと変な再生速度になる

`AVAssetExportSession`の`videoComposition`に正しい値がセットされているか確認しましょう。