【问题标题】:Swift: Custom camera save modified metadata with imageSwift:自定义相机将修改后的元数据与图像一起保存
【发布时间】:2016-11-03 02:17:09
【问题描述】:

我正在尝试将图像样本缓冲区中的一些元数据与图像一起保存。

我需要:

  • 将图像旋转到元数据中的方向
  • 从元数据中删除方向
  • 将拍摄日期保存到元数据中
  • 将该图像与元数据一起保存到文档目录中

我尝试从数据中创建 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)
    }
}

这是我保存图像的代码。

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)
}

【问题讨论】:

    标签: ios swift swift2


    【解决方案1】:

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

    这是我的最终代码:

    如您所见,我在 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)
        }
    }
    

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

    很多守卫语句,我讨厌,但它比强制展开要好。

    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)
    }
    

    这是我更新的save 方法(与我写这个问题时使用的不完全相同,因为我已经更新到 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])
    }
    

    【讨论】:

    • 我假设我可以从NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first! + "name.png" 获得filePath。但是,fileService 是什么? fileService 是自定义类吗?
    • @Carlos.V fileService 是我为包装NSFileManager 而制作的自定义类,让我的生活更轻松。
    • 我能够读取实际元数据并对其进行修改,但无法将新元数据写入文件。您能否解释一下.save() 函数的工作原理?我尝试使用fileMan.createFileAtPath(filePath, contents: finalData, attributes: metadata as! [String : AnyObject] ) 但不保存元数据,它是用于其他属性的。也许,您使用的是PHPhotoLibrary?我想尝试这种方式,但我不确定它是否能 100% 起作用。
    • @Carlos.V 我已经更新了我的答案以包含我用于保存的代码。
    • 对于那些想要使用这段代码的人来说,记得import ImageIO这样你就可以访问所有的CGImageSource方法。
    【解决方案2】:

    我对上面的代码做了一个大大简化的版本。它确实制作了一个图像文件,但正如 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)
    }
    

    【讨论】:

    • 它对我有用。我正在获取生成的文件,将其上传到服务器,并且它具有新的元数据。所以这绝对是可能的。
    • 您好,如何向此函数添加元数据?另外,如果我想将照片保存到照片库中,我该怎么做呢?
    • 制作字典并传入。只要确保字典中的键是Apple支持的键,否则它们看起来像是被保存了,但最终不会出现在实际文件中保存时。
    • 我已经更新了data,但是当我将数据转换回image source 时,它会丢失数据。我认为这可能是因为UIImage 是对image source贫乏 阅读。如何确保 UIImage 拥有所有 Exif 数据。或者至少是我添加的键/值(UserComment)。 @SeanRobinson159
    • @SeanRobinson159 - 在我的实验中,我能够检索任何键/值,只要它是 Apple 自己的 EXIF 代码“理解”的键/值。因此,如果您输入自定义键,它们就会消失,但如果您使用 Apple 使用的一个(用于其他目的),它似乎可以工作。这是限制性的,但也许仍然有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-24
    • 2016-02-21
    • 1970-01-01
    • 2012-12-07
    • 1970-01-01
    相关资源
    最近更新 更多