BadmintonCat
BadmintonCat

Reputation: 9586

PHPhotoLibrary performChanges errors when copying images and videos

Users can download image and video files from a server which are then stored temporarily under the app's Documents path and then copied from there to a custom album in the iOS photo library with either PHAssetChangeRequest.creationRequestForAssetFromImage or PHAssetChangeRequest.creationRequestForAssetFromVideo.

This works flawless when only image files OR video files are downloaded but when both images and videos are mixed in the same download batch there are always some files failing arbitrarily with one of these errors:

Error was: The operation couldn’t be completed. (PHPhotosErrorDomain error -1.)

or

Error was: The operation couldn’t be completed. (PHPhotosErrorDomain error 3302.)

This is the responsible code for copying files to the photo library:

public func saveFileToPhotoLibrary(_ url: URL, completion: @escaping (Bool) -> Void)
{
    guard let album = _album else
    {
        completion(false)
        return
    }

    guard let supertype = url.supertype else
    {
        completion(false)
        return
    }

    if supertype == .image || supertype == .movie
    {
        DispatchQueue.global(qos: .background).async
        {
            PHPhotoLibrary.shared().performChanges(
            {
                guard let assetChangeRequest = supertype == .image
                    ? PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: url)
                    : PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url) else
                {
                    completion(false)
                    return
                }

                guard let assetPlaceholder = assetChangeRequest.placeholderForCreatedAsset else
                {
                    completion(false)
                    return
                }

                guard let albumChangeRequest = PHAssetCollectionChangeRequest(for: album) else
                {
                    completion(false)
                    return
                }

                albumChangeRequest.addAssets([assetPlaceholder] as NSFastEnumeration)
            })
            {
                saved, error in

                if let error = error
                {
                    print("Failed to save image file from \(url.lastPathComponent) to photo gallery. Error was: \(error.localizedDescription)")
                    DispatchQueue.main.async { completion(false) }
                }
            }
        }
    }
}

Does anyone know why the errors happen or how to solve this issue so that no file copies are failing?

Upvotes: 3

Views: 2953

Answers (4)

Xys
Xys

Reputation: 10899

I had the -1 error because the file didn't exist at the specified path.

Upvotes: 0

Kavindu Dissanayake
Kavindu Dissanayake

Reputation: 1

The same error also I had, Things when we downloaded and created an extension(MemeType) should be a correct example if we want to save an mp4 file we should have to download the .mp4 file, if any reason mismatch then we face this type errrors. when you don't know exactly the file type I arrange code snippets to achieve it .

  enum FileType {
    case jpg
    case mp4
    case ogv
    case webm
    case threeGP
    case unknown 
 } 


func determineFileType(data: Data) -> FileType {
var values = [UInt8](repeating:0, count:4)
data.copyBytes(to: &values, count: 4)

if values[0] == 0xFF && values[1] == 0xD8 && values[2] == 0xFF {
    return .jpg
} else if values[0] == 0x00 && values[1] == 0x00 && values[2] == 0x00 {
    return .mp4 // Basic heuristic for .mp4
} else if values[0] == 0x4F && values[1] == 0x67 && values[2] == 0x67 && values[3] == 0x53 {
    return .ogv
} else if values[0] == 0x1A && values[1] == 0x45 && values[2] == 0xDF && values[3] == 0xA3 {
    return .webm
} else if values[0] == 0x66 && values[1] == 0x74 && values[2] == 0x79 && values[3] == 0x70 {
    return .threeGP // Basic heuristic for .3gp
} else {
    return .unknown
}}

private func saveDataAsVideoFile(_ data: Data, completion: @escaping (Bool, Error?) -> Void) {
let fileType = determineFileType(data: data)
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
let filePath: String

switch fileType {
case .jpg:
    filePath = "\(documentsPath)/tempFile.jpg"
case .mp4:
    filePath = "\(documentsPath)/tempFile.mp4"
case .ogv:
    filePath = "\(documentsPath)/tempFile.ogv"
case .webm:
    filePath = "\(documentsPath)/tempFile.webm"
case .threeGP:
    filePath = "\(documentsPath)/tempFile.3gp"
case .unknown:
    completion(false, nil)
    return
}

let fileUrl = URL(fileURLWithPath: filePath)

do {
    try data.write(to: fileUrl)
    
    PHPhotoLibrary.shared().performChanges({
        // Since PHAssetChangeRequest doesn't support other video formats like .ogv, .webm or .3gp directly,
        // you might be limited in the types of videos you can save to the Photos library.
        if fileType == .jpg {
            _ = PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileUrl)
        } else if fileType == .mp4 || fileType == .threeGP {
            _ = PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileUrl)
        } else {
            completion(false, nil)
            return
        }
    }) { success, error in
        completion(success, error)
    }
    
} catch {
    print("Error saving data: \(error)")
    completion(false, error)
} }

Upvotes: 0

AndiDog
AndiDog

Reputation: 70218

PHPhotosErrorDomain error 3302 means invalidResource, see list of error codes. This happened to me because I tried to save a picture from a file in the temporary directory. It seems only specific directories can be used:

// FAILS
let imageUrl = FileManager.default.temporaryDirectory.appendingPathComponent("save-me")

// WORKS
let documentsPath = NSSearchPathForDirectoriesInDomains(
  .documentDirectory, .userDomainMask, true)[0]
let filePath = "\(documentsPath)/tempFile.jpg"
let imageUrl = URL(fileURLWithPath: filePath)

// And then
PHPhotoLibrary.shared().performChanges(
  {
    let assetRequest = PHAssetChangeRequest.creationRequestForAssetFromImage(
      atFileURL: imageUrl)

    [...]

You can also get other cryptic errors. For instance, 3300 means changeNotSupported and can occur if you try to add an asset to special albums such as "Recents" (which identifies as PHAssetCollectionType.smartAlbum + PHAssetCollectionSubtype.smartAlbumUserLibrary).

Upvotes: 0

BadmintonCat
BadmintonCat

Reputation: 9586

Solved it! The above code works as intended. The problem was that file types were mixed up between some video and image files due to file order and filenames provided from server. So sometimes a video might have received an image file type extension and vice versa. This seems to confuse the PHPhotoLibrary creationRequestForAssetFromImage API.

Upvotes: 0

Related Questions