Swift扩展视频画布后,如何实现背景透明导出?
解决AVMutableVideoComposition扩展画布后背景不透明的问题
核心问题分析
你当前的代码存在两个关键问题导致背景无法透明:
- 导出时使用的是原视频asset而非你创建的
AVMutableComposition,导致画布扩展的设置没有被正确应用 - 未明确将
AVMutableVideoComposition的背景设置为透明,且原视频轨道未调整到扩展画布的正确位置
解决方案步骤
1. 修正导出逻辑,使用自定义Composition
你的extractData方法创建了AVMutableComposition但未在导出时使用,需要将其与AVMutableVideoComposition一起返回,并用该Composition初始化导出会话。
2. 设置透明背景
给AVMutableVideoComposition明确设置透明背景色:
videoComposition.backgroundColor = NSColor.clear.cgColor
3. 调整原视频在扩展画布中的位置
通过AVMutableVideoCompositionLayerInstruction给原视频轨道添加平移变换,将其放置在扩展画布的合适位置(例如居中)。
修改后的完整代码
VideoEditor类修改
import AppKit import AVFoundation import Foundation import OSLog let logger = Logger() class VideoEditor { func export( url: URL, outputDir: URL ) async { let asset = AVURLAsset(url: url) // 返回composition和videoComposition let (composition, videoComposition) = try! await extractData(videoAsset: asset) try! await exportVideo(outputPath: outputDir, composition: composition, videoComposition: videoComposition) } private func exportVideo(outputPath: URL, composition: 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() } // 使用自定义composition初始化导出会话 guard let exporter = AVAssetExportSession(asset: composition, 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 -> (AVMutableComposition, 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: .video, preferredTrackID: videoTrack.trackID) else { fatalError() } guard let compositionAudioTrack = composition.addMutableTrack(withMediaType: .audio, preferredTrackID: audioTrack.trackID) else { fatalError() } let duration = try await videoAsset.load(.duration) try compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: duration), of: videoTrack, at: .zero) try compositionAudioTrack.insertTimeRange(CMTimeRangeMake(start: .zero, duration: duration), of: audioTrack, at: .zero) let naturalSize = try await videoTrack.load(.naturalSize) let preferredTransform = try await videoTrack.load(.preferredTransform) let mainInstruction = AVMutableVideoCompositionInstruction() mainInstruction.timeRange = CMTimeRange(start: .zero, end: duration) let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack) let videoComposition = AVMutableVideoComposition() videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30) // 设置透明背景 videoComposition.backgroundColor = NSColor.clear.cgColor mainInstruction.layerInstructions = [layerInstruction] videoComposition.instructions = [mainInstruction] // 扩展画布尺寸 let newRenderSize = CGSize(width: naturalSize.width + 500, height: naturalSize.height + 500) videoComposition.renderSize = newRenderSize // 计算偏移量,将原视频居中放置 let offsetX = (newRenderSize.width - naturalSize.width) / 2 let offsetY = (newRenderSize.height - naturalSize.height) / 2 // 组合原变换与平移变换 let finalTransform = preferredTransform.concatenating(CGAffineTransform(translationX: offsetX, y: offsetY)) layerInstruction.setTransform(finalTransform, at: .zero) return (composition, videoComposition) } }
关键说明
- 必须使用自定义的
AVMutableComposition初始化导出会话,否则videoComposition的画布扩展设置无法生效 - 明确设置
backgroundColor为NSColor.clear.cgColor,确保扩展区域背景透明 - 通过变换将原视频轨道调整到扩展画布的目标位置(示例中为居中),避免原视频位置异常
- 导出预设
AVAssetExportPresetHEVCHighestQualityWithAlpha和输出格式.mov是支持Alpha通道的正确选择,无需修改
内容的提问来源于stack exchange,提问作者J. Doe
相关产品推荐
相关产品推荐

