2017-06-13 4 views
4

Dans mon application, je veux permettre à l'utilisateur de définir un StarRating de 0 à 5 pour toute image qu'il a dans sa photothèque. Ma recherche montre qu'il ya deux façons pour y parvenir:Modifier les métadonnées à partir de phAsset existant ne semble pas fonctionner

Save the exif metadata using the new PHPhotoLibrary

Swift: Custom camera save modified metadata with image

Writing a Photo with Metadata using Photokit

La plupart de ces réponses ont été la création d'une nouvelle photo. Mon extrait ressemble maintenant à ceci:

let options = PHContentEditingInputRequestOptions() 
options.isNetworkAccessAllowed = true 

self.requestContentEditingInput(with: options, completionHandler: { 
      (contentEditingInput, _) -> Void in 

    if contentEditingInput != nil { 

     if let url = contentEditingInput!.fullSizeImageURL { 
      if let nsurl = url as? NSURL { 
       if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) { 
        var imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? 
        if imageProperties != nil { 
         imageProperties![kCGImagePropertyIPTCStarRating] = rating as AnyObject 

         let imageData = NSMutableData(contentsOf: url) 
         let image = UIImage(contentsOfFile: url.path) 

         let destination = CGImageDestinationCreateWithData(imageData!, CGImageSourceGetType(imageSource)!, 1, nil) 

         CGImageDestinationAddImage(destination!, image!.cgImage!, imageProperties! as CFDictionary) 

         var contentEditingOutput : PHContentEditingOutput? = nil 

         if CGImageDestinationFinalize(destination!) { 
          let archievedData = NSKeyedArchiver.archivedData(withRootObject: rating) 
          let identifier = "com.example.starrating" 
          let adjustmentData = PHAdjustmentData(formatIdentifier: identifier, formatVersion: "1.0", data: archievedData) 

          contentEditingOutput = PHContentEditingOutput(contentEditingInput: contentEditingInput!) 
          contentEditingOutput!.adjustmentData = adjustmentData 
          if imageData!.write(to: contentEditingOutput!.renderedContentURL, atomically: true) { 
           PHPhotoLibrary.shared().performChanges({ 
            let request = PHAssetChangeRequest(for: self) 
            request.contentEditingOutput = contentEditingOutput 
           }, completionHandler: { 
            success, error in 
            if success && error == nil { 
             completion(true) 
            } else { 
             completion(false) 
            } 
           }) 
          } 
         } else { 
          completion(false) 
         } 

        } 
       } 
      } 
     } 
    } 
}) 

Maintenant, quand je veux lire les métadonnées du PHAsset Je demande à nouveau ContentEditingInput et procédez comme suit:

if let url = contentEditingInput!.fullSizeImageURL { 
    if let nsurl = url as? NSURL { 
     if let imageSource = CGImageSourceCreateWithURL(nsurl, nil) { 
      if let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary? { 

       if let starRating = imageProperties[kCGImagePropertyIPTCStarRating] as? Int { 
        rating = starRating 
       } 
      } 
     } 
    } 
} 

Mais je ne me ma note car il dit que la valeur de imageProperties[kCGImagePropertyIPTCStarRating] est nulle.

J'ai également essayé les exemples des Réponses que j'ai postées ci-dessus, mais j'ai toujours le même résultat.

J'espère que tout le monde sait, ce que je peux faire pour changer les métadonnées.

En outre, comment puis-je changer les métadonnées d'un PHAsset avec le MediaType .video? J'ai essayé d'atteindre cela à travers les objets AVAssetWriter et AVExportSession, mais dans les deux cas cela ne fonctionne pas. Voici ce que j'ai essayé pour les vidéos:

var exportSession = AVAssetExportSession(asset: asset!, presetName: AVAssetExportPresetPassthrough) 
exportSession!.outputURL = outputURL 
exportSession!.outputFileType = AVFileTypeQuickTimeMovie 
exportSession!.timeRange = CMTimeRange(start: start, duration: duration) 

var modifiedMetadata = asset!.metadata 

let metadataItem = AVMutableMetadataItem() 
metadataItem.keySpace = AVMetadataKeySpaceQuickTimeMetadata 
metadataItem.key = AVMetadataQuickTimeMetadataKeyRatingUser as NSCopying & NSObjectProtocol 
metadataItem.value = rating as NSCopying & NSObjectProtocol 

modifiedMetadata.append(metadataItem) 

exportSession!.metadata = modifiedMetadata 


exportSession!.exportAsynchronously(completionHandler: { 
    let status = exportSession?.status 
    let success = status == AVAssetExportSessionStatus.completed 
    if success { 
     do { 
      let sourceURL = urlAsset.url 
      let manager = FileManager.default 
      _ = try manager.removeItem(at: sourceURL) 
      _ = try manager.moveItem(at: outputURL, to: sourceURL) 
     } catch { 
      LogError("\(error)") 
      completion(false) 
     } 
    } else { 
     LogError("\(exportSession!.error!)") 
     completion(false) 
    } 
}) 
+0

Pour quelqu'un d'autre intéressé par cela, j'ai un ticket TSI ouvert pour cela donc probablement Apple sait comment y parvenir. –

+0

J'ai ouvert une nouvelle question pour les [Video Metadata] (https://stackoverflow.com/questions/44824936/modifying-metadata-from-phasset-with-mediatype-video-fails) –

Répondre

1

Désolé, ce n'est pas une réponse complète, mais il couvre une partie de votre question. J'ai remarqué que vous placez le StarRating au mauvais endroit. Vous devez le placer dans un dictionnaire IPTC. Les données de propriétés sont également stockées en tant que chaînes. Étant donné que vous avez les imageProperties vous pouvez ajouter le nombre d'étoiles comme suit et relire en utilisant les deux fonctions

func setIPTCStarRating(imageProperties : NSMutableDictionary, rating : Int) { 
    if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSMutableDictionary { 
     iptc[kCGImagePropertyIPTCStarRating] = String(rating) 
    } else { 
     let iptc = NSMutableDictionary() 
     iptc[kCGImagePropertyIPTCStarRating] = String(rating) 
     imageProperties[kCGImagePropertyIPTCDictionary] = iptc 
    } 
} 


func getIPTCStarRating(imageProperties : NSMutableDictionary) -> Int? { 
    if let iptc = imageProperties[kCGImagePropertyIPTCDictionary] as? NSDictionary { 
     if let starRating = iptc[kCGImagePropertyIPTCStarRating] as? String { 
      return Int(starRating) 
     } 
    } 
    return nil 
} 

Comme les imageProperties que vous obtenez de l'image ne sont pas mutable vous devez créer une copie mutable de ces propriétés suivantes d'abord avant que vous puissiez appeler les fonctions ci-dessus. Lorsque vous créez votre image pour enregistrer utiliser les propriétés mutables dans votre appel à CGImageDestinationAddImage()

if let mutableProperties = imageProperties.mutableCopy() as? NSMutableDictionary { 
    setIPTCStarRating(imageProperties:mutableProperties, rating:rating) 
} 

Un autre point que vous créez un UIImage inutile. Si vous utilisez CGImageDestinationAddImageFromSource() au lieu de CGImageDestinationAddImage(), vous pouvez utiliser l'imageSource que vous avez créé précédemment au lieu de charger les données d'image dans un UIImage.

+0

Je reçois *** Application de terminaison en raison de l'exception non interceptée 'NSInternalInconsistencyException', raison: '- [__ NSCFDictionary setObject: forKey:]: méthode de mutation envoyée à l'objet immuable'' à la ligne 'imageProperties [kCGImagePropertyIPTCDictionary] = iptc' –

+0

Etes-vous en train de vérifier le dictionnaire imageProperties un NSMutableDictionary? – Spads

+0

oui, j'obtiens un NSMutableDictionary pour les ImageProperties –