2016-06-30 25 views
6

Próbuję zapisać NIEKTÓRE z metadanych z bufora próbki obrazu wraz z obrazem.Swift: Niestandardowe metadane zapisywania kamery z obrazem

muszę:

  • Obracanie obrazu w orientacji z metadanych
  • Usuń orientację z metadanych
  • zapisać daty pobranej z metadanych
  • Zapisz tego obrazu z metadanych do katalogu dokumentów:

Próbowałem utworzyć UIImage z danych, ale to usuwa metadane. Próbowałem użyć CIImage z danych, które zachowuje metadane, ale nie mogę go obrócić, a następnie zapisać go do pliku.

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

Oto mój kod do zapisania obrazu.

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

Odpowiedz

7

Skończyłem, zastanawiając się, jak wszystko, aby działać tak, jak było to potrzebne. To, co pomogło mi najbardziej, to odkrycie, że CFDictionary może być rzutowany jako NSMutableDictionary.

Oto mój ostatni kod:

Jak widać ja dodać obiekt do słownika EXIF ​​daty zdigitalizowanych i zmienił wartość orientacyjną.

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

A mój ostateczny kod do zapisu obrazu z metadanych:

Wiele stwierdzeń straży, czego nienawidzę, ale to lepsze niż życie 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) 
} 

Tu jest mój zaktualizowany save metoda (nie dokładnie to samo, że używałem kiedy pisałem to pytanie, ponieważ mam zaktualizowany do Swift 2.3, ale idea jest taka sama):

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]) 
} 
+0

Jestem zakładając, że mogę dostać 'filePath' od' NSSearchPathForDirectoriesInDomains (.DocumentDirectory, .UserDomainMask, true) .first! + "name.png" '. Ale czym jest "fileService"? Czy 'fileService' jest niestandardową klasą? –

+1

@ Carlos.V 'fileService' jest niestandardową klasą, którą stworzyłem aby owinąć' NSFileManager' i ułatwić mi życie. – SeanRobinson159

+0

Jestem w stanie odczytać rzeczywiste metadane i zmodyfikować je, ale nie mogę zapisać nowej meta do pliku. Czy możesz wyjaśnić nieco, w jaki sposób działa funkcja '.save()', spróbowałem użyć 'fileMan.createFileAtPath (filePath, zawartość: finalData, atrybuty: metadane jako! [String: AnyObject])' ale nie zapisuje metadane, dotyczy innych atrybutów. Być może używasz 'PHPhotoLibrary'? Myślę, że próbuję w ten sposób, ale nie jestem pewien, czy to zadziała w 100%. –

0

Zrobiłem znacznie uproszczoną wersję powyższego kodu. Tworzy plik obrazu, ale, jak zauważył Carlos, w pliku nie ma żadnych niestandardowych metadanych, gdy ładujesz go ponownie. Według innych wątków może to być po prostu niemożliwe.

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

To działa dla mnie. Pobieram przygotowany plik, przesyłając go na serwer i ma on nowe metadane. Więc zdecydowanie jest to możliwe. – SeanRobinson159

+0

Witam, jak dodać metadane do tej funkcji? Ponadto, jeśli chcę zapisać zdjęcia w bibliotece zdjęć, to jak bym to zrobił? –

+0

Utwórz słownik i przekaż go. Upewnij się, że klucze w słowniku są tymi, które obsługuje Apple, w przeciwnym razie będą wyglądały, jakby były zapisywane, ale nie zostaną zapisane w rzeczywistym pliku podczas zapisywania. –