SYPictureMetadata 2.1.1

SYPictureMetadata 2.1.1

Stanislas Chevallier维护。



  • 作者:
  • Stan Chevallier

SYPictureMetadata

使用ImageIO和易于使用的模型读写图像元数据。

屏幕截图

Data set analysis

Metadata preview

元数据支持

ImageIO键值

自iOS 11以来,ImageIO添加了很多键值,它们大多关于DNG、HEIC和IPTC扩展数据,目前不支持。完整列表可在Keys/Unsupported.txt中找到。请随意向我提交PR,最好附上测试用例:)

目前我们支持iOS 14.4中的628个定义键值中的325个,约51%。

测试覆盖率

目前已有基本测试,覆盖大约 32% 的已定义获取器和 3% 的已定义设置器。如果您需要在您的应用程序中支持特定的属性,也应测试这些属性,并且欢迎您提交 PR 来增加此库的覆盖率。

Xcode 报告此库的测试覆盖率为 36.4%。

照片标题(iOS 14)

我已为 iOS 14 照片标题增加了初步支持。由于这些数据不像其他元数据一样存储在图像数据中,而是存储在 ImageIO 中直到图像导出,因此我创建了一个名为 assetCaption 的只读属性,属于 PHAsset 扩展,用于获取这些信息。

更多详情见 SYMetadataExtensions.swift

请注意

当将元数据保存到文件时,可能会遇到以下问题

  • 未写入元数据
  • 元数据已被修改
  • 添加了元数据
  • 应删除的元数据没有被删除,因为 ImageIO 在另一个命名空间中找到了类似的元数据(例如,如果存在 IPTC.byLine,则删除 TIFF.artist 就没效果)

此库使用 ImageIO,它有其自身的限制和值检查。这只是在框架暴露的 NSDictionary 结构周围的一个包装器,并不完美。请在使用此库编辑元数据时,对您的应用程序进行充分的测试,测试应用程序示例中可以找到测试图像集。此库不能保证数据完整性,如 libexifexiv2 一样。

示例

添加IPTC数据示例

// this is available in the Example project
let imageURL = TestFile.iptc2.url!

// load metadata from original file (please handle errors, the type is SYMetadata.Error)
let metadata = try! SYMetadata(fileURL: imageURL)
    
// create IPTC container if not present
if (metadata.metadataIPTC == nil) {
    metadata.metadataIPTC = SYMetadataIPTC()
}
    
// edit metadata
metadata.metadataIPTC?.keywords  = ["Some test keywords", "added by SYMetadata example app"];
metadata.metadataIPTC?.city      = "Lyon";
metadata.metadataIPTC?.credit    = "© Me 2017";
    
// create new image data with original image data and edited metadata
let originalImageData = try! Data(contentsOf: imageURL)
let imageDataWithMetadata = try! metadata.apply(to: originalImageData)
    
// log the delta in file size
print("File size delta:", imageDataWithMetadata.count - originalImageData.count);
// File size delta: 60325

// load metadata for newly cerated image
let reloadedMetadata = try! SYMetadata(imageData: imageDataWithMetadata)
    
// log the differences between files
print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: true).jsonString)
// Differences:
//  {
//   "{Exif}" : {
//     "ShutterSpeedValue" : "Updated: 8.643856 -> 8.643855995239512"
//   },
//   "{IPTC}" : {
//     "City" : "Added: Lyon",
//     "Credit" : "Added: © Me 2017",
//     "Keywords" : "Updated: beach, baywatch, LA, sunset -> Some test keywords, added by SYMetadata example app"
//   },
//   "{JFIF}" : {
//     "DensityUnit" : "Added: 0",
//     "JFIFVersion" : "Added: 1, 0, 1",
//     "XDensity" : "Added: 72",
//     "YDensity" : "Added: 72"
//   }
// }

删除所有元数据

let imageURL = TestFile.iptc2.url!
    
// load metadata from original file (please handle errors, the type is SYMetadata.Error)
let metadata = try! SYMetadata(fileURL: imageURL)
    
// create new image data with original image data and strip all metadata
let originalImageData = try! Data(contentsOf: imageURL)
let imageDataWithoutMetadata = try! SYMetadata.stripAllMetadata(from: originalImageData)
    
// log the delta in file size
print("File size delta:", imageDataWithoutMetadata.count - originalImageData.count);
// File size delta: 73491

// load metadata for newly cerated image
let reloadedMetadata = try! SYMetadata(imageData: imageDataWithoutMetadata)
    
// log the differences between files
print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: false).jsonString)
// Differences:
//  {
//   "{Exif}" : {
//     "ApertureValue" : "Removed",
//     "ComponentsConfiguration" : "Removed",
//     "CompressedBitsPerPixel" : "Removed",
//     "Contrast" : "Removed",
//     "CustomRendered" : "Removed",
//     "DateTimeDigitized" : "Removed",
//     "DateTimeOriginal" : "Removed",
//     "DigitalZoomRatio" : "Removed",
//     "ExifVersion" : "Removed",
//     "ExposureBiasValue" : "Removed",
//     "ExposureMode" : "Removed",
//     "ExposureProgram" : "Removed",
//     "ExposureTime" : "Removed",
//     "FileSource" : "Removed",
//     "Flash" : "Removed",
//     "FlashPixVersion" : "Removed",
//     "FNumber" : "Removed",
//     "FocalLength" : "Removed",
//     "FocalLenIn35mmFilm" : "Removed",
//     "GainControl" : "Removed",
//     "ISOSpeedRatings" : "Removed",
//     "LightSource" : "Removed",
//     "MaxApertureValue" : "Removed",
//     "MeteringMode" : "Removed",
//     "Saturation" : "Removed",
//     "SceneCaptureType" : "Removed",
//     "SceneType" : "Removed",
//     "SensingMethod" : "Removed",
//     "Sharpness" : "Removed",
//     "ShutterSpeedValue" : "Removed",
//     "WhiteBalance" : "Removed"
//   },
//   "{IPTC}" : {
//     "Byline" : "Removed",
//     "BylineTitle" : "Removed",
//     "Caption\/Abstract" : "Removed",
//     "CopyrightNotice" : "Removed",
//     "DateCreated" : "Removed",
//     "DigitalCreationDate" : "Removed",
//     "DigitalCreationTime" : "Removed",
//     "Keywords" : "Removed",
//     "ObjectName" : "Removed",
//     "TimeCreated" : "Removed"
//   },
//   "{JFIF}" : {
//     "DensityUnit" : "Added",
//     "IsProgressive" : "Removed",
//     "JFIFVersion" : "Added",
//     "XDensity" : "Added",
//     "YDensity" : "Added"
//   },
//   "{TIFF}" : {
//     "Artist" : "Removed",
//     "Copyright" : "Removed",
//     "DateTime" : "Removed",
//     "ImageDescription" : "Removed",
//     "Make" : "Removed",
//     "Model" : "Removed",
//     "PhotometricInterpretation" : "Removed",
//     "ResolutionUnit" : "Removed",
//     "Software" : "Removed",
//     "XResolution" : "Removed",
//     "YResolution" : "Removed"
//   },
//   "DPIHeight" : "Removed",
//   "DPIWidth" : "Removed"
// }

// log kept metadata
print("Kept metadata:\n", reloadedMetadata.originalDictionary.jsonString)
// Kept metadata:
//  {
//   "{Exif}" : {
//     "ColorSpace" : 1,
//     "PixelXDimension" : 1920,
//     "PixelYDimension" : 1080
//   },
//   "{JFIF}" : {
//     "DensityUnit" : 0,
//     "JFIFVersion" : [
//       1,
//       0,
//       1
//     ],
//     "XDensity" : 72,
//     "YDensity" : 72
//   },
//   "{TIFF}" : {
//     "Orientation" : 1
//   },
//   "ColorModel" : "RGB",
//   "Depth" : 8,
//   "Orientation" : 1,
//   "PixelHeight" : 1080,
//   "PixelWidth" : 1920,
//   "ProfileName" : "sRGB IEC61966-2.1"
// }

致谢

在这个库内的评论中,您可以找到对我帮助很大的链接。主要是:

许可证

在您想用的任何项目中使用它,提及我的名字并不要怪我出故障 :)

-- syan