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 不保存到库

用swift在ios上分享资源库视频

iOS - 保存到库/缓存/会导致错误吗?

使用CameraManager和Swift将捕获的视频保存到iOS库

将图像保存到库时不显示 HUD 微调器

在 UIImagepickercontroller ios 中单击使用按钮后无法点击任何地方