Applying metadata to image causes performChanges request to fail
Asked Answered
T

5

4

I am using PhotoKit to edit photos and I need to preserve the metadata from the original photo. To do so I save the metadata then provide it to the options parameter in CGImageDestinationAddImage. I am able to finalize it and write it to disk successfully, but when I call performChanges to commit the asset edit, it fails. If I instead provide nil for options it will succeed. What is going wrong here?

asset.requestContentEditingInputWithOptions(options) { (input: PHContentEditingInput!, _) -> Void in
    //get full image
    let url = input.fullSizeImageURL
    let inputImage = CIImage(contentsOfURL: url)

    //get orginal photo's metadata
    let originalImageData = NSData(contentsOfURL: url)!
    let imageSource = CGImageSourceCreateWithData(originalImageData, nil)
    let metadata: CFDictionaryRef = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)
    println(metadata) //prints all the metadata, yay!

    //do some processing on original photo here and create an output CIImage...

    //save to disk
    let dataRef = CFDataCreateMutable(nil, 0)
    let destination = CGImageDestinationCreateWithData(dataRef, CGImageSourceGetType(imageSource), 1, nil)
    let eaglContext = EAGLContext(API: .OpenGLES2)
    let ciContext = CIContext(EAGLContext: eaglContext)
    let cgImage = ContextStruct.ciContext!.createCGImage(outputPhoto, fromRect: outputPhoto.extent())
    CGImageDestinationAddImage(destination, cgImage, metadata) //metadata is problematic - replacing with nil causes it to work

    if CGImageDestinationFinalize(destination) {
        let contentEditingOutput = PHContentEditingOutput(contentEditingInput: input)
        contentEditingOutput.adjustmentData = "something"

        let imageData: NSData = dataRef
        let success = imageData.writeToURL(contentEditingOutput.renderedContentURL, options: .AtomicWrite, error: _)
        if success {
            PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
                let request = PHAssetChangeRequest(forAsset: asset)
                request.contentEditingOutput = contentEditingOutput
            }, completionHandler: { (success: Bool, error: NSError!) -> Void in
                if success == false { println('failed to commit image edit: \(error)') } //fails unless metadata is replaced with nil above
            })
        }
    }
})

The error is: Error Domain=NSCocoaErrorDomain Code=-1 "The operation couldn’t be completed. (Cocoa error -1.)

Tovatovar answered 1/2, 2015 at 20:23 Comment(8)
I have run into the same error. Is there an open bug with apple?Clop
@Clop Yes, haven't heard back from them on it. Please file your own report, and report back if you hear anything.Tovatovar
@Clop The proposed workaround is to use Obj-C to obtain the metadata and set it when adding the image but I couldn't get that to work either - same issue. If you attempt that and are successful do share your solution. :)Tovatovar
FYI: I am using a combination with no luck. First, applying the metadata to the imageData in objective C, and then running the performChanges block with contentEditingOutput in swift. This combo is also failing with the aforementioned error code (which is pretty useless for debugging purposes). As you mentioned before, this works fine for me when setting the metadata to nil.Clop
I may test this entirely in objective C, will report back with findings if/when I doClop
@Clop Ok, just fyi Apple is requesting bugs be filed both for doing this entirely in Swift and a combination of Swift with Obj-C, and of course only Obj-C if you can't get it to work with that either.Tovatovar
Good to know @Joey, will file relevant bugs for what I have testedClop
my all obj-c implementation also failed with the same error code. Will follow up with apple bugsClop
S
1

It seems that filling the adjustementData property of the PHContentEditingOutput object is mandatory in order to edit a photo.

Samsun answered 1/2, 2015 at 20:32 Comment(2)
I actually am setting adjustmentData, I just left that out in the code above so it would be more concise, but it is one line so I added it.Tovatovar
This resolved the "error code -1" for me, but I'm still not seeing the updated metadata. Will continue debugging.Salinas
D
1

Certain keys in the metadata seem to cause failure. This works:

NSArray *validMetadataKeys = @[
    (NSString *)kCGImagePropertyTIFFDictionary,
    (NSString *)kCGImagePropertyGIFDictionary,
    (NSString *)kCGImagePropertyJFIFDictionary,
    (NSString *)kCGImagePropertyExifDictionary,
    (NSString *)kCGImagePropertyPNGDictionary,
    (NSString *)kCGImagePropertyIPTCDictionary,
    (NSString *)kCGImagePropertyGPSDictionary,
    (NSString *)kCGImagePropertyRawDictionary,
    (NSString *)kCGImagePropertyCIFFDictionary,
    (NSString *)kCGImageProperty8BIMDictionary,
    (NSString *)kCGImagePropertyDNGDictionary,
    (NSString *)kCGImagePropertyExifAuxDictionary,
];

NSMutableDictionary *validMetadata = [NSMutableDictionary dictionary];
[metadata enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    if ([validMetadataKeys containsObject:key]) {
        validMetadata[key] = obj;
    }
}];

// your CGImage stuff

CGImageDestinationAddImageFromSource(destination, imgSource, 0, (__bridge CFDictionaryRef)validMetadata);
Desmonddesmoulins answered 9/3, 2015 at 2:48 Comment(1)
This still causes performChanges to fail, at least when using Swift, unless I've done something wrong in conversion.Tovatovar
E
1

I have exactly the same problem, and I have filed a radar rdar://21057247. I try to log a case using TSI but I have been told to file a radar instead.

Elevator answered 21/5, 2015 at 15:40 Comment(0)
H
0

// if the asset does not allow the type of change requested, these methods will raise an exception, call canPerformEditOperation: on the asset to determine if the type of edit operation is allowed.

  • (instancetype)changeRequestForAsset:(PHAsset *)asset;

I think it's because Apple don't want you to change the metaData.You can try this method below,maybe create a new one is allowed.

  • (instancetype)creationRequestForAssetFromImage:(UIImage *)image;
  • (instancetype)creationRequestForAssetFromImageAtFileURL:(NSURL *)fileURL;
  • (instancetype)creationRequestForAssetFromVideoAtFileURL:(NSURL *)fileURL;
Headlock answered 29/5, 2015 at 7:45 Comment(0)
D
0

We are supposed to write the data for the new image to the URL we obtain via

                let theExportURL = output.renderedContentURL

In my case i was doing this...

                let outputData = UIImagePNGRepresentation(bakedImage)

And at runtime i was getting the error

Error Domain=NSCocoaErrorDomain Code=-1 "The operation couldn’t be completed. (Cocoa error -1.)

But when I started exporting the data like this...

let outputData = UIImageJPEGRepresentation(bakedImage, 1)

It worked. This is documented here ... https://developer.apple.com/library/prerelease/ios/documentation/Photos/Reference/PHContentEditingOutput_Class/index.html

and this same question has been answered here... https://stackoverflow.com/a/28362235

Drivein answered 31/8, 2015 at 20:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.