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

dauxcl2d  于 12个月前  发布在  iOS
关注(0)|答案(1)|浏览(114)

我想以类似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但是这个库合并了不同的视频
任何帮助将不胜感激。

5rgfhyps

5rgfhyps1#

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

let transform1 = videoTrack1.preferredTransform
let transform2 = videoTrack2.preferredTransform

let halfWidth = 1080/2   // change for real size
transform1.scaledBy(x: 0.5, y: 0.5)
transform1.translatedBy(x: 0, y: 0) // keep in left half of screen

transform2.scaledBy(x: 0.5, y: 0.5)
transform2.translatedBy(x: halfWidth, y: 0) // move to right half of screen

transformer1.setTransform(transform1, at: .zero)
transformer2.setTransform(transform2, at: .zero)

相关问题