Swift, как изменить информацию exif на изображениях, снятых с мобильной камеры

Я использую UIImagePickerController для выбора изображений в своем приложении для iOS и знаю, что информацию exif можно получить с помощью info[UIImagePickerControllerMediaMetadata]. Но когда я загружаю свое изображение на свой сервер UIImage, большая часть информации exif была полосатой. Интересно, могу ли я добавить информацию exif к моему изображению в запросе Http (после этого изображение загружается как 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!)
    }
}

person SuperBi    schedule 23.06.2016    source источник
comment
Есть новости по этому поводу? Я тоже не могу найти простой способ сделать это.   -  person jonmecer    schedule 11.07.2016


Ответы (4)


Да! Наконец, я сделал трюк, чтобы изменить информацию EXIF. Во-первых, вы можете получить информацию EXIF ​​из информации [UIImagePickerControllerMediaMetadata] и NSData без EXIF ​​из выбранного UIImage с помощью UIImageJPEGRepresentation. Затем вы можете создать новый NSDictionary с измененной информацией EXIF. После этого, вызвав мою функцию в дальнейшем, вы сможете получить изображение NSData с модифицированным 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! ")

}
person SuperBi    schedule 13.07.2016
comment
При печати все идет отлично. Но при извлечении изображения после его сохранения новые метаданные не отображаются. Пожалуйста, помогите! if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil) { let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) if let dict = imageProperties as? [String: Any] { print(dict) } } - person softdev; 20.10.2018
comment
@DeviOS У меня та же проблема, и я сейчас пытаюсь ее сузить. Я полагаю, что сам файл сохраняется без самого EXIF ​​он как-то где-то теряется - person AD Progress; 22.03.2021
comment
@ADProgress, вы выяснили, почему метаданные не извлекаются после сохранения изображения? - person Madhav Thakker; 25.04.2021
comment
@MadhavThakker Да. Что это было по крайней мере в моем коде. При сохранении файла я преобразовывал данные в jpegData, и это создало новый контейнер данных без информации EXIF. Но данные уже были jpegData, поэтому мне нужно было только сохранить их, а не воссоздавать. - person AD Progress; 25.04.2021

используя и объединяя некоторую информацию из других сообщений, я подошел к проблеме, используя словарь в Swift. Я использовал его в обратном вызове CaptureOutput AVFounfation для 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
    }

}

После этого «metadataAttachments» используется для создания окончательного изображения (в моем случае с использованием CGImageDestinationAddImage)

Кажется, работает (пробовал в сборке проекта с Swift 4.0)

Надеюсь, это может помочь!

person Seamusx    schedule 26.01.2018
comment
Как использовать metadataAttachments для создания окончательного изображения? - person Madhav Thakker; 25.05.2021

СВИФТ 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)
            }
        }
    }
person Naloiko Eugene    schedule 01.11.2016
comment
Я попробовал этот метод, но мои необработанные метаданные равны нулю. Это работает с данными изображения из AVCapturePhotoOutput? - person Maury Markowitz; 02.03.2017
comment
stackoverflow.com/questions/43489623 / можете ли вы и это также - person Subhojit Mandal; 19.04.2017

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

}
person Madhav Thakker    schedule 24.04.2021