Reputation: 336
I'm trying to make a video from images and then moving it to the photo library. I always end up with "Error saving video: Optional(Error Domain=PHPhotosErrorDomain Code=-1 "(null)")" Most of the code was found in other posts and fixed, as swift seems to have changed a lot. Finding information is a real nightmare and I would be very grateful for some help. I've attached the entire project, if anyone is interested in the code or helping out.
Project Download: 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))")
}
}
}
}
Upvotes: 3
Views: 3338
Reputation: 1
I have found the issue in your above code.
your are passing the name of video to
saveVideoToLibrary(videoURL: NSURL(fileURLWithPath: videoPath))
instead of passing the url of video that you've created.
just replace your saveVideoToLibrary(videoURL: NSURL(fileURLWithPath: videoPath))
with the following line of code
let outputURL = appdocUrl.appendingPathComponent("playground.mov") saveVideoToLibrary(videoURL: outputURL)
and see the magic.
Upvotes: 0
Reputation: 1
Maybe you could convert .png format to .jpg format. I tried it today and successed.
Upvotes: 0
Reputation: 73
I just ran into this problem because the image does not exist in the image path, so the image cannot be copied to the album.
Upvotes: 0