Swift:自定义相机使用图像保存已修改的元数据

时间:2021-09-25 16:50:00

I am trying to save SOME of the metadata from an image sample buffer along with the image.

我试图保存图像样本缓冲区中的一些元数据以及图像。

I need to:

我需要:

  • Rotate the image to the orientation from the metadata
  • 将图像旋转到元数据的方向
  • Remove orientation from the metadata
  • 从元数据中删除方向
  • Save the date taken to the metadata
  • 保存所采用的日期到元数据
  • Save that image with the metadata to the documents directory
  • 将带有元数据的图像保存到文档目录中

I have tried creating a UIImage from the data, but that strips out the metadata. I have tried using a CIImage from the data, which keeps the metadata, but I can't rotate it then save it to a file.

我试过从数据创建一个UIImage,但是删除了元数据。我已经尝试使用数据中的CIImage来保存元数据,但是我无法将其旋转然后将其保存到文件中。

private func snapPhoto(success: (UIImage, CFMutableDictionary) -> Void, errorMessage: String -> Void) {
    guard !self.stillImageOutput.capturingStillImage,
        let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo) else { return }

    videoConnection.fixVideoOrientation()

    stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) {
        (imageDataSampleBuffer, error) -> Void in
        guard imageDataSampleBuffer != nil && error == nil else {
            errorMessage("Couldn't snap photo")
            return
        }

        let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

        let metadata = CMCopyDictionaryOfAttachments(nil, imageDataSampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
        let metadataMutable = CFDictionaryCreateMutableCopy(nil, 0, metadata)

        let utcDate = "\(NSDate())"
        let cfUTCDate = CFStringCreateCopy(nil, utcDate)
        CFDictionarySetValue(metadataMutable!, unsafeAddressOf(kCGImagePropertyGPSDateStamp), unsafeAddressOf(cfUTCDate))

        guard let image = UIImage(data: data)?.fixOrientation() else { return }
        CFDictionarySetValue(metadataMutable, unsafeAddressOf(kCGImagePropertyOrientation), unsafeAddressOf(1))

        success(image, metadataMutable)
    }
}

Here is my code for saving the image.

这是我保存图像的代码。

func saveImageAsJpg(image: UIImage, metadata: CFMutableDictionary) {
    // Add metadata to image
    guard let jpgData = UIImageJPEGRepresentation(image, 1) else { return }
    jpgData.writeToFile("\(self.documentsDirectory)/image1.jpg", atomically: true)
}

2 个解决方案

#1


10  

I ended up figuring out how to get everything to work the way I needed it to. The thing that helped me the most was finding out that a CFDictionary can be cast as a NSMutableDictionary.

我最终弄清楚如何让一切按照我需要的方式工作。对我帮助最大的事情是发现CFDictionary可以作为NSMutableDictionary投射。

Here is my final code:

这是我的最终代码:

As you can see I add a property to the EXIF dictionary for the date digitized, and changed the orientation value.

如您所见,我在EXIF词典中为数字化日期添加了一个属性,并更改了方向值。

private func snapPhoto(success: (UIImage, NSMutableDictionary) -> Void, errorMessage: String -> Void) {
    guard !self.stillImageOutput.capturingStillImage,
        let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo) else { return }

    videoConnection.fixVideoOrientation()

    stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) {
        (imageDataSampleBuffer, error) -> Void in
        guard imageDataSampleBuffer != nil && error == nil else {
            errorMessage("Couldn't snap photo")
            return
        }

        let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

        let rawMetadata = CMCopyDictionaryOfAttachments(nil, imageDataSampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
        let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary

        let exifData = metadata.valueForKey(kCGImagePropertyExifDictionary as String) as? NSMutableDictionary
        exifData?.setValue(NSDate().toString("yyyy:MM:dd HH:mm:ss"), forKey: kCGImagePropertyExifDateTimeDigitized as String)

        metadata.setValue(exifData, forKey: kCGImagePropertyExifDictionary as String)
        metadata.setValue(1, forKey: kCGImagePropertyOrientation as String)

        guard let image = UIImage(data: data)?.fixOrientation() else {
            errorMessage("Couldn't create image")
            return
        }

        success(image, metadata)
    }
}

And my final code for saving the image with the metadata:

以及使用元数据保存图像的最终代码:

Lots of guard statements, which I hate, but it is better than force unwrapping.

很多防守声明,我讨厌,但它比强行解缠更好。

func saveImage(withMetadata image: UIImage, metadata: NSMutableDictionary) {
    let filePath = "\(self.documentsPath)/image1.jpg"

    guard let jpgData = UIImageJPEGRepresentation(image, 1) else { return }

    // Add metadata to jpgData
    guard let source = CGImageSourceCreateWithData(jpgData, nil),
        let uniformTypeIdentifier = CGImageSourceGetType(source) else { return }
    let finalData = NSMutableData(data: jpgData)
    guard let destination = CGImageDestinationCreateWithData(finalData, uniformTypeIdentifier, 1, nil) else { return }
    CGImageDestinationAddImageFromSource(destination, source, 0, metadata)
    guard CGImageDestinationFinalize(destination) else { return }

    // Save image that now has metadata
    self.fileService.save(filePath, data: finalData)
}

Here is my updated save method (Not the exact same that I was using when I wrote this question, since I have updated to Swift 2.3, but the concept is the same):

这是我更新的保存方法(与我在编写此问题时使用的完全相同,因为我已更新到Swift 2.3,但概念是相同的):

public func save(fileAt path: NSURL, with data: NSData) throws -> Bool {
    guard let pathString = path.absoluteString else { return false }
    let directory = (pathString as NSString).stringByDeletingLastPathComponent

    if !self.fileManager.fileExistsAtPath(directory) {
        try self.makeDirectory(at: NSURL(string: directory)!)
    }

    if self.fileManager.fileExistsAtPath(pathString) {
        try self.delete(fileAt: path)
    }

    return self.fileManager.createFileAtPath(pathString, contents: data, attributes: [NSFileProtectionKey: NSFileProtectionComplete])
}

#2


0  

I made a greatly simplified version of the code above. It does make an image file, but as Carlos has noted, no custom metadata is in the file when you load it up again. According to other threads, this simply may not be possible.

我制作了上面代码的大大简化版本。它确实生成了一个图像文件,但正如Carlos所说,当你再次加载它时,文件中没有自定义元数据。根据其他线程,这可能是不可能的。

func saveImage(_ image: UIImage, withMetadata metadata: NSMutableDictionary, atPath path: URL) -> Bool {
    guard let jpgData = UIImageJPEGRepresentation(image, 1) else {
        return false
    }
    // make an image source
    guard let source = CGImageSourceCreateWithData(jpgData as CFData, nil), let uniformTypeIdentifier = CGImageSourceGetType(source) else {
        return false
    }
    // make an image destination pointing to the file we want to write
    guard let destination = CGImageDestinationCreateWithURL(path as CFURL, uniformTypeIdentifier, 1, nil) else {
        return false
    }

    // add the source image to the destination, along with the metadata
    CGImageDestinationAddImageFromSource(destination, source, 0, metadata)

    // and write it out
    return CGImageDestinationFinalize(destination)
}

#1


10  

I ended up figuring out how to get everything to work the way I needed it to. The thing that helped me the most was finding out that a CFDictionary can be cast as a NSMutableDictionary.

我最终弄清楚如何让一切按照我需要的方式工作。对我帮助最大的事情是发现CFDictionary可以作为NSMutableDictionary投射。

Here is my final code:

这是我的最终代码:

As you can see I add a property to the EXIF dictionary for the date digitized, and changed the orientation value.

如您所见,我在EXIF词典中为数字化日期添加了一个属性,并更改了方向值。

private func snapPhoto(success: (UIImage, NSMutableDictionary) -> Void, errorMessage: String -> Void) {
    guard !self.stillImageOutput.capturingStillImage,
        let videoConnection = stillImageOutput.connectionWithMediaType(AVMediaTypeVideo) else { return }

    videoConnection.fixVideoOrientation()

    stillImageOutput.captureStillImageAsynchronouslyFromConnection(videoConnection) {
        (imageDataSampleBuffer, error) -> Void in
        guard imageDataSampleBuffer != nil && error == nil else {
            errorMessage("Couldn't snap photo")
            return
        }

        let data = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(imageDataSampleBuffer)

        let rawMetadata = CMCopyDictionaryOfAttachments(nil, imageDataSampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
        let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary

        let exifData = metadata.valueForKey(kCGImagePropertyExifDictionary as String) as? NSMutableDictionary
        exifData?.setValue(NSDate().toString("yyyy:MM:dd HH:mm:ss"), forKey: kCGImagePropertyExifDateTimeDigitized as String)

        metadata.setValue(exifData, forKey: kCGImagePropertyExifDictionary as String)
        metadata.setValue(1, forKey: kCGImagePropertyOrientation as String)

        guard let image = UIImage(data: data)?.fixOrientation() else {
            errorMessage("Couldn't create image")
            return
        }

        success(image, metadata)
    }
}

And my final code for saving the image with the metadata:

以及使用元数据保存图像的最终代码:

Lots of guard statements, which I hate, but it is better than force unwrapping.

很多防守声明,我讨厌,但它比强行解缠更好。

func saveImage(withMetadata image: UIImage, metadata: NSMutableDictionary) {
    let filePath = "\(self.documentsPath)/image1.jpg"

    guard let jpgData = UIImageJPEGRepresentation(image, 1) else { return }

    // Add metadata to jpgData
    guard let source = CGImageSourceCreateWithData(jpgData, nil),
        let uniformTypeIdentifier = CGImageSourceGetType(source) else { return }
    let finalData = NSMutableData(data: jpgData)
    guard let destination = CGImageDestinationCreateWithData(finalData, uniformTypeIdentifier, 1, nil) else { return }
    CGImageDestinationAddImageFromSource(destination, source, 0, metadata)
    guard CGImageDestinationFinalize(destination) else { return }

    // Save image that now has metadata
    self.fileService.save(filePath, data: finalData)
}

Here is my updated save method (Not the exact same that I was using when I wrote this question, since I have updated to Swift 2.3, but the concept is the same):

这是我更新的保存方法(与我在编写此问题时使用的完全相同,因为我已更新到Swift 2.3,但概念是相同的):

public func save(fileAt path: NSURL, with data: NSData) throws -> Bool {
    guard let pathString = path.absoluteString else { return false }
    let directory = (pathString as NSString).stringByDeletingLastPathComponent

    if !self.fileManager.fileExistsAtPath(directory) {
        try self.makeDirectory(at: NSURL(string: directory)!)
    }

    if self.fileManager.fileExistsAtPath(pathString) {
        try self.delete(fileAt: path)
    }

    return self.fileManager.createFileAtPath(pathString, contents: data, attributes: [NSFileProtectionKey: NSFileProtectionComplete])
}

#2


0  

I made a greatly simplified version of the code above. It does make an image file, but as Carlos has noted, no custom metadata is in the file when you load it up again. According to other threads, this simply may not be possible.

我制作了上面代码的大大简化版本。它确实生成了一个图像文件,但正如Carlos所说,当你再次加载它时,文件中没有自定义元数据。根据其他线程,这可能是不可能的。

func saveImage(_ image: UIImage, withMetadata metadata: NSMutableDictionary, atPath path: URL) -> Bool {
    guard let jpgData = UIImageJPEGRepresentation(image, 1) else {
        return false
    }
    // make an image source
    guard let source = CGImageSourceCreateWithData(jpgData as CFData, nil), let uniformTypeIdentifier = CGImageSourceGetType(source) else {
        return false
    }
    // make an image destination pointing to the file we want to write
    guard let destination = CGImageDestinationCreateWithURL(path as CFURL, uniformTypeIdentifier, 1, nil) else {
        return false
    }

    // add the source image to the destination, along with the metadata
    CGImageDestinationAddImageFromSource(destination, source, 0, metadata)

    // and write it out
    return CGImageDestinationFinalize(destination)
}