我想以类似TikTok的方式合并两个视频。我在Stackoverflow上看到了很多解决方案,但没有一个对我有效。视频应该放在并排和顶部底部的方式。请看附件中的图片。在左侧,一个视频在顶部,第二个在底部,但填充了全屏的宽度和高度。同样,在图像的右侧,两个视频并排,相等地填充所需的屏幕宽度和高度。
我使用两个不同的功能合并视频。
func mergeVideosTopBottom(videoURL1: URL, videoURL2: URL, outputURL: URL, completion: @escaping (Bool, Error?) -> Void) {
let asset1 = AVAsset(url: videoURL1)
let asset2 = AVAsset(url: videoURL2)
guard let videoTrack1 = asset1.tracks(withMediaType: .video).first,
let videoTrack2 = asset2.tracks(withMediaType: .video).first else {
completion(false, nil)
return
}
let composition = AVMutableComposition()
guard let compositionTrack1 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
let compositionTrack2 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
completion(false, nil)
return
}
try? compositionTrack1.insertTimeRange(CMTimeRange(start: .zero, duration: asset1.duration), of: videoTrack1, at: .zero)
try? compositionTrack2.insertTimeRange(CMTimeRange(start: .zero, duration: asset2.duration), of: videoTrack2, at: .zero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: .zero, duration: CMTimeMaximum(asset1.duration, asset2.duration))
let transformer1 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack1)
let transformer2 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack2)
let transform1 = videoTrack1.preferredTransform
let transform2 = videoTrack2.preferredTransform
transformer1.setTransform(transform1, at: .zero)
transformer2.setTransform(transform2, at: .zero)
let videoComposition = AVMutableVideoComposition()
videoComposition.instructions = [instruction]
videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // Set desired frame rate
// Calculate the output video size
let videoSize = CGSize(width: max(videoTrack1.naturalSize.width, videoTrack2.naturalSize.width),
height: videoTrack1.naturalSize.height + videoTrack2.naturalSize.height)
videoComposition.renderSize = videoSize
instruction.layerInstructions = [transformer1, transformer2]
guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
completion(false, nil)
return
}
exporter.outputURL = outputURL
exporter.outputFileType = .mp4
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = videoComposition
exporter.exportAsynchronously {
switch exporter.status {
case .completed:
print("Completed......")
DispatchQueue.main.async {
guard let url = exporter.outputURL else {return}
let objAVPlayerVC = AVPlayerViewController()
objAVPlayerVC.player = AVPlayer(url: url)
self.present(objAVPlayerVC, animated: true, completion: {() -> Void in
objAVPlayerVC.player?.play()
})
}
case .failed:
print("Failed......")
case .cancelled:
print("Cancelled......")
default:
break
}
}
}
第二个功能是
func mergeVideosSideBySide(videoURL1: URL, videoURL2: URL, outputURL: URL, completion: @escaping (Bool, Error?) -> Void) {
let asset1 = AVAsset(url: videoURL1)
let asset2 = AVAsset(url: videoURL2)
guard let videoTrack1 = asset1.tracks(withMediaType: .video).first,
let videoTrack2 = asset2.tracks(withMediaType: .video).first else {
completion(false, nil)
return
}
let composition = AVMutableComposition()
guard let compositionTrack1 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
let compositionTrack2 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
completion(false, nil)
return
}
try? compositionTrack1.insertTimeRange(CMTimeRange(start: .zero, duration: asset1.duration), of: videoTrack1, at: .zero)
try? compositionTrack2.insertTimeRange(CMTimeRange(start: .zero, duration: asset2.duration), of: videoTrack2, at: .zero)
let instruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRange(start: .zero, duration: CMTimeMaximum(asset1.duration, asset2.duration))
let transformer1 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack1)
let transformer2 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack2)
let transform1 = videoTrack1.preferredTransform
let transform2 = videoTrack2.preferredTransform
transformer1.setTransform(transform1, at: .zero)
transformer2.setTransform(transform2, at: .zero)
let videoComposition = AVMutableVideoComposition()
videoComposition.instructions = [instruction]
videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // Set desired frame rate
// Calculate the output video size
let videoSize = CGSize(width: videoTrack1.naturalSize.width + videoTrack2.naturalSize.width,
height: max(videoTrack1.naturalSize.height, videoTrack2.naturalSize.height))
videoComposition.renderSize = videoSize
instruction.layerInstructions = [transformer1, transformer2]
guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
completion(false, nil)
return
}
exporter.outputURL = outputURL
exporter.outputFileType = .mp4
exporter.shouldOptimizeForNetworkUse = true
exporter.videoComposition = videoComposition
exporter.exportAsynchronously {
switch exporter.status {
case .completed:
print("Completed......")
DispatchQueue.main.async {
guard let url = exporter.outputURL else {return}
let objAVPlayerVC = AVPlayerViewController()
objAVPlayerVC.player = AVPlayer(url: url)
self.present(objAVPlayerVC, animated: true, completion: {() -> Void in
objAVPlayerVC.player?.play()
})
}
case .failed:
print("Failed......")
case .cancelled:
print("Cancelled......")
default:
break
}
}
}
但在输出视频中,只有一个视频可用,它不会填满屏幕。
我也试过这个库DPVideoMerger但是这个库合并了不同的视频
任何帮助将不胜感激。
1条答案
按热度按时间5rgfhyps1#
你快到了!您的代码只是重叠两个视频轨道,所以最后添加的轨道显示时导出。我们需要在它上面添加一些变换来分离它。
您所需要做的就是在Transformer.setTransform中添加额外的transform,将第一个视频轨道移动到屏幕的左/上半部分,第二个视频轨道移动到屏幕的右/下半部分。
让我们来谈谈左和右的情况。
注意:这可能不是你想要的最终结果,但我们走在正确的道路上。