AVFoundationを使った動画編集(簡単なフェード処理)の応用で、二つの動画を重ねてみようと思います。
素材を二つ読み込む
let srcAsset1 = AVURLAsset(url: url1)
let srcAsset2 = AVURLAsset(url: url2)
まずは素材となる映像を二つ読み込みます。
今回は各素材の冒頭5秒を切り出して合成していくので、5秒以上の長さの動画ファイルを二つ指定してください。
出力用のCompositionと出力用ビデオトラックを2つ作る
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秒を切り出してコピーしていきます。
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秒を切り出して出力用のビデオトラックにコピーする処理をメソッドにしておきます。
// それぞれのトラックから最初の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を用意する
// 出力用の各ビデオトラックに対する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を用意する
// 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を用意する
// VideoComposition
let videoComposition = AVMutableVideoComposition()
videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
videoComposition.renderSize = videoTrack1.naturalSize
videoComposition.instructions = [instruction]
最後にCompositionを用意します。fps
は固定で1/30
を指定しています。解像度はvideoTrack1
のものを指定しています。元素材の解像度が異なる場合、出力映像の解像度をどちらに合わせるか、あるいは全く別の値にするか、は目的次第となるので、ここでは省略します。
プレビューする
let playerItem = AVPlayerItem(asset: composition)
playerItem.videoComposition = videoComposition
let player = AVPlayer(playerItem: playerItem)
playerView.player = player
作成したCompositionの内容は、前回のAVFoundationを使った動画編集(簡単なフェード処理)同様、AVPlayerViewを使ってプレビューしたり、AVAssetExportSessionでファイルに書き出したりすることができます。