Swift iOS:将视频保存到库

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

我正在尝试从图像制作视频,然后将其移动到照片库。我总是以“保存视频时出错:可选(错误域=PHPhotosErrorDomain代码=-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))")
            }
        }
    }
}
ios swift video
3个回答
0
投票

我刚刚遇到这个问题,因为图片路径中不存在该图片,所以无法将图片复制到相册中。


0
投票

也许您可以将 .png 格式转换为 .jpg 格式。我今天尝试了一下,成功了。


0
投票

我在您上面的代码中发现了问题。 您正在将视频名称传递给

saveVideoToLibrary(videoURL: NSURL(fileURLWithPath: videoPath))
而不是传递您创建的视频的网址。

只需将您的

saveVideoToLibrary(videoURL: NSURL(fileURLWithPath: videoPath))
替换为以下代码行

let outputURL = appdocUrl.appendingPathComponent("playground.mov") saveVideoToLibrary(videoURL: outputURL)

见证魔法。

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