Help us understand the problem. What is going on with this article?

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

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つ目の素材/ビデオトラック用)の順で指定します。この順番は重要です。layerInstructionstop-to-bottomの順で指定し、先に指定されたlayerInstructionの内容が上に描画されるようになります。今回は2つ目のビデオトラックの上に1つ目のビデオトラックを描画したいので、この順番で指定します。

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

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でファイルに書き出したりすることができます。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away