快速叠加两个视频

问题描述 投票:3回答:1

我想备份this问题。我在重叠两个视频时遇到问题。我相信这与第一个AVMutableVideoCompositionLayerInstruction的透明度有关,但是我在运气方面做了大量工作。任何建议将不胜感激!:

func overlay(video firstAsset: AVURLAsset, withSecondVideo secondAsset: AVURLAsset) {

let mixComposition = AVMutableComposition()

let firstTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
let secondTrack = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)

guard let firstMediaTrack = firstAsset.tracks(withMediaType: AVMediaType.video).first else { return }
guard let secondMediaTrack = secondAsset.tracks(withMediaType: AVMediaType.video).first else { return }

do {
    try firstTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, firstAsset.duration), of: firstMediaTrack, at: kCMTimeZero)
    try secondTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, secondAsset.duration), of: secondMediaTrack, at: kCMTimeZero)
} catch (let error) {
    print(error)
}

self.width = max(firstMediaTrack.naturalSize.width, secondMediaTrack.naturalSize.width)
self.height = max(firstMediaTrack.naturalSize.height, secondMediaTrack.naturalSize.height)

let videoComposition = AVMutableVideoComposition()
videoComposition.renderSize = CGSize(width: width!, height: height!)
videoComposition.frameDuration = firstMediaTrack.minFrameDuration

let firstLayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: firstMediaTrack)
let scale = CGAffineTransform(scaleX: 0.3, y: 0.3)
let move = CGAffineTransform(translationX: self.width! - ((self.width! * 0.3) + 10), y: 10)
firstLayerInstruction.setTransform(scale.concatenating(move), at: kCMTimeZero)
firstLayerInstruction.setOpacity(1.0, at: kCMTimeZero)

let secondlayerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: secondMediaTrack)
secondlayerInstruction.setTransform((secondTrack?.preferredTransform)!, at: kCMTimeZero)
secondlayerInstruction.setOpacity(1.0, at: kCMTimeZero)

let combined = AVMutableVideoCompositionInstruction()
combined.timeRange = CMTimeRangeMake(kCMTimeZero, max(firstAsset.duration, secondAsset.duration))
combined.backgroundColor = UIColor.clear.cgColor
combined.layerInstructions = [firstLayerInstruction, secondlayerInstruction]

videoComposition.instructions = [combined]

let outputUrl = self.getPathForTempFileNamed(filename: "output.mov")

self.exportCompositedVideo(compiledVideo: mixComposition, toURL: outputUrl, withVideoComposition: videoComposition)

self.removeTempFileAtPath(path: outputUrl.absoluteString)

}

[预期结果是一个视频,其中两个视频都被覆盖。第一层是全屏视频,第二层是位于右上角的较小视频。奇怪的是,当我在AVMutableVideoCompositionInstruction上使用并将两个AVMutableVideoCompositionLayerInstruction放在其层指令中时,它就可以工作! -但是FirstMediaTrack中使用的视频同时用于两个图层?试了一下,然后尝试实现详细的方法here,该方法对两层都有单独的说明,但是这种方法导致仅第一层显示全屏视频,而第二层完全不可见。

ios swift avfoundation
1个回答
0
投票

这里是对我有用的代码,我基于this教程。我发现关键是将背景设置为清晰(位于this线程上)

import AVFoundation
import AVKit
import Photos

var myurl: URL?

func newoverlay(video firstAsset: AVURLAsset, withSecondVideo secondAsset: AVURLAsset) {



    // 1 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
    let mixComposition = AVMutableComposition()

    // 2 - Create two video tracks
    guard let firstTrack = mixComposition.addMutableTrack(withMediaType: .video,
                                                          preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
    do {
        try firstTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: firstAsset.duration),
                                       of: firstAsset.tracks(withMediaType: .video)[0],
                                       at: CMTime.zero)
    } catch {
        print("Failed to load first track")
        return
    }

    guard let secondTrack = mixComposition.addMutableTrack(withMediaType: .video,
                                                           preferredTrackID: Int32(kCMPersistentTrackID_Invalid)) else { return }
    do {
        try secondTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: secondAsset.duration),
                                        of: secondAsset.tracks(withMediaType: .video)[0],
                                        at: CMTime.zero)
    } catch {
        print("Failed to load second track")
        return
    }

    // 2.1
    let mainInstruction = AVMutableVideoCompositionInstruction()
    mainInstruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: CMTimeAdd(firstAsset.duration, secondAsset.duration))

    // 2.2
    let firstInstruction = ViewController.videoCompositionInstruction(firstTrack, asset: firstAsset)
    //firstInstruction.setOpacity(0.0, at: firstAsset.duration)
    let scale = CGAffineTransform(scaleX: 0.3, y: 0.3)
    let move = CGAffineTransform(translationX: 10, y: 10)
    firstInstruction.setTransform(scale.concatenating(move), at: CMTime.zero)
    //firstInstruction.setTransform(scale, at: CMTime.zero)
    let secondInstruction = ViewController.videoCompositionInstruction(secondTrack, asset: secondAsset)

    // 2.3
    mainInstruction.layerInstructions = [firstInstruction, secondInstruction]
    let mainComposition = AVMutableVideoComposition()
    mainComposition.instructions = [mainInstruction]
    mainComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)

    let width = max(firstTrack.naturalSize.width, secondTrack.naturalSize.width)
    let height = max(firstTrack.naturalSize.height, secondTrack.naturalSize.height)

    mainComposition.renderSize = CGSize(width: width, height: height)

    mainInstruction.backgroundColor = UIColor.clear.cgColor


    // 4 - Get path
    guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .long
    dateFormatter.timeStyle = .short
    let date = dateFormatter.string(from: Date())
    let url = documentDirectory.appendingPathComponent("mergeVideo-\(date).mov")

    // Check exists and remove old file
    FileManager.default.removeItemIfExisted(url as URL)

    // 5 - Create Exporter
    guard let exporter = AVAssetExportSession(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) else { return }
    exporter.outputURL = url
    exporter.outputFileType = AVFileType.mov
    exporter.shouldOptimizeForNetworkUse = true
    exporter.videoComposition = mainComposition


    // 6 - Perform the Export
    exporter.exportAsynchronously() {
        DispatchQueue.main.async {

            print("Movie complete")

            self.myurl = url as URL

            PHPhotoLibrary.shared().performChanges({
                PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url as URL)
            }) { saved, error in
                if saved {
                    print("Saved")
                }
            }

            self.playVideo()

        }
    }
}



func playVideo() {
    let player = AVPlayer(url: myurl!)
    let playerLayer = AVPlayerLayer(player: player)
    playerLayer.frame = self.view.bounds
    self.view.layer.addSublayer(playerLayer)
    player.play()
    print("playing...")
}




static func videoCompositionInstruction(_ track: AVCompositionTrack, asset: AVAsset) -> AVMutableVideoCompositionLayerInstruction {
    let instruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)
    let assetTrack = asset.tracks(withMediaType: AVMediaType.video)[0]

    let transform = assetTrack.preferredTransform
    let assetInfo = orientationFromTransform(transform)

    var scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.width
    if assetInfo.isPortrait {
        scaleToFitRatio = UIScreen.main.bounds.width / assetTrack.naturalSize.height
        let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
        instruction.setTransform(assetTrack.preferredTransform.concatenating(scaleFactor), at: CMTime.zero)
    } else {
        let scaleFactor = CGAffineTransform(scaleX: scaleToFitRatio, y: scaleToFitRatio)
        var concat = assetTrack.preferredTransform.concatenating(scaleFactor)
            .concatenating(CGAffineTransform(translationX: 0, y: UIScreen.main.bounds.width / 2))
        if assetInfo.orientation == .down {
            let fixUpsideDown = CGAffineTransform(rotationAngle: CGFloat(Double.pi))
            let windowBounds = UIScreen.main.bounds
            let yFix = assetTrack.naturalSize.height + windowBounds.height
            let centerFix = CGAffineTransform(translationX: assetTrack.naturalSize.width, y: yFix)
            concat = fixUpsideDown.concatenating(centerFix).concatenating(scaleFactor)
        }
        instruction.setTransform(concat, at: CMTime.zero)
    }

    return instruction
}

static func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
    var assetOrientation = UIImage.Orientation.up
    var isPortrait = false
    if transform.a == 0 && transform.b == 1.0 && transform.c == -1.0 && transform.d == 0 {
        assetOrientation = .right
        isPortrait = true
    } else if transform.a == 0 && transform.b == -1.0 && transform.c == 1.0 && transform.d == 0 {
        assetOrientation = .left
        isPortrait = true
    } else if transform.a == 1.0 && transform.b == 0 && transform.c == 0 && transform.d == 1.0 {
        assetOrientation = .up
    } else if transform.a == -1.0 && transform.b == 0 && transform.c == 0 && transform.d == -1.0 {
        assetOrientation = .down
    }
    return (assetOrientation, isPortrait)
}


}


extension FileManager {
func removeItemIfExisted(_ url:URL) -> Void {
    if FileManager.default.fileExists(atPath: url.path) {
        do {
            try FileManager.default.removeItem(atPath: url.path)
        }
        catch {
            print("Failed to delete file")
        }
    }
}
}
© www.soinside.com 2019 - 2024. All rights reserved.