Swift中旋转URL来源视频及AVAssetWriter写入后旋转视频的方法
嘿,我来帮你搞定这两个Swift视频旋转的问题,全部用当前最新的AVFoundation API,绝对不会用到废弃的Objective-C旧方法~
1. 如何在Swift中旋转来自URL的视频?
要旋转本地或网络URL的视频,我们可以借助AVFoundation的AVAsset、AVMutableVideoComposition和AVAssetExportSession实现,这是目前最稳定且通用的方案。具体步骤如下:
第一步:加载目标视频资源
先通过视频URL初始化AVAsset,这是我们处理视频的基础:let inputURL = URL(fileURLWithPath: "你的视频本地路径") // 网络URL直接传即可 let asset = AVAsset(url: inputURL)第二步:配置视频旋转规则
接下来要创建AVMutableVideoComposition来定义视频的渲染逻辑,核心是给视频轨道添加旋转的仿射变换。这里以顺时针旋转90度为例,还要注意旋转后视频的宽高会互换,得调整渲染尺寸:guard let videoTrack = asset.tracks(withMediaType: .video).first else { print("没找到视频轨道,无法处理") return } let videoComposition = AVMutableVideoComposition() // 旋转后宽高互换,所以renderSize要改成原视频的高×宽 videoComposition.renderSize = CGSize(width: videoTrack.naturalSize.height, height: videoTrack.naturalSize.width) videoComposition.frameDuration = videoTrack.minFrameDuration // 创建渲染指令 let instruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration) // 给视频轨道添加旋转变换 let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack) // 顺时针转90度 + 平移修正,避免视频被裁剪 let rotationTransform = CGAffineTransform(rotationAngle: .pi/2) .translatedBy(x: videoTrack.naturalSize.height, y: 0) layerInstruction.setTransform(rotationTransform, at: .zero) instruction.layerInstructions = [layerInstruction] videoComposition.instructions = [instruction]要是你需要其他旋转角度,直接替换
rotationAngle的值就行:- 逆时针90度:
-.pi/2,对应的平移是translatedBy(x: 0, y: videoTrack.naturalSize.width) - 180度:
.pi,对应的平移是translatedBy(x: videoTrack.naturalSize.width, y: videoTrack.naturalSize.height)
- 逆时针90度:
第三步:导出旋转后的视频
最后用AVAssetExportSession把处理后的视频导出到指定路径:// 生成输出URL,比如放在Documents目录 let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let outputURL = documentsDir.appendingPathComponent("rotated_video_\(Date().timeIntervalSince1970).mov") guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else { print("创建导出会话失败") return } exportSession.outputURL = outputURL exportSession.outputFileType = .mov // 也可以用.mp4,根据需求调整 exportSession.videoComposition = videoComposition // 异步导出,避免阻塞主线程 exportSession.exportAsynchronously { DispatchQueue.main.async { switch exportSession.status { case .completed: print("视频旋转完成!输出路径:\(outputURL)") // 这里可以做后续操作,比如展示给用户或保存到相册 case .failed: print("导出失败:\(exportSession.error?.localizedDescription ?? "未知错误")") case .cancelled: print("导出被取消") default: break } } }
2. AVAssetWriter完成写入后旋转已生成视频的解决方案
你提到的在AVAssetWriter完成回调里旋转已生成视频的场景,完全可以复用上面的旋转逻辑,只需要把回调里的输出URL作为旋转的输入源就行。下面是适配你场景的完整代码示例:
假设你的AVAssetWriter完成回调是这样的,我们在里面加入旋转逻辑:
// 你的AVAssetWriter完成处理程序 assetWriter.finishWriting { [weak self] in guard let self = self else { return } guard assetWriter.status == .completed else { print("AVAssetWriter写入失败:\(assetWriter.error?.localizedDescription ?? "未知错误")") return } // 拿到写入完成后的视频URL let writtenVideoURL = assetWriter.outputURL! // 调用封装好的旋转方法 self.rotateVideo(inputURL: writtenVideoURL) { success, rotatedURL in guard success, let url = rotatedURL else { print("视频旋转失败") return } // 旋转完成!这里可以创建播放器展示给用户,或者让用户保存 print("旋转后的视频已准备好:\(url)") // 示例:用AVPlayerViewController展示视频 let player = AVPlayer(url: url) let playerVC = AVPlayerViewController() playerVC.player = player self.present(playerVC, animated: true) { player.play() } } } // 封装的视频旋转方法,方便复用 private func rotateVideo(inputURL: URL, completion: @escaping (Bool, URL?) -> Void) { let asset = AVAsset(url: inputURL) guard let videoTrack = asset.tracks(withMediaType: .video).first else { completion(false, nil) return } let videoComposition = AVMutableVideoComposition() // 这里还是以顺时针旋转90度为例,你可以按需修改角度 videoComposition.renderSize = CGSize(width: videoTrack.naturalSize.height, height: videoTrack.naturalSize.width) videoComposition.frameDuration = videoTrack.minFrameDuration let instruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRange(start: .zero, duration: asset.duration) let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: videoTrack) let rotationTransform = CGAffineTransform(rotationAngle: .pi/2) .translatedBy(x: videoTrack.naturalSize.height, y: 0) layerInstruction.setTransform(rotationTransform, at: .zero) instruction.layerInstructions = [layerInstruction] videoComposition.instructions = [instruction] // 生成输出URL let documentsDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let outputURL = documentsDir.appendingPathComponent("post_rotated_video_\(Date().timeIntervalSince1970).mov") guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else { completion(false, nil) return } exportSession.outputURL = outputURL exportSession.outputFileType = .mov exportSession.videoComposition = videoComposition exportSession.exportAsynchronously { DispatchQueue.main.async { switch exportSession.status { case .completed: completion(true, outputURL) case .failed, .cancelled: completion(false, nil) default: completion(false, nil) } } } }
额外小贴士
- 如果你追求速度,也可以只修改视频的旋转元数据(不修改像素内容),但这种方式依赖播放器对元数据的支持,有些第三方播放器可能不会自动应用旋转。如果要兼容所有场景,还是建议上面的视频合成方式。
- 导出预设
presetName可以选AVAssetExportPresetMediumQuality,导出速度更快,适合对画质要求不高的场景。 - 记得处理权限:比如写入Documents目录没问题,但要保存到相册的话,需要申请
PHPhotoLibrary的权限。
内容的提问来源于stack exchange,提问作者Chewie The Chorkie
相关产品推荐
相关产品推荐

