ios 合并两个视频并排和顶部底部像TikTok Duet一样

dauxcl2d  于 2023-10-21  发布在  iOS
关注(0)|答案(1)|浏览(172)

我想以类似TikTok的方式合并两个视频。我在Stackoverflow上看到了很多解决方案,但没有一个对我有效。视频应该放在并排和顶部底部的方式。请看附件中的图片。在左侧,一个视频在顶部,第二个在底部,但填充了全屏的宽度和高度。同样,在图像的右侧,两个视频并排,相等地填充所需的屏幕宽度和高度。

我使用两个不同的功能合并视频。

  1. func mergeVideosTopBottom(videoURL1: URL, videoURL2: URL, outputURL: URL, completion: @escaping (Bool, Error?) -> Void) {
  2. let asset1 = AVAsset(url: videoURL1)
  3. let asset2 = AVAsset(url: videoURL2)
  4. guard let videoTrack1 = asset1.tracks(withMediaType: .video).first,
  5. let videoTrack2 = asset2.tracks(withMediaType: .video).first else {
  6. completion(false, nil)
  7. return
  8. }
  9. let composition = AVMutableComposition()
  10. guard let compositionTrack1 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
  11. let compositionTrack2 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
  12. completion(false, nil)
  13. return
  14. }
  15. try? compositionTrack1.insertTimeRange(CMTimeRange(start: .zero, duration: asset1.duration), of: videoTrack1, at: .zero)
  16. try? compositionTrack2.insertTimeRange(CMTimeRange(start: .zero, duration: asset2.duration), of: videoTrack2, at: .zero)
  17. let instruction = AVMutableVideoCompositionInstruction()
  18. instruction.timeRange = CMTimeRange(start: .zero, duration: CMTimeMaximum(asset1.duration, asset2.duration))
  19. let transformer1 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack1)
  20. let transformer2 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack2)
  21. let transform1 = videoTrack1.preferredTransform
  22. let transform2 = videoTrack2.preferredTransform
  23. transformer1.setTransform(transform1, at: .zero)
  24. transformer2.setTransform(transform2, at: .zero)
  25. let videoComposition = AVMutableVideoComposition()
  26. videoComposition.instructions = [instruction]
  27. videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // Set desired frame rate
  28. // Calculate the output video size
  29. let videoSize = CGSize(width: max(videoTrack1.naturalSize.width, videoTrack2.naturalSize.width),
  30. height: videoTrack1.naturalSize.height + videoTrack2.naturalSize.height)
  31. videoComposition.renderSize = videoSize
  32. instruction.layerInstructions = [transformer1, transformer2]
  33. guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
  34. completion(false, nil)
  35. return
  36. }
  37. exporter.outputURL = outputURL
  38. exporter.outputFileType = .mp4
  39. exporter.shouldOptimizeForNetworkUse = true
  40. exporter.videoComposition = videoComposition
  41. exporter.exportAsynchronously {
  42. switch exporter.status {
  43. case .completed:
  44. print("Completed......")
  45. DispatchQueue.main.async {
  46. guard let url = exporter.outputURL else {return}
  47. let objAVPlayerVC = AVPlayerViewController()
  48. objAVPlayerVC.player = AVPlayer(url: url)
  49. self.present(objAVPlayerVC, animated: true, completion: {() -> Void in
  50. objAVPlayerVC.player?.play()
  51. })
  52. }
  53. case .failed:
  54. print("Failed......")
  55. case .cancelled:
  56. print("Cancelled......")
  57. default:
  58. break
  59. }
  60. }
  61. }

第二个功能是

  1. func mergeVideosSideBySide(videoURL1: URL, videoURL2: URL, outputURL: URL, completion: @escaping (Bool, Error?) -> Void) {
  2. let asset1 = AVAsset(url: videoURL1)
  3. let asset2 = AVAsset(url: videoURL2)
  4. guard let videoTrack1 = asset1.tracks(withMediaType: .video).first,
  5. let videoTrack2 = asset2.tracks(withMediaType: .video).first else {
  6. completion(false, nil)
  7. return
  8. }
  9. let composition = AVMutableComposition()
  10. guard let compositionTrack1 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid),
  11. let compositionTrack2 = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid) else {
  12. completion(false, nil)
  13. return
  14. }
  15. try? compositionTrack1.insertTimeRange(CMTimeRange(start: .zero, duration: asset1.duration), of: videoTrack1, at: .zero)
  16. try? compositionTrack2.insertTimeRange(CMTimeRange(start: .zero, duration: asset2.duration), of: videoTrack2, at: .zero)
  17. let instruction = AVMutableVideoCompositionInstruction()
  18. instruction.timeRange = CMTimeRange(start: .zero, duration: CMTimeMaximum(asset1.duration, asset2.duration))
  19. let transformer1 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack1)
  20. let transformer2 = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionTrack2)
  21. let transform1 = videoTrack1.preferredTransform
  22. let transform2 = videoTrack2.preferredTransform
  23. transformer1.setTransform(transform1, at: .zero)
  24. transformer2.setTransform(transform2, at: .zero)
  25. let videoComposition = AVMutableVideoComposition()
  26. videoComposition.instructions = [instruction]
  27. videoComposition.frameDuration = CMTime(value: 1, timescale: 30) // Set desired frame rate
  28. // Calculate the output video size
  29. let videoSize = CGSize(width: videoTrack1.naturalSize.width + videoTrack2.naturalSize.width,
  30. height: max(videoTrack1.naturalSize.height, videoTrack2.naturalSize.height))
  31. videoComposition.renderSize = videoSize
  32. instruction.layerInstructions = [transformer1, transformer2]
  33. guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
  34. completion(false, nil)
  35. return
  36. }
  37. exporter.outputURL = outputURL
  38. exporter.outputFileType = .mp4
  39. exporter.shouldOptimizeForNetworkUse = true
  40. exporter.videoComposition = videoComposition
  41. exporter.exportAsynchronously {
  42. switch exporter.status {
  43. case .completed:
  44. print("Completed......")
  45. DispatchQueue.main.async {
  46. guard let url = exporter.outputURL else {return}
  47. let objAVPlayerVC = AVPlayerViewController()
  48. objAVPlayerVC.player = AVPlayer(url: url)
  49. self.present(objAVPlayerVC, animated: true, completion: {() -> Void in
  50. objAVPlayerVC.player?.play()
  51. })
  52. }
  53. case .failed:
  54. print("Failed......")
  55. case .cancelled:
  56. print("Cancelled......")
  57. default:
  58. break
  59. }
  60. }
  61. }

但在输出视频中,只有一个视频可用,它不会填满屏幕。
我也试过这个库DPVideoMerger但是这个库合并了不同的视频
任何帮助将不胜感激。

5rgfhyps

5rgfhyps1#

你快到了!您的代码只是重叠两个视频轨道,所以最后添加的轨道显示时导出。我们需要在它上面添加一些变换来分离它。
您所需要做的就是在Transformer.setTransform中添加额外的transform,将第一个视频轨道移动到屏幕的左/上半部分,第二个视频轨道移动到屏幕的右/下半部分。
让我们来谈谈左和右的情况。
注意:这可能不是你想要的最终结果,但我们走在正确的道路上。

  1. let transform1 = videoTrack1.preferredTransform
  2. let transform2 = videoTrack2.preferredTransform
  3. let halfWidth = 1080/2 // change for real size
  4. transform1.scaledBy(x: 0.5, y: 0.5)
  5. transform1.translatedBy(x: 0, y: 0) // keep in left half of screen
  6. transform2.scaledBy(x: 0.5, y: 0.5)
  7. transform2.translatedBy(x: halfWidth, y: 0) // move to right half of screen
  8. transformer1.setTransform(transform1, at: .zero)
  9. transformer2.setTransform(transform2, at: .zero)
展开查看全部

相关问题