Swift how to modify exif info in images taken from mobile camera

10,263

Solution 1

Yes! Finally I made a trick to modify the EXIF info. At first, you can get EXIF info from info[UIImagePickerControllerMediaMetadata] and NSData without EXIF from picked UIImage by UIImageJPEGRepresentation. Then, you can create a new NSDictionary with modified EXIF info. After that, call my function in the following, you can get image NSData with modified EXIF!

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! ")

}

Solution 2

using and mergind some info from other posts, I approached problem using Dictionary in Swift. I used it in captureOutput of AVFounfation callback for AVCapturePhoto:

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
    }

}

After that "metadataAttachments" is used to build final image (using CGImageDestinationAddImage in my case)

It seems to work (tried in a project build with Swift 4.0)

Hope it can help!

Solution 3

Swift 5 Version of the accepted answer -

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! ")

}
Share:
10,263

Related videos on Youtube

SuperBi
Author by

SuperBi

Updated on September 15, 2022

Comments

  • SuperBi
    SuperBi over 1 year

    I use UIImagePickerController to pick images in my iOS App and I know exif info can be got by info[UIImagePickerControllerMediaMetadata]. But when I upload my image to my server by UIImage, most of exif info has been striped. I wonder whether I can add exif info to my image in Http request(image uploaded as jpg after that). If not, how should I solve this problem? I wanna change Make, Model attributes(in other words, what device was used to take this picture)

    Below are my code snippets:

    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!)
        }
    }
    
    • jonmecer
      jonmecer almost 8 years
      Any updates on this? I too can't find a simple way of doing this.
  • Maury Markowitz
    Maury Markowitz about 7 years
    I tried this method, but my rawMetadata is nil. Does this work on image data from AVCapturePhotoOutput?
  • Subhojit Mandal
    Subhojit Mandal about 7 years
  • hamedazhar
    hamedazhar over 5 years
    While printing, its all coming perfectly. But while retrieving the image after it is saved, the new metadata is not showing. Pls help! if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil) { let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) if let dict = imageProperties as? [String: Any] { print(dict) } }
  • AD Progress
    AD Progress about 3 years
    @DeviOS I have the same problem and I am trying to narrow it down now. I believe that the file itself is being saved without the EXIF itself it somehow gets lost somewhere
  • Madhav Thakker
    Madhav Thakker about 3 years
    @ADProgress did you track it down why the metadata is not being retrieved after saving the image?
  • AD Progress
    AD Progress about 3 years
    @MadhavThakker Yes. What it was at least in my code. When saving the file I was converting the data to jpegData and that created a brand new Data container without the EXIF information. But the data was already a jpegData so I only needed to save it rather than recreating it.
  • Madhav Thakker
    Madhav Thakker almost 3 years
    How to use metadataAttachments to build final image?