How to read exif data from UIImage in swift 4? - ios

I have an image with a-lot of exif informations. But when trying to read the exif information with swift, it shows limited number of exif information.
I have tried following code:
let data = UIImageJPEGRepresentation(image, 1.0)
let source = CGImageSourceCreateWithData(data! as CFData, nil)
let metadata = (CGImageSourceCopyPropertiesAtIndex(source!, 0, nil))
debugPrint(metadata ?? "nil")
And it prints the following result:
{
ColorModel = RGB;
Depth = 8;
Orientation = 6;
PixelHeight = 2448;
PixelWidth = 3264;
ProfileName = "sRGB IEC61966-2.1";
"{Exif}" = {
ColorSpace = 1;
PixelXDimension = 3264;
PixelYDimension = 2448;
};
"{JFIF}" = {
DensityUnit = 0;
JFIFVersion = (
1,
0,
1
);
XDensity = 72;
YDensity = 72;
};
"{TIFF}" = {
Orientation = 6;
};
}
How can I read all the exif information from UIImage?

if your image is captured using avcapturesession.than following is code for extract exif Data.
photoFileOutput?.captureStillImageAsynchronously(from: videoConnection, completionHandler: {(sampleBuffer, error) in
if (sampleBuffer != nil) {
let imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(sampleBuffer!)
let image = self.processPhoto(imageData!)
let source: CGImageSource = CGImageSourceCreateWithData((imageData as! CFMutableData), nil)!
let metadata = CGImageSourceCopyPropertiesAtIndex(source, 0,nil) as! [String:Any]
print("exif data = \(metadata![kCGImagePropertyExifDictionary as String] as? [String : AnyObject]) ")
completionHandler(true)
} else {
completionHandler(false)
}
}

My suspicion is UIImageJPEGRepresentation function is the culprit as it does the conversion from HEIC to JPEG (assuming you're pulling images from the Photos app). A lot of valuable Exif tags, including things like geo-location seem to get lost during this conversion.

If you have the image Data, you can create a CIImage with it and read its properties, you'll find the EXIF data there. I tried with a UIImage, get the JPEG data and read the EXIF from there, but I only got the same values you printed in your post. I think some of the EXIF stuff is stripped out in the jpeg conversion. By using CIImage I'm able to get LensMode, the ISO, Exposure time etc.
Here is an example with PHImageManager where I read all the images and print EXIF data
private func getPhotos() {
let manager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .fastFormat
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let results: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
if results.count > 0 {
for i in 0..<results.count {
let asset = results.object(at: i)
manager.requestImageDataAndOrientation(for: asset, options: requestOptions) { (data, fileName, orientation, info) in
if let data = data,
let cImage = CIImage(data: data) {
let exif = cImage.properties["{Exif}"]
print("EXIF Data: \(exif)")
}
}
}
}
}

Related

iOS: UIImage size in bytes is different than actual image size

I’m try to get the size in bytes of UIImage. The problem is that my actual image is of 5MB and when i get the size using
let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
let imgData = NSData(data: image.jpegData(compressionQuality: 1)!)
var imageSize: Int = imgData.count
print("actual size of image in KB: %f ", Double(imageSize) / 1000.0)
I’m getting 2MB only. can anyone enlighten me on this please.
Doesn't this line recompress the image?
let imgData = NSData(data: image.jpegData(compressionQuality: 1)!)
If so, I'd definitely expect the resulting image to be smaller than the original.
This worked for me instead of UIImage().jpegdata
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
var asset: PHAsset!
if #available(iOS 11.0, *) {
asset = info[UIImagePickerControllerPHAsset] as? PHAsset
} else {
if let url = info[UIImagePickerControllerReferenceURL] as? URL {
asset = PHAsset.fetchAssets(withALAssetURLs: [url], options: .none).firstObject!
}
}
if #available(iOS 13, *) {
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: .none) { data, string, orien, info in
let imgData = NSData(data:data!)
var imageSize: Int = imgData.count
print("actual size of image in KB: %f ", Double(imageSize) / 1024.0)
}
} else {
PHImageManager.default().requestImageData(for: asset, options: .none) { data, string, orientation, info in
let imgData = NSData(data:data!)
var imageSize: Int = imgData.count
print("actual size of image in KB: %f ", Double(imageSize) / 1024.0)
}
}
}
Add option .original to the PHImageRequestOptions. This one should do the trick:
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.resizeMode = .none
options.version = .original
PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options)

PHAsset video EXIF metadata retrieval

Currently, I am extracting EXIF metadata for PHAsset's that are images, but not having luck with videos.
For images, this works for me:
let imageOptions = PHImageRequestOptions()
imageOptions.isNetworkAccessAllowed = true
imageOptions.isSynchronous = true
imageOptions.version = .current
PHImageManager.default().requestImageDataAndOrientation(for: self.asset!, options: imageOptions) { (data, responseString, orientation, info) in
if let imageData: Data = data {
if let imageSource = CGImageSourceCreateWithData(imageData as CFData, nil) {
let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil)! as NSDictionary
}
}
})
What alteration would I need to retrieve metadata for a video asset?
self.asset!.mediaType == .video
UPDATE
After the first answer, I tried studying:
https://developer.apple.com/documentation/avfoundation/media_assets_and_metadata/retrieving_media_metadata
So far, I am still having issues understanding the concept. I tried:
let formatsKey = "availableMetadataFormats"
asset.loadValuesAsynchronously(forKeys: [formatsKey]) {
var error: NSError? = nil
let status = asset.statusOfValue(forKey: formatsKey, error: &error)
if status == .loaded {
for format in asset.availableMetadataFormats {
let metadata = asset.metadata(forFormat: format)
print (metadata)
}
}
}
I wasn't able to extract anything inside PHImageManager.default().requestAVAsset, coming up empty.
What I need is video fps/codec/sound(stereo or mono)/colorspace. That is it. I was able to get somewhere with:
if let videoTrack = asset.tracks(withMediaType: .video).first {
let videoFormatDescription = videoTrack.formatDescriptions.first as! CMVideoFormatDescription
print (videoFormatDescription)
}
Within CMVideoFormatDescription, most of the required attributes seem to be present, yet, I am unable to extract them so far.
Call requestAVAsset(forVideo:options:resultHandler:). Now you have an AVAsset. It has metadata and commonMetadata properties and you’re off to the races.

Modifing metadata from existing phAsset seems not working

In my App I want to make it possible, that the user sets an StarRating from 0 to 5 for any Image he has in his PhotoLibrary. My research shows, that there are a couple of ways to get this done:
Save the exif metadata using the new PHPhotoLibrary
Swift: Custom camera save modified metadata with image
Writing a Photo with Metadata using Photokit
Most of these Answers were creating a new Photo. My snippet now looks like this:
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)
}
}
}
}
}
}
})
Now when I want to read the metadata from the PHAsset I request the ContentEditingInput again and do the following:
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
}
}
}
}
}
But I never get my rating because it says that the value of imageProperties[kCGImagePropertyIPTCStarRating] is nil.
I also tried the examples from the Answers I posted above, but I always get the same result.
I hope anybody knows, what I can do to change the Metadata.
Also, how can I change the Metadata from an PHAsset with the MediaType .video? I tried to achieve that through the AVAssetWriter and AVExportSession Objects, but in both cases it does not work. Here what I tried for Videos:
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)
}
})
Sorry this isn't a full answer but it covers one part of your question. I noticed you are placing the StarRating in the wrong place. You need to place it in a IPTC dictionary. Also the properties data is stored as strings. Given you have the imageProperties you can add the star rating as follows and read it back using the following two functions
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
}
As the imageProperties you get from the image are not mutable you need to create a mutable copy of these properties first before you can call the functions above. When you create your image to save use the mutable properties in your call to CGImageDestinationAddImage()
if let mutableProperties = imageProperties.mutableCopy() as? NSMutableDictionary {
setIPTCStarRating(imageProperties:mutableProperties, rating:rating)
}
One other point you are creating an unnecessary UIImage. If you use CGImageDestinationAddImageFromSource() instead of CGImageDestinationAddImage() you can use the imageSource you created earlier instead of loading the image data into a UIImage.

Save metadata for custom image with Swift (iOS) and Photos framework

I am trying to add metadata to my synthetically generated and save it to camera roll by using the Photos framework. I got the saving and editing working but I just can seem to figure out how to add metadata. I have tried many approaches like adding the metadata by creating a CoreGraphics image (see code below). All these approaches do not give me an error but I just cannot see the metadata when I open the image on my Mac.
Can anyone point me in the right direction here?
PHPhotoLibrary.sharedPhotoLibrary().performChanges({
let assets : PHFetchResult = PHAsset.fetchAssetsWithLocalIdentifiers([self.localIdentifier], options: nil);
let asset : PHAsset = assets[0] as! PHAsset;
let changeRequest = PHAssetChangeRequest(forAsset: asset);
changeRequest.location = self.currentLocation;
asset.requestContentEditingInputWithOptions(nil, completionHandler: { (input: PHContentEditingInput?,
info: [NSObject : AnyObject]) -> Void in
guard let input = input else { return }
let imageManager : PHImageManager = PHImageManager();
let requestoptions : PHImageRequestOptions = PHImageRequestOptions();
requestoptions.resizeMode = PHImageRequestOptionsResizeMode.None;
imageManager.requestImageForAsset(asset, targetSize: PHImageManagerMaximumSize, contentMode: PHImageContentMode.Default, options: PHImageRequestOptions(), resultHandler: { (let image : UIImage?, _) -> Void in
let output : PHContentEditingOutput = PHContentEditingOutput(contentEditingInput: input);
PHPhotoLibrary.sharedPhotoLibrary().performChanges({ () -> Void in
let changeRequest = PHAssetChangeRequest(forAsset: asset);
/* Neu */
let imageData : NSData = NSData(contentsOfURL: (input.fullSizeImageURL)!)!;
let image : CIImage = CIImage(data: imageData)!;
let dataPtr = CFDataCreate(kCFAllocatorDefault, UnsafePointer<UInt8>(imageData.bytes), imageData.length)
// Save off the properties
let imageSource : CGImageSourceRef = CGImageSourceCreateWithData(dataPtr, nil)!;
var metadata : NSMutableDictionary = NSMutableDictionary(dictionary: CGImageSourceCopyProperties(imageSource, nil)!);
/* Add some values to metadata */
....
NSLog("New metadata: %#", metadata);
// Save the image
let outputImageSource : CGImageSourceRef = CGImageSourceCreateWithData(dataPtr, nil)!;
let jpegData : CFMutableDataRef = CFDataCreateMutable(kCFAllocatorDefault, 0);
let outputDestination : CGImageDestinationRef = CGImageDestinationCreateWithData(jpegData, CGImageSourceGetType(outputImageSource)!, 1, nil)!;
// add the image data to the destination
CGImageDestinationAddImageFromSource(outputDestination, outputImageSource, 0, metadata);
if CGImageDestinationFinalize(outputDestination)
{
NSLog("Successful image creation.");
// process the image rendering, adjustment data creation and finalize the asset edit.
}
else
{
NSLog("Image creation failed.");
}
(jpegData as NSData).writeToURL(output.renderedContentURL, atomically: true);
let options : String = NSString(format: "%f|%f|%f|%f|%f|%f", self.saturationSlider.value, self.warmthSlider.value, self.brightnessSlider.value, self.sharpnessSlider.value, self.contrastSlider.value, self.gammaSlider.value ) as String;
let nsObject: AnyObject? = NSBundle.mainBundle().infoDictionary!["CFBundleShortVersionString"];
output.adjustmentData = PHAdjustmentData(formatIdentifier: NSBundle.mainBundle().bundleIdentifier!,
formatVersion: nsObject as! String,
data: options.dataUsingEncoding(NSUTF8StringEncoding)!);
changeRequest.contentEditingOutput = output;
}, completionHandler: { (_bool, _error) -> Void in
if !_bool && error != nil
{
NSLog("%#", error!);
}
});
});
});
}, completionHandler: { (_bool, _error) -> Void in
});
You can add/set a few properties on the creationRequest object. I don't know about adding custom metadata.
PHPhotoLibrary.shared().performChanges({
let creationRequest = PHAssetChangeRequest.creationRequestForAsset(from: self.anImage!)
let aLocation = CLLocation(latitude: 27.63866, longitude: -80.39707)
creationRequest.location = aLocation
creationRequest.isFavorite = true
creationRequest.creationDate = Date()
}, completionHandler: {success, error in
if !success {
print("error creating asset: \(error!)")
} else {
print("success creating asset!")
}
})

Handle Image Metadata Swift 2.0

I have a code to save a picture in Swift (1.1) and since IOS 9 i try to change it for swift 2.0 but after 3 days i can not ...
So it's a juste a simple function get metadata and function get this, update this and create new image with this. Works fine with xCode 6.2 and Swift (1.1)
So if you can help me please :s
import ImageIO
func savePicture(data: NSData) {
var img: CGImageRef
if let source = CGImageSourceCreateWithData(data, nil) {
var metadata = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)! as Dictionary
let width = CGFloat(metadata[kCGImagePropertyPixelWidth] as NSNumber)
let height = CGFloat(metadata[kCGImagePropertyPixelHeight] as NSNumber)
let reso = width * height
metadata[kCGImageDestinationLossyCompressionQuality as String] = self.jpegQuality
var tiff:NSMutableDictionary = metadata[kCGImagePropertyTIFFDictionary] as NSMutableDictionary
tiff.setValue("Pictures", forKey: kCGImagePropertyTIFFModel)
var exif:NSMutableDictionary = metadata[kCGImagePropertyExifDictionary] as NSMutableDictionary
exif.setValue("Pictures", forKey: kCGImagePropertyExifLensModel)
let gps:NSMutableDictionary = NSMutableDictionary()
metadata[kCGImagePropertyGPSDictionary] = gps
gps.setValue(self.loc["lat"], forKey: kCGImagePropertyGPSLatitude)
gps.setValue(self.loc["lng"], forKey: kCGImagePropertyGPSLongitude)
self.dateFormatter.dateFormat = "HH:mm:ss"
gps.setValue(self.dateFormatter.stringFromDate(NSDate()), forKey: kCGImagePropertyGPSTimeStamp)
self.dateFormatter.dateFormat = "yyyy:MM:dd"
gps.setValue(self.dateFormatter.stringFromDate(NSDate()), forKey: kCGImagePropertyGPSDateStamp)
if (reso > self.desiredRes) {
let ratio = reso / self.desiredRes
metadata[kCGImageSourceThumbnailMaxPixelSize as String] = max(width, height) / sqrt(ratio)
metadata[kCGImageSourceCreateThumbnailFromImageIfAbsent as String] = true
img = CGImageSourceCreateThumbnailAtIndex(source, 0, metadata)
} else {
img = CGImageSourceCreateImageAtIndex(source, 0, metadata)
}
let uti = "public.jpeg"
var d = NSMutableData()
var dest: CGImageDestinationRef = CGImageDestinationCreateWithData(d, uti, 1, nil)
CGImageDestinationAddImage(dest, img, metadata)
CGImageDestinationFinalize(dest)
d.writeToFile(self._makePicName(), atomically: false)
}
// debug purpose: show that metadata have been saved
// let d = NSData(contentsOfFile: self.paths.last!)
// if let t = CGImageSourceCreateWithData(d, nil) {
// let again = CGImageSourceCopyPropertiesAtIndex(t, 0, nil) as Dictionary
// println(again)
// }
dispatch_async(dispatch_get_main_queue(), {
for dlg in self.delegate {
dlg.didFinishSavingPhoto?()
}
})
}

Resources