Swift 如何修改手机相机拍摄的图像中的 exif 信息
Swift how to modify exif info in images taken from mobile camera
我使用 UIImagePickerController
在我的 iOS 应用程序中选择图像,我知道可以通过 info[UIImagePickerControllerMediaMetadata]
获取 exif 信息。但是当我通过 UIImage
将我的图像上传到我的服务器时,大部分 exif 信息都被条带化了。我想知道我是否可以在 Http 请求中将 exif 信息添加到我的图像中(之后图像以 jpg 格式上传)。如果没有,我应该如何解决这个问题?我想更改 Make、Model 属性(换句话说,就是用什么设备拍的这张照片)
下面是我的代码片段:
func Tapped() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = UIImagePickerControllerSourceType.Camera
myPickerController.allowsEditing = false
self.presentViewController(myPickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
myImageView.image = image
UIImageWriteToSavedPhotosAlbum(image!, self, #selector(ViewController.image(_:didFinishSavingWithError:contextInfo:)), nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
func myImageUploadRequest()
{
let myUrl = NSURL(string: "http://XXXXXX/Uploadfile")
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
let param = [
"userId" : "7"
]
let boundary = generateBoundaryString()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let imageData = UIImageJPEGRepresentation(myImageView.image!, 1)
if(imageData == nil) { return; }
request.HTTPBody = createBodyWithParameters(param, filePathKey: "file", imageDataKey: imageData!, boundary: boundary)
myActivityIndicator.startAnimating()
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
// You can print out response object
print("******* response = \(response)")
// Print out response body
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("****** response data = \(responseString!)")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
}catch{
print(error)
}
dispatch_async(dispatch_get_main_queue(),{
self.myActivityIndicator.stopAnimating()
self.myImageView.image = nil
})
}
task.resume()
}
func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, imageDataKey: NSData, boundary: String) -> NSData {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
let filename = "test.jpg"
let mimetype = "image/jpg"
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.appendData(imageDataKey)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
return body
}
func generateBoundaryString() -> String {
return "Boundary-\(NSUUID().UUIDString)"
}
extension NSMutableData {
func appendString(string: String) {
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
appendData(data!)
}
}
是的!最后我做了一个修改 EXIF 信息的技巧。首先,您可以从 info[UIImagePickerControllerMediaMetadata] 和 NSData 获取 EXIF 信息,而无需从 UIImageJPEGRepresentation 选取的 UIImage 获取 EXIF。然后,您可以使用修改后的 EXIF 信息创建一个新的 NSDictionary。之后在下面调用我的函数,就可以得到修改过EXIF的图片NSData了!
func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (data: NSData, path: NSURL) -> Void) {
let imageRef: CGImageSourceRef = CGImageSourceCreateWithData((data as CFDataRef), nil)!
let uti: CFString = CGImageSourceGetType(imageRef)!
let dataWithEXIF: NSMutableData = NSMutableData(data: data)
let destination: CGImageDestinationRef = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableDataRef), uti, 1, nil)!
CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionaryRef))
CGImageDestinationFinalize(destination)
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let savePath: String = paths[0].stringByAppendingPathComponent("exif.jpg")
let manager: NSFileManager = NSFileManager.defaultManager()
manager.createFileAtPath(savePath, contents: dataWithEXIF, attributes: nil)
completion(data: dataWithEXIF,path: NSURL(string: savePath)!)
print("image with EXIF info converting to NSData: Done! Ready to upload! ")
}
SWIFT 3
如果您正在捕获视频并获取 CMSampleBuffer,则有一种方法可以更新 EXIF 元数据。在我的例子中,在 iOS9 中我没有得到 DateTimeOriginal,尽管在 iOS10 中 DataTimeOriginal 已经存在。因此我不得不添加一些额外的键值。
self.stillCameraOutput.captureStillImageAsynchronously(from: connectionVideo) { (sampleBuffer, err) in
if let err = err {
blockCompletion(nil, err as NSError?)
}
else {
if let sampleBuffer = sampleBuffer {
let rawMetadata = CMCopyDictionaryOfAttachments(nil, sampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary
let exifData = metadata.value(forKey: "{Exif}") as? NSMutableDictionary
print("EXIF DATA: \(exifData)")
if let dateTime = exifData?["DateTimeOriginal"] as? String {
print("DateTime exists \(dateTime)")
}
else {
exifData?.setValue(Date().exifDate(), forKey: "DateTimeOriginal")
}
if let dateTime = exifData?["DateTimeDigitized"] as? String {
print("DateTime exists \(dateTime)")
}
else {
exifData?.setValue(Date().exifDate(), forKey: "DateTimeDigitized")
}
metadata.setValue(exifData, forKey: "{Exif}")
CMSetAttachments(sampleBuffer, metadata as CFDictionary, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let rawMetadata2 = CMCopyDictionaryOfAttachments(nil, sampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let metadata2 = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata2) as NSMutableDictionary
let exifData2 = metadata2.value(forKey: "{Exif}") as? NSMutableDictionary
print("EXIF DATA: \(exifData2)")
if let dataImage = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer) {
blockCompletion(dataImage, nil)
}
else {
blockCompletion(nil, nil)
}
}
else {
blockCompletion(nil, nil)
}
}
}
使用和合并其他帖子中的一些信息,我在 Swift 中使用字典解决了问题。我在 AVCapturePhoto 的 AVFounfation 回调的 captureOutput 中使用了它:
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
//retrieve exif information
var photoFormatDescription: CMFormatDescription?
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, photoPixelBuffer, &photoFormatDescription)
var metadataAttachments: Dictionary = photo.metadata as Dictionary
if var exifData = metadataAttachments["{Exif}"] as? [String: Any] {
exifData[kCGImagePropertyExifUserComment as String] = "<whatever you want to write>"
metadataAttachments[kCGImagePropertyExifDictionary as String] = exifData
}
}
之后 "metadataAttachments" 用于构建最终图像(在我的例子中使用 CGImageDestinationAddImage)
它似乎有效(在使用 Swift 4.0 构建的项目中尝试过)
希望对您有所帮助!
Swift 5 接受答案的版本 -
func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (_ data: NSData, _ path: NSURL) -> Void) {
let imageRef: CGImageSource = CGImageSourceCreateWithData((data as CFData), nil)!
let uti: CFString = CGImageSourceGetType(imageRef)!
let dataWithEXIF: NSMutableData = NSMutableData(data: data as Data)
let destination: CGImageDestination = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableData), uti, 1, nil)!
CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionary))
CGImageDestinationFinalize(destination)
let paths: [String] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let savePath: String = paths[0].appending("exif.jpg")
let manager: FileManager = FileManager.default
manager.createFile(atPath: savePath, contents: dataWithEXIF as Data, attributes: nil)
completion(dataWithEXIF,NSURL(string: savePath)!)
print("image with EXIF info converting to NSData: Done! Ready to upload! ")
}
我使用 UIImagePickerController
在我的 iOS 应用程序中选择图像,我知道可以通过 info[UIImagePickerControllerMediaMetadata]
获取 exif 信息。但是当我通过 UIImage
将我的图像上传到我的服务器时,大部分 exif 信息都被条带化了。我想知道我是否可以在 Http 请求中将 exif 信息添加到我的图像中(之后图像以 jpg 格式上传)。如果没有,我应该如何解决这个问题?我想更改 Make、Model 属性(换句话说,就是用什么设备拍的这张照片)
下面是我的代码片段:
func Tapped() {
let myPickerController = UIImagePickerController()
myPickerController.delegate = self
myPickerController.sourceType = UIImagePickerControllerSourceType.Camera
myPickerController.allowsEditing = false
self.presentViewController(myPickerController, animated: true, completion: nil)
}
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
let image = info[UIImagePickerControllerOriginalImage] as? UIImage
myImageView.image = image
UIImageWriteToSavedPhotosAlbum(image!, self, #selector(ViewController.image(_:didFinishSavingWithError:contextInfo:)), nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
func myImageUploadRequest()
{
let myUrl = NSURL(string: "http://XXXXXX/Uploadfile")
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
let param = [
"userId" : "7"
]
let boundary = generateBoundaryString()
request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
let imageData = UIImageJPEGRepresentation(myImageView.image!, 1)
if(imageData == nil) { return; }
request.HTTPBody = createBodyWithParameters(param, filePathKey: "file", imageDataKey: imageData!, boundary: boundary)
myActivityIndicator.startAnimating()
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
print("error=\(error)")
return
}
// You can print out response object
print("******* response = \(response)")
// Print out response body
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("****** response data = \(responseString!)")
do{
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .MutableContainers) as? NSDictionary
}catch{
print(error)
}
dispatch_async(dispatch_get_main_queue(),{
self.myActivityIndicator.stopAnimating()
self.myImageView.image = nil
})
}
task.resume()
}
func createBodyWithParameters(parameters: [String: String]?, filePathKey: String?, imageDataKey: NSData, boundary: String) -> NSData {
let body = NSMutableData();
if parameters != nil {
for (key, value) in parameters! {
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
body.appendString("\(value)\r\n")
}
}
let filename = "test.jpg"
let mimetype = "image/jpg"
body.appendString("--\(boundary)\r\n")
body.appendString("Content-Disposition: form-data; name=\"\(filePathKey!)\"; filename=\"\(filename)\"\r\n")
body.appendString("Content-Type: \(mimetype)\r\n\r\n")
body.appendData(imageDataKey)
body.appendString("\r\n")
body.appendString("--\(boundary)--\r\n")
return body
}
func generateBoundaryString() -> String {
return "Boundary-\(NSUUID().UUIDString)"
}
extension NSMutableData {
func appendString(string: String) {
let data = string.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
appendData(data!)
}
}
是的!最后我做了一个修改 EXIF 信息的技巧。首先,您可以从 info[UIImagePickerControllerMediaMetadata] 和 NSData 获取 EXIF 信息,而无需从 UIImageJPEGRepresentation 选取的 UIImage 获取 EXIF。然后,您可以使用修改后的 EXIF 信息创建一个新的 NSDictionary。之后在下面调用我的函数,就可以得到修改过EXIF的图片NSData了!
func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (data: NSData, path: NSURL) -> Void) {
let imageRef: CGImageSourceRef = CGImageSourceCreateWithData((data as CFDataRef), nil)!
let uti: CFString = CGImageSourceGetType(imageRef)!
let dataWithEXIF: NSMutableData = NSMutableData(data: data)
let destination: CGImageDestinationRef = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableDataRef), uti, 1, nil)!
CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionaryRef))
CGImageDestinationFinalize(destination)
var paths: [AnyObject] = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)
let savePath: String = paths[0].stringByAppendingPathComponent("exif.jpg")
let manager: NSFileManager = NSFileManager.defaultManager()
manager.createFileAtPath(savePath, contents: dataWithEXIF, attributes: nil)
completion(data: dataWithEXIF,path: NSURL(string: savePath)!)
print("image with EXIF info converting to NSData: Done! Ready to upload! ")
}
SWIFT 3
如果您正在捕获视频并获取 CMSampleBuffer,则有一种方法可以更新 EXIF 元数据。在我的例子中,在 iOS9 中我没有得到 DateTimeOriginal,尽管在 iOS10 中 DataTimeOriginal 已经存在。因此我不得不添加一些额外的键值。
self.stillCameraOutput.captureStillImageAsynchronously(from: connectionVideo) { (sampleBuffer, err) in
if let err = err {
blockCompletion(nil, err as NSError?)
}
else {
if let sampleBuffer = sampleBuffer {
let rawMetadata = CMCopyDictionaryOfAttachments(nil, sampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let metadata = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata) as NSMutableDictionary
let exifData = metadata.value(forKey: "{Exif}") as? NSMutableDictionary
print("EXIF DATA: \(exifData)")
if let dateTime = exifData?["DateTimeOriginal"] as? String {
print("DateTime exists \(dateTime)")
}
else {
exifData?.setValue(Date().exifDate(), forKey: "DateTimeOriginal")
}
if let dateTime = exifData?["DateTimeDigitized"] as? String {
print("DateTime exists \(dateTime)")
}
else {
exifData?.setValue(Date().exifDate(), forKey: "DateTimeDigitized")
}
metadata.setValue(exifData, forKey: "{Exif}")
CMSetAttachments(sampleBuffer, metadata as CFDictionary, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let rawMetadata2 = CMCopyDictionaryOfAttachments(nil, sampleBuffer, CMAttachmentMode(kCMAttachmentMode_ShouldPropagate))
let metadata2 = CFDictionaryCreateMutableCopy(nil, 0, rawMetadata2) as NSMutableDictionary
let exifData2 = metadata2.value(forKey: "{Exif}") as? NSMutableDictionary
print("EXIF DATA: \(exifData2)")
if let dataImage = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer) {
blockCompletion(dataImage, nil)
}
else {
blockCompletion(nil, nil)
}
}
else {
blockCompletion(nil, nil)
}
}
}
使用和合并其他帖子中的一些信息,我在 Swift 中使用字典解决了问题。我在 AVCapturePhoto 的 AVFounfation 回调的 captureOutput 中使用了它:
func photoOutput(_ output: AVCapturePhotoOutput,
didFinishProcessingPhoto photo: AVCapturePhoto,
error: Error?) {
//retrieve exif information
var photoFormatDescription: CMFormatDescription?
CMVideoFormatDescriptionCreateForImageBuffer(kCFAllocatorDefault, photoPixelBuffer, &photoFormatDescription)
var metadataAttachments: Dictionary = photo.metadata as Dictionary
if var exifData = metadataAttachments["{Exif}"] as? [String: Any] {
exifData[kCGImagePropertyExifUserComment as String] = "<whatever you want to write>"
metadataAttachments[kCGImagePropertyExifDictionary as String] = exifData
}
}
之后 "metadataAttachments" 用于构建最终图像(在我的例子中使用 CGImageDestinationAddImage)
它似乎有效(在使用 Swift 4.0 构建的项目中尝试过)
希望对您有所帮助!
Swift 5 接受答案的版本 -
func saveImageWithImageData(data: NSData, properties: NSDictionary, completion: (_ data: NSData, _ path: NSURL) -> Void) {
let imageRef: CGImageSource = CGImageSourceCreateWithData((data as CFData), nil)!
let uti: CFString = CGImageSourceGetType(imageRef)!
let dataWithEXIF: NSMutableData = NSMutableData(data: data as Data)
let destination: CGImageDestination = CGImageDestinationCreateWithData((dataWithEXIF as CFMutableData), uti, 1, nil)!
CGImageDestinationAddImageFromSource(destination, imageRef, 0, (properties as CFDictionary))
CGImageDestinationFinalize(destination)
let paths: [String] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let savePath: String = paths[0].appending("exif.jpg")
let manager: FileManager = FileManager.default
manager.createFile(atPath: savePath, contents: dataWithEXIF as Data, attributes: nil)
completion(dataWithEXIF,NSURL(string: savePath)!)
print("image with EXIF info converting to NSData: Done! Ready to upload! ")
}