Swift iOS:将视频保存到库
Posted
技术标签:
【中文标题】Swift iOS:将视频保存到库【英文标题】:Swift iOS: Save Video to Library 【发布时间】:2020-08-25 09:05:46 【问题描述】:我正在尝试从图像制作视频,然后将其移至照片库。我总是以“保存视频时出错:可选(错误域=phphotosErrorDomain Code=-1“(null)”)结束 大部分代码都在其他帖子中找到并已修复,因为 swift 似乎发生了很大变化。查找信息是一场真正的噩梦,我将非常感谢您的帮助。如果有人对代码感兴趣或提供帮助,我已附上整个项目。
项目下载: https://www.dropbox.com/s/nx910dy9arssngy/SwiftMP4ios.zip?dl=0
// ContentView.swift
// SwiftMP4ios
//
// Created by Marc Breuer on 21.08.20.
// Copyright © 2020 Marc Breuer. All rights reserved.
//
import SwiftUI
import Foundation
import AVFoundation
import UIKit
import Photos
struct ContentView: View
var body: some View
Text("Hello, World!")
.onTapGesture
createMovie()
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
var appdocUrl: URL
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
private func createMovie()
let outputPath = "playground.mov"
let image1 = UIImage(named: "1")
let image2 = UIImage(named: "2")
let image3 = UIImage(named: "3")
let image4 = UIImage(named: "4")
let width = Int(image1!.size.width);
let height = Int(image1!.size.height);
let size = CGSize(width: width, height: height)
//load(fileName:"test.jpg")
//if(image != nil)
writeImagesAsMovie(allImages:[image1!, image2!, image3!, image4!],
videoPath:outputPath,videoSize:size,videoFPS:30)
private func load(fileName: String) -> UIImage?
let fileURL = appdocUrl.appendingPathComponent(fileName)
do
let imageData = try Data(contentsOf: fileURL)
return UIImage(data: imageData)
catch
print("Error loading image : \(error)")
return nil
private func save(fileName: String, image: UIImage) -> String?
let fileURL = appdocUrl.appendingPathComponent(fileName)
if let imageData = image.jpegData(compressionQuality: 1.0)
try? imageData.write(to: fileURL, options: .atomic)
return fileName // ----> Save fileName
print("Error saving image")
return nil
func writeImagesAsMovie(allImages: [UIImage], videoPath: String, videoSize: CGSize, videoFPS: Int32)
// Create AVAssetWriter to write video
guard let assetWriter = createAssetWriter(path:videoPath, size: videoSize) else
print("Error converting images to video: AVAssetWriter not created")
return
// If here, AVAssetWriter exists so create AVAssetWriterInputPixelBufferAdaptor
let writerInput = assetWriter.inputs.filter $0.mediaType == AVMediaType.video .first!
let sourceBufferAttributes : [String : Any] = [
kCVPixelBufferPixelFormatTypeKey as String : Int(kCVPixelFormatType_32ARGB),
kCVPixelBufferWidthKey as String : videoSize.width,
kCVPixelBufferHeightKey as String : videoSize.height,
]
let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput, sourcePixelBufferAttributes: sourceBufferAttributes)
// Start writing session
assetWriter.startWriting()
print("ASSET WRITER STATUS",assetWriter.status.rawValue);
assetWriter.startSession(atSourceTime:CMTime.zero)
if (pixelBufferAdaptor.pixelBufferPool == nil)
print("Error converting images to video: pixelBufferPool nil after starting session")
return
// -- Create queue for <requestMediaDataWhenReadyOnQueue>
let mediaQueue = DispatchQueue.init(label:"mediaInputQueue")
// -- Set video parameters
let frameDuration = CMTimeMake(value:1, timescale:videoFPS)
var frameCount = 0
// -- Add images to video
let numImages = allImages.count
writerInput.requestMediaDataWhenReady(on: mediaQueue, using: () -> Void in
// Append unadded images to video but only while input ready
while (writerInput.isReadyForMoreMediaData && frameCount < numImages)
let lastFrameTime = CMTimeMake(value: Int64(frameCount), timescale: videoFPS)
let presentationTime = frameCount == 0 ? lastFrameTime : CMTimeAdd(lastFrameTime, frameDuration)
if !appendPixelBufferForImageAtURL(image: allImages[frameCount], pixelBufferAdaptor: pixelBufferAdaptor, presentationTime: presentationTime)
print("Error converting images to video: AVAssetWriterInputPixelBufferAdapter failed to append pixel buffer")
return
frameCount += 1
// No more images to add? End video.
if (frameCount >= numImages)
writerInput.markAsFinished()
assetWriter.finishWriting
if (assetWriter.error != nil)
print("Error converting images to video: \(String(describing: assetWriter.error))")
else
saveVideoToLibrary(videoURL: NSURL(fileURLWithPath: videoPath))
print("Converted images to movie @ \(videoPath)")
//UISaveVideoAtPathToSavedPhotosAlbum(videoPath)
)
func createAssetWriter(path: String, size: CGSize) -> AVAssetWriter?
// Convert <path> to NSURL object
//let pathURL = NSURL(fileURLWithPath: path)
let outputURL = appdocUrl.appendingPathComponent(path)
print("Output URL ",outputURL)
//make sure there is not file here
do
try FileManager.default.removeItem(at: outputURL)
catch
// Return new asset writer or nil
do
// Create asset writer
let newWriter = try AVAssetWriter(outputURL: outputURL as URL, fileType: AVFileType.mp4)
// Define settings for video input
let videoSettings: [String : Any] = [
AVVideoCodecKey : AVVideoCodecType.h264,
AVVideoWidthKey : size.width,
AVVideoHeightKey : size.height,
]
// Add video input to writer
let assetWriterVideoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoSettings)
newWriter.add(assetWriterVideoInput)
// Return writer
print("Created asset writer for \(size.width)x\(size.height) video", newWriter.status.rawValue)
return newWriter
catch
print("Error creating asset writer: \(error)")
return nil
func appendPixelBufferForImageAtURL(image: UIImage, pixelBufferAdaptor: AVAssetWriterInputPixelBufferAdaptor, presentationTime: CMTime) -> Bool
var appendSucceeded = false
autoreleasepool
if let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool
//let pixelBufferPointer = CVPixelBufferPointer
var pixelBuffer : CVPixelBuffer? = nil
let status: CVReturn = CVPixelBufferPoolCreatePixelBuffer(
kCFAllocatorDefault,
pixelBufferPool,
&pixelBuffer
)
if status == 0
fillPixelBufferFromImage(image: image, pixelBuffer: pixelBuffer!)
appendSucceeded = pixelBufferAdaptor.append(pixelBuffer!, withPresentationTime: presentationTime)
else
NSLog("Error: Failed to allocate pixel buffer from pool")
return appendSucceeded
func fillPixelBufferFromImage(image: UIImage, pixelBuffer: CVPixelBuffer)
CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
let pixelData = CVPixelBufferGetBaseAddress(pixelBuffer)
let rgbColorSpace = CGColorSpaceCreateDeviceRGB()
let width = Int(image.size.width);
let height = Int(image.size.height);
// Create CGBitmapContext
let context = CGContext(
data: pixelData,
width: width,
height: height,
bitsPerComponent: 8,
bytesPerRow: CVPixelBufferGetBytesPerRow(pixelBuffer),
space: rgbColorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue
)
// Draw image into context
context!.draw(image.cgImage!, in: CGRect(origin: .zero, size: image.size))
//draw(context, in: CGRect(0, 0, image.size.width, image.size.height), false)
//CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.cgImage)
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
func saveVideoToLibrary(videoURL: NSURL)
PHPhotoLibrary.requestAuthorization status in
// Return if unauthorized
guard status == .authorized else
print("Error saving video: unauthorized access")
return
PHPhotoLibrary.shared().performChanges(
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: videoURL as URL)
) success, error in
if !success
print("Error saving video: \(String(describing: error))")
【问题讨论】:
感谢***.com/questions/38064042/…,我已经设法通过下载应用程序数据来验证设备上是否存在视频,因此将工作电影移动到照片确实只是一个问题。 我还找到了一个功能“UISaveVideoAtPathToSavedPhotosAlbum”,它应该完全符合我的要求。我不得不进行大量重组和研究,但这只是给了我“可选(错误域=ALAssetsLibraryErrorDomain Code=-1“未知错误”UserInfo= ...... Error Domain=com.apple.photos.error Code= 42001 “(空)”)”。千刀万剐。 我也遇到了同样的问题,请问您找到解决方法了吗? 我停止使用照片库并切换到通过 Unity 插件共享视频。老实说,我不记得我是否曾经设法让照片库正常工作。对不起! 【参考方案1】:我刚刚遇到这个问题,因为图片路径中不存在图片,所以无法将图片复制到相册中。
【讨论】:
以上是关于Swift iOS:将视频保存到库的主要内容,如果未能解决你的问题,请参考以下文章
Xamarin iOS ALAssetsLibrary WriteVideoToSavedPhotosAlbum 不保存到库