Skip to content

Instantly share code, notes, and snippets.

@geor-kasapidi
Created August 12, 2020 10:12
Show Gist options
  • Select an option

  • Save geor-kasapidi/a0566abff10c99df6b2359944c6f2eb5 to your computer and use it in GitHub Desktop.

Select an option

Save geor-kasapidi/a0566abff10c99df6b2359944c6f2eb5 to your computer and use it in GitHub Desktop.
import Foundation
import ImageIO
import UIKit.UIImage
struct ImageMetadata: CustomDebugStringConvertible {
private let properties: NSDictionary
init(raw properties: CFDictionary) {
self.properties = properties
}
var debugDescription: String {
self.properties.debugDescription
}
init?(imageData: Data) {
guard
let source = CGImageSourceCreateWithData(imageData as CFData, nil),
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil)
else {
return nil
}
self.properties = properties
}
@discardableResult
func enrich(imageData: Data) -> Data? {
guard
let source = CGImageSourceCreateWithData(imageData as CFData, nil),
let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, nil),
let uti = CGImageSourceGetType(source)
else {
return nil
}
let updatedProperties = Self.merge(
properties: self.properties,
to: properties,
ignoredKeyPaths: Self.dateKeyPaths
)
let destinationData = NSMutableData()
guard let destination = CGImageDestinationCreateWithData(destinationData, uti, 1, nil) else {
return nil
}
CGImageDestinationAddImageFromSource(destination, source, 0, updatedProperties)
if !CGImageDestinationFinalize(destination) {
return nil
}
return destinationData as Data
}
// MARK: - Serialization/Deserialization
init?(archivedData: Data) {
guard let properties = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(archivedData) as? NSDictionary else {
return nil
}
self.properties = properties
}
func archivedData() throws -> Data {
try NSKeyedArchiver.archivedData(withRootObject: self.properties, requiringSecureCoding: true)
}
// MARK: - Merge
private static let dateKeyPaths: [String: Set<String>] = [
"{TIFF}": [
"DateTime",
],
"{Exif}": [
"OffsetTimeDigitized",
"SubsecTimeOriginal",
"OffsetTimeOriginal",
"DateTimeOriginal",
"SubsecTimeDigitized",
"OffsetTime",
"DateTimeDigitized",
],
]
private static func merge(
properties: NSDictionary,
to otherProperties: NSDictionary,
ignoredKeyPaths: [String: Set<String>]
) -> NSDictionary {
let copy = NSMutableDictionary(dictionary: otherProperties)
for (key, value) in properties {
self.append(
from: value as? NSDictionary,
to: otherProperties[key] as? NSDictionary,
ignoredKeys: (key as? String).flatMap { ignoredKeyPaths[$0] } ?? []
).flatMap {
copy[key] = $0
}
}
return copy
}
private static func append(
from source: NSDictionary?,
to target: NSDictionary?,
ignoredKeys: Set<String>
) -> NSDictionary? {
switch (source, target) {
case let (.some(source), .some(target)):
let copy = NSMutableDictionary(dictionary: target)
for (key, value) in source where target[key] == nil {
guard !((key as? String).flatMap(ignoredKeys.contains) ?? false) else {
continue
}
copy[key] = value
}
return copy
case let (.some(source), nil):
return source
case let (nil, .some(target)):
return target
case (nil, nil):
return nil
}
}
}
extension ImageMetadata {
func enrichedJPEGData(from image: UIImage) -> Data? {
return image.jpegData(compressionQuality: 1).flatMap {
self.enrich(imageData: $0)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment