iOSで動画編集中にプログレスバーを表示する

#AVFoundationで動画作成中にプログレスバーを表示する方法
[動画編集について記事](https://qiita.com/drafts/225eb3c6aa864d950f65/edit)を記載していますが、作成中のプログレスバーを表示する方法について説明します。

AVAssetExportSession.progressで簡単に取得できます。例としてはこんな感じです。

```Swift
class SampleController: UIViewController {    
    var assetExport: AVAssetExportSession!
    let timeInterval = 0.1          // プログレスバーの表示間隔 (0.1秒)
        
    @IBOutlet weak var progressBar: UIProgressView! // storyboardで作成

    override func viewDidLoad() {
        super.viewDidLoad()

        // タイマーをセット
        Timer.scheduledTimer(
            timeInterval: timeInterval,
            target: self,
            selector: #selector(self.step),
            userInfo: nil,
            repeats: true
        )

        // 動画作成のメイン関数
        self.makeMovie()
    }

    //タイマー関数
    @objc func step() {        
        //プログレス表示
        if assetExport != nil {    // タイミングによってはnilの場合もあり
            //動画作成処理中 
            progressBar.setProgress(assetExport.progress, animated: true)
        }
        else {
            // 動画作成前
        }
    }

    // 動画作成の関数
    func makeMovie() {
        //
        // 色々省略
        //

        // 動画のコンポジションをベースにAVAssetExportを生成
        assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
        // 合成用コンポジションを設定
        assetExport?.videoComposition = videoComp
        
        // エクスポートファイルの設定
        assetExport?.outputFileType = AVFileType.mov
        assetExport?.outputURL = exportUrl
        assetExport?.shouldOptimizeForNetworkUse = true
        
        // エクスポート実行
        assetExport?.exportAsynchronously(completionHandler: {() -> Void in
            //処理省略
            //このあとプログレス情報が取得可能
        })
    }
}
```

##解説
AVAssetExportSession.exportAsynchronouslyで動画の作成を開始した後にAVAssetExportSession.progressで取得できるようになります。
progressは0.0から1.0の値で取得できるので、そのままUIProgressViewに突っ込めます。

取得できる値はそこそこ正確なので、特に細工をせずそのまま使って大丈夫と思います。
動画作成の処理時間は、作成する動画の長さと画質に比例する様子で、それ以外のアニメーション処理などによる処理時間の変動はほとんどない様子。(全てのパターンを試した訳ではないですが、今のところそのような感覚です)

動画の前処理が長い場合は少し工夫が必要です。

#動画の前処理が長い場合
ちょっと工夫してこんな感じです。

```Swift
class SampleController: UIViewController {    
    var assetExport: AVAssetExportSession!
    let timeInterval = 0.1          // プログレスバーの表示間隔 (0.1秒)
    let processTime : Float = 0.10    // 前処理にかかる割合(10%の場合)   
    var progressTimer: Float = 0.0    // 前処理中のプログレスバーの数値

    @IBOutlet weak var progressBar: UIProgressView! // storyboardで作成

    override func viewDidLoad() {
        super.viewDidLoad()

        // タイマーをセット
        Timer.scheduledTimer(
            timeInterval: timeInterval,
            target: self,
            selector: #selector(self.step),
            userInfo: nil,
            repeats: true
        )

        // 動画作成のメイン関数
        // 前処理をプログレスバーに反映させるために非同期処理にする
        DispatchQueue.global(qos: .default).async {
            self.makeMovie()
        }
    }

    //タイマー関数
    @objc func step() {        
        //プログレス表示
        if assetExport != nil {    // タイミングによってはnilの場合もあり
            //動画作成処理中 
            progressBar.setProgress((assetExport.progress * (1.0 - processTime)) + processTime, animated: true)
        }
        else {
            // 動画作成前
            if self.progressTimer < processTime {
                self.progressTimer = self.progressTimer + 0.01 // (数値はちょうどよくなるように調整)
                progressBar.setProgress(self.progressTimer, animated: true)
            }
        }
    }

    // 動画作成の関数
    func makeMovie() {
        // ここは変更なし
    }
}
```

##解説
まずは`DispatchQueue.global(qos: .default).async`で時間がかかる部分を非同期処理に。
assetExportがnilかどうかで前処理中かどうかが分かるので、nilの場合のプログレスバーの計算をしています。
`let processTime : Float = 0.10` は動画編集にかかる前処理の割合です。何度か実機で試してみて、この数値は出せば大丈夫。ただし、動画作成の長さや画質が動的に変えられるアプリの場合は、この比率が動的に変わるので、その計算もしないといけなくなるはずです。
いくつかの機種で試したところ、処理時間はCPU性能にのみ比例するようなので、どれか一つの機種で試せばOKと思われます。

`self.progressTimer = self.progressTimer + 0.01` でインクリメントしているのですが、この数値はタイマーの更新間隔や実際に処理が終了する時間によって調整が必要です。一番処理がかかる機種をベースにして計算しておけば安心です。