在更大的画布中导出 Swift 视频并用透明度填充背景

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

问题

我有一个视频,我想将其放置在比视频本身更大的画布中。我使用

AVMutableVideoComposition
工具并通过增加
renderSize
属性来完成此操作。

无论我做什么,我都无法在调整大小的画布上获得透明度。我尝试使用蒙版,设置

backgroundColors
等等
CALayers
,但我无法让透明度粘住。

复制

我创建了这个存储库,您可以在其中运行测试并亲自查看生成的视频:https://github.com/Jasperav/TransparencyVideo/tree/main/TransparencyVideoTests

下面是如何导入和导出视频的代码(与上面的存储库中的代码相同):

呼叫方

let video = Bundle(for: TransparencyVideoTests.self).url(forResource: "transparency", withExtension: ".mov")!
let fileManager = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]

await VideoEditor().export(url: video, outputDir: fileManager.appending(path: "temp.mov"))

视频编辑器

import AppKit
import AVFoundation
import Foundation
import Photos
import QuartzCore
import OSLog

let logger = Logger()

class VideoEditor {
    func export(
        url: URL,
        outputDir: URL
    ) async {
        let asset = AVURLAsset(url: url)
        let extract = try! await extractData(videoAsset: asset)
    
        try! await exportVideo(outputPath: outputDir, asset: asset, videoComposition: extract)
    }

    private func exportVideo(outputPath: URL, asset: AVAsset, videoComposition: AVMutableVideoComposition) async throws {
        let fileExists = FileManager.default.fileExists(atPath: outputPath.path())

        logger.debug("Output dir: \(outputPath), exists: \(fileExists), render size: \(String(describing: videoComposition.renderSize))")

        if fileExists {
            do {
                try FileManager.default.removeItem(atPath: outputPath.path())
            } catch {
                logger.error("remove file failed")
            }
        }

        let dir = outputPath.deletingLastPathComponent().path()

        logger.debug("Will try to create dir: \(dir)")

        try? FileManager.default.createDirectory(atPath: dir, withIntermediateDirectories: true)

        var isDirectory = ObjCBool(false)

        guard FileManager.default.fileExists(atPath: dir, isDirectory: &isDirectory), isDirectory.boolValue else {
            logger.error("Could not create dir, or dir is a file")

            fatalError()
        }

        guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHEVCHighestQualityWithAlpha) else {
            logger.error("generate export failed")

            fatalError()
        }

        exporter.outputURL = outputPath
        exporter.outputFileType = .mov
        exporter.shouldOptimizeForNetworkUse = false
        exporter.videoComposition = videoComposition

        await exporter.export()

        logger.debug("Status: \(String(describing: exporter.status)), error: \(exporter.error)")

        if exporter.status != .completed {
            fatalError()
        }
    }

    private func extractData(videoAsset: AVURLAsset) async throws -> AVMutableVideoComposition {
        guard let videoTrack = try await videoAsset.loadTracks(withMediaType: .video).first else {
            fatalError()
        }

        guard let audioTrack = try await videoAsset.loadTracks(withMediaType: .audio).first else {
            fatalError()
        }

        let composition = AVMutableComposition(urlAssetInitializationOptions: nil)

        guard let compositionVideoTrack = composition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: videoTrack.trackID) else {
            fatalError()
        }
        guard let compostiionAudioTrack = composition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: audioTrack.trackID) else {
            fatalError()
        }

        let duration = try await videoAsset.load(.duration)

        try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: duration), of: videoTrack, at: CMTime.zero)
        try compostiionAudioTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: duration), of: audioTrack, at: CMTime.zero)

        let naturalSize = try await videoTrack.load(.naturalSize)
        let preferredTransform = try await videoTrack.load(.preferredTransform)
        let mainInstruction = AVMutableVideoCompositionInstruction()

        mainInstruction.timeRange = CMTimeRange(start: CMTime.zero, end: duration)

        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        let videoComposition = AVMutableVideoComposition()
 
        videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)

        mainInstruction.layerInstructions = [layerInstruction]
        videoComposition.instructions = [mainInstruction]
        
        // Increases the canvas
        videoComposition.renderSize = .init(width: naturalSize.width + 500, height: naturalSize.height + 500)

        return videoComposition
    }

}

上传结果后

temp.mov
(请参阅控制台日志记录),显示黑边(来自https://rotato.app/tools/transparent-video):

如何确保背景填充透明颜色?

swift avfoundation video-editing
1个回答
0
投票

修改您的

VideoEditor
课程。这只是一种快速而肮脏的方法

import AVFoundation

class VideoEditor {
    func export(url: URL, outputDir: URL) async {
        let asset = AVURLAsset(url: url)
        let videoComposition = try await createVideoComposition(asset: asset)
        try! await exportVideo(outputPath: outputDir, asset: asset, videoComposition: videoComposition)
    }
    
    private func exportVideo(outputPath: URL, asset: AVAsset, videoComposition: AVVideoComposition) async throws {
        // Same as before
        // Configure export session with video composition
    }
    
    private func createVideoComposition(asset: AVAsset) async throws -> AVVideoComposition {
        // Extract video track
        guard let videoTrack = asset.tracks(withMediaType: .video).first else {
            throw NSError(domain: "VideoEditor", code: 1, userInfo: [NSLocalizedDescriptionKey: "Video track not found"])
        }
        
        // Create video composition instruction
        let mainInstruction = AVMutableVideoCompositionInstruction()
        mainInstruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration)
        
        // Create layer instruction for video track
        let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack)
        
        // Configure transformation for video layer
        let naturalSize = videoTrack.naturalSize
        let transform = CGAffineTransform(translationX: 250, y: 250) // Translate to center of increased canvas
        layerInstruction.setTransform(transform, at: .zero)
        
        // Set up video composition
        let videoComposition = AVMutableVideoComposition()
        videoComposition.renderSize = CGSize(width: naturalSize.width + 500, height: naturalSize.height + 500)
        videoComposition.frameDuration = CMTime(value: 1, timescale: 30)
        mainInstruction.layerInstructions = [layerInstruction]
        videoComposition.instructions = [mainInstruction]
        
        // Set background color to transparent
        videoComposition.backgroundColor = UIColor.clear.cgColor
        
        return videoComposition
    }
}

然后显式设置

backgroundColor
AVMutableVideoComposition to UIColor.clear.cgColor
属性,将视频的背景色设置为透明

© www.soinside.com 2019 - 2024. All rights reserved.