酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

SwiftでAVFoundationを使って元ある動画から異なる動画を生成する (動画編集の基本編)

SwiftでAVFoundationを使って動画を編集をできるようになりたいと考えている。まずは基本を抑えるために、元ある動画の情報をそのまま使って異なる動画を生成する処理を本記事では紹介したい。

本記事は「AVFoundationを使ったiOSの動画編集 - Qiita」を参考に書いており、説明に関しては元ネタの方を読んでいただきたい。

PHAssetオブジェクトからAVAssetオブジェクトを取得する

UIImagePickerControllerで選択したコンテンツはPhotos.frameworkの PHAssestオブジェクトとして取得することができる。

UIImagePickerControllerを使って、動画コンテンツを取得する方法に関しては「UIImagePickerControllerを使って取得したPHAssetビデオのサムネイルを取得する - 酢ろぐ!」にて軽く触れたので、そちらの方をご覧ください。

AVFoundationを使って動画編集をしたいが PHAssetオブジェクトのままでは扱えないため、AVAssetオブジェクトに変換する必要がある。PHImageManager#requestAVAsset(forVideo:options:resultHandler:)を使って、非同期でAVAssetオブジェクトを取得する。

let phAsset = getAsset() // UIImagePickerとかで PHAssetオブジェクトを取得する

let option = PHVideoRequestOptions()
option.deliveryMode = .mediumQualityFormat

let manager = PHImageManager.default()
manager.requestAVAsset(forVideo: asset, options: option) { (avAsset, audioMix, info) in
    let avAsset = avAsset as! AVURLAsset

    // ビデオを読み込んで処理する
    VideoUtil.loadVideo(asset: avAsset) {  (exportUrl) in
        
        // ビデオの処理完了通知はメインスレッドではないみたいので注意する
        DispatchQueue.main.async { [weak self] in
            // 完了処理
        }
    }
}

元ある動画から異なる動画を生成する

AVFoundationを使ったiOSの動画編集 - Qiita」がとても参考になった。この記事ではアプリに組み込まれている動画データを読み出して、別の異なる動画を生成している。AVFoundationを使って動画を生成する方法を学ぶ上でとても参考になった。

元ネタのtastasさんのサンプルコードは3年前のもののようで、APIが古くサンプルコードがコピペして使えなかったため、Xcode 11.4.1 でビルドが通るように書き換えた。

UIImagePickerControllerから選択した動画を編集することを前提にしているので一部コードは元ネタと違う点に注意していただきたい。

class VideoUtil {

    /// 画像を読み出してそのまま保存する(サンプルコード)
    static func loadVideo(asset: AVURLAsset, completeHandler: ((URL?) -> Void)?) {
        
        //アセットの作成
        //動画のアセットとトラックを作成
        var videoTrack: AVAssetTrack
        var audioTrack: AVAssetTrack
        
        let videoTracks = asset.tracks(withMediaType: AVMediaType.video)
        videoTrack = videoTracks[0]   //トラックの取得
        let audioTracks = asset.tracks(withMediaType: AVMediaType.audio)
        audioTrack = audioTracks[0]   //トラックの取得

        //コンポジション作成
        let mixComposition = AVMutableComposition()
        // ベースとなる動画のコンポジション作成
        let compositionVideoTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        // ベースとなる音声のコンポジション作成
        let compositionAudioTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)

        // コンポジションの設定
        // 動画の長さ設定
        try! compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: videoTrack, at: .zero)
        // 音声の長さ設定
        try! compositionAudioTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: asset.duration), of: audioTrack, at: .zero)
        // 回転方向の設定
        compositionVideoTrack.preferredTransform = asset.tracks(withMediaType: .video)[0].preferredTransform

        // 動画のサイズを取得
        let videoSize: CGSize = videoTrack.naturalSize
        
        // 合成用コンポジション作成
        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = videoSize
        videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)

        // インストラクションを合成用コンポジションに設定
        let instruction = AVMutableVideoCompositionInstruction()
        instruction.timeRange = CMTimeRangeMake(start: .zero, duration: asset.duration)
        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack)
        instruction.layerInstructions = [ layerInstruction ]
        videoComposition.instructions = [ instruction ]

        // 動画のコンポジションをベースにAVAssetExportを生成
        guard let assetExport = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else {
            return
        }
        
        // 合成用コンポジションを設定
        assetExport.videoComposition = videoComposition

        // エクスポートファイルの設定
        let exportPath: String = NSHomeDirectory() + "/tmp/createdMovie.mp4"
        let exportUrl: URL = URL(fileURLWithPath: exportPath)
        assetExport.outputFileType = AVFileType.mp4
        assetExport.outputURL = exportUrl
        assetExport.shouldOptimizeForNetworkUse = true

        // ファイルが存在している場合は削除
        if FileManager.default.fileExists(atPath: exportPath) {
            try! FileManager.default.removeItem(atPath: exportPath)
        }

        // エクスポート実行
        assetExport.exportAsynchronously {
            switch assetExport.status {
            case .completed:
                print("completed")
                completeHandler?(exportUrl)
                
            case .failed:
                print("failed")
                completeHandler?(nil)
                
            default:
                print("status: \(assetExport.status.rawValue)")
            }
        }
    }

}

動作確認環境

  • iOS 13.4
  • Xcode 11.4.1

関連記事

iOSアプリ開発Tips/動画編集にて関連した記事をまとめています。

それ以外にも iOSアプリ開発で役立つ情報をまとめています。