如何将(hstack)多个视频与 AVMutableVideoComposition 并排组合?
How to combine (hstack) multiple videos side by side with AVMutableVideoComposition?
我正在尝试将 3 个视频组合成单个视频,其中视频被裁剪并并排放置在 hstack 中。
这是我目前的解决方案,它只将第一个视频并排放置 3 次(重复)。我不知道我做错了什么。
import Foundation
import AVFoundation
func hstackVideos() {
let videoPaths: [String] = [
"path/to/video1.mp4",
"path/to/video2.mp4",
"path/to/video3.mp4",
]
let composition = AVMutableComposition()
let assetInfos: [(AVURLAsset, AVAssetTrack, AVMutableCompositionTrack)] = videoPaths.map {
let asset = AVURLAsset(url: URL(fileURLWithPath: [=10=]))
let track = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)!
let videoAssetTrack = asset.tracks(withMediaType: .video)[0]
try! track.insertTimeRange(CMTimeRangeMake(start: videoAssetTrack.timeRange.start, duration: videoAssetTrack.timeRange.duration), of: videoAssetTrack, at: videoAssetTrack.timeRange.start)
return (asset, videoAssetTrack, track)
}
let stackComposition = AVMutableVideoComposition()
stackComposition.renderSize = CGSize(width: 512, height: 288)
stackComposition.frameDuration = CMTime(seconds: 1/30, preferredTimescale: 600)
var i = 0
let instructions: [AVMutableVideoCompositionLayerInstruction] = assetInfos.map { (asset, assetTrack, compTrack) in
let lInst = AVMutableVideoCompositionLayerInstruction(assetTrack: assetTrack)
let w: CGFloat = 512/CGFloat(assetInfos.count)
let inRatio = assetTrack.naturalSize.width / assetTrack.naturalSize.height
let cropRatio = w / 288
let scale: CGFloat
if inRatio < cropRatio {
scale = w / assetTrack.naturalSize.width
} else {
scale = 288 / assetTrack.naturalSize.height
}
lInst.setCropRectangle(CGRect(x: w/scale, y: 0, width: w/scale, height: 288/scale), at: CMTime.zero)
let transform = CGAffineTransform(scaleX: scale, y: scale)
let t2 = transform.concatenating(CGAffineTransform(translationX: -w + CGFloat(i)*w, y: 0))
lInst.setTransform(t2, at: CMTime.zero)
i += 1
return lInst
}
let inst = AVMutableVideoCompositionInstruction()
inst.timeRange = CMTimeRange(start: CMTime.zero, duration: assetInfos[0].0.duration)
inst.layerInstructions = instructions
stackComposition.instructions = [inst]
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
let outPath = "path/to/finalVideo.mp4"
let outUrl = URL(fileURLWithPath: outPath)
try? FileManager.default.removeItem(at: outUrl)
exporter.outputURL = outUrl
exporter.videoComposition = stackComposition
exporter.outputFileType = .mp4
exporter.shouldOptimizeForNetworkUse = true
let group = DispatchGroup()
group.enter()
exporter.exportAsynchronously(completionHandler: {
switch exporter.status {
case .completed:
print("SUCCESS!")
if exporter.error != nil {
print("Error: \(String(describing: exporter.error))")
print("Description: \(exporter.description)")
}
group.leave()
case .exporting:
let progress = exporter.progress
print("Progress: \(progress)")
case .failed:
print("Error: \(String(describing: exporter.error))")
print("Description: \(exporter.description)")
group.leave()
default:
break
}
})
group.wait()
}
您正在将指令应用于源资产轨道,但您需要将它们应用于输出合成轨道,如下所示:
let instructions: [AVMutableVideoCompositionLayerInstruction] = assetInfos.map { (asset, assetTrack, compTrack) in
let lInst = AVMutableVideoCompositionLayerInstruction(assetTrack: compTrack)
let w: CGFloat = 512/CGFloat(assetInfos.count)
let inRatio = compTrack.naturalSize.width / compTrack.naturalSize.height
let cropRatio = w / 288
let scale: CGFloat
if inRatio < cropRatio {
scale = w / compTrack.naturalSize.width
} else {
scale = 288 / compTrack.naturalSize.height
}
lInst.setCropRectangle(CGRect(x: w/scale, y: 0, width: w/scale, height: 288/scale), at: CMTime.zero)
let transform = CGAffineTransform(scaleX: scale, y: scale)
let t2 = transform.concatenating(CGAffineTransform(translationX: -w + CGFloat(i)*w, y: 0))
lInst.setTransform(t2, at: CMTime.zero)
i += 1
return lInst
}
我正在尝试将 3 个视频组合成单个视频,其中视频被裁剪并并排放置在 hstack 中。
这是我目前的解决方案,它只将第一个视频并排放置 3 次(重复)。我不知道我做错了什么。
import Foundation
import AVFoundation
func hstackVideos() {
let videoPaths: [String] = [
"path/to/video1.mp4",
"path/to/video2.mp4",
"path/to/video3.mp4",
]
let composition = AVMutableComposition()
let assetInfos: [(AVURLAsset, AVAssetTrack, AVMutableCompositionTrack)] = videoPaths.map {
let asset = AVURLAsset(url: URL(fileURLWithPath: [=10=]))
let track = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)!
let videoAssetTrack = asset.tracks(withMediaType: .video)[0]
try! track.insertTimeRange(CMTimeRangeMake(start: videoAssetTrack.timeRange.start, duration: videoAssetTrack.timeRange.duration), of: videoAssetTrack, at: videoAssetTrack.timeRange.start)
return (asset, videoAssetTrack, track)
}
let stackComposition = AVMutableVideoComposition()
stackComposition.renderSize = CGSize(width: 512, height: 288)
stackComposition.frameDuration = CMTime(seconds: 1/30, preferredTimescale: 600)
var i = 0
let instructions: [AVMutableVideoCompositionLayerInstruction] = assetInfos.map { (asset, assetTrack, compTrack) in
let lInst = AVMutableVideoCompositionLayerInstruction(assetTrack: assetTrack)
let w: CGFloat = 512/CGFloat(assetInfos.count)
let inRatio = assetTrack.naturalSize.width / assetTrack.naturalSize.height
let cropRatio = w / 288
let scale: CGFloat
if inRatio < cropRatio {
scale = w / assetTrack.naturalSize.width
} else {
scale = 288 / assetTrack.naturalSize.height
}
lInst.setCropRectangle(CGRect(x: w/scale, y: 0, width: w/scale, height: 288/scale), at: CMTime.zero)
let transform = CGAffineTransform(scaleX: scale, y: scale)
let t2 = transform.concatenating(CGAffineTransform(translationX: -w + CGFloat(i)*w, y: 0))
lInst.setTransform(t2, at: CMTime.zero)
i += 1
return lInst
}
let inst = AVMutableVideoCompositionInstruction()
inst.timeRange = CMTimeRange(start: CMTime.zero, duration: assetInfos[0].0.duration)
inst.layerInstructions = instructions
stackComposition.instructions = [inst]
let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality)!
let outPath = "path/to/finalVideo.mp4"
let outUrl = URL(fileURLWithPath: outPath)
try? FileManager.default.removeItem(at: outUrl)
exporter.outputURL = outUrl
exporter.videoComposition = stackComposition
exporter.outputFileType = .mp4
exporter.shouldOptimizeForNetworkUse = true
let group = DispatchGroup()
group.enter()
exporter.exportAsynchronously(completionHandler: {
switch exporter.status {
case .completed:
print("SUCCESS!")
if exporter.error != nil {
print("Error: \(String(describing: exporter.error))")
print("Description: \(exporter.description)")
}
group.leave()
case .exporting:
let progress = exporter.progress
print("Progress: \(progress)")
case .failed:
print("Error: \(String(describing: exporter.error))")
print("Description: \(exporter.description)")
group.leave()
default:
break
}
})
group.wait()
}
您正在将指令应用于源资产轨道,但您需要将它们应用于输出合成轨道,如下所示:
let instructions: [AVMutableVideoCompositionLayerInstruction] = assetInfos.map { (asset, assetTrack, compTrack) in
let lInst = AVMutableVideoCompositionLayerInstruction(assetTrack: compTrack)
let w: CGFloat = 512/CGFloat(assetInfos.count)
let inRatio = compTrack.naturalSize.width / compTrack.naturalSize.height
let cropRatio = w / 288
let scale: CGFloat
if inRatio < cropRatio {
scale = w / compTrack.naturalSize.width
} else {
scale = 288 / compTrack.naturalSize.height
}
lInst.setCropRectangle(CGRect(x: w/scale, y: 0, width: w/scale, height: 288/scale), at: CMTime.zero)
let transform = CGAffineTransform(scaleX: scale, y: scale)
let t2 = transform.concatenating(CGAffineTransform(translationX: -w + CGFloat(i)*w, y: 0))
lInst.setTransform(t2, at: CMTime.zero)
i += 1
return lInst
}