├── .gitignore ├── Examples ├── SYPictureMetadataExample.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ ├── SYPictureMetadataExample.xcscheme │ │ └── SYPictureMetadataTests.xcscheme └── SYPictureMetadataExample │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json │ ├── Cells │ └── SYSummaryCell.swift │ ├── Info.plist │ ├── LaunchScreen.storyboard │ ├── Main.storyboard │ ├── SYPictureMetadataExample.entitlements │ └── ViewControllers │ ├── SYImageVC.swift │ ├── SYImageVC.xib │ ├── SYImagesVC.swift │ ├── SYOpenPhotoVC.swift │ └── SYSummaryVC.swift ├── LICENSE.md ├── Package.swift ├── README.md ├── Sources ├── SYPictureMetadata │ ├── Extensions │ │ └── ImageIO+SYPictureMetadata.swift │ ├── SYMetadata.swift │ ├── SYMetadata8BIM.swift │ ├── SYMetadataBase.swift │ ├── SYMetadataCIFF.swift │ ├── SYMetadataDNG.swift │ ├── SYMetadataExif.swift │ ├── SYMetadataExifAux.swift │ ├── SYMetadataExtensions.swift │ ├── SYMetadataGIF.swift │ ├── SYMetadataGPS.swift │ ├── SYMetadataIPTC.swift │ ├── SYMetadataIPTCContactInfo.swift │ ├── SYMetadataJFIF.swift │ ├── SYMetadataMakerApple.swift │ ├── SYMetadataMakerCanon.swift │ ├── SYMetadataMakerFuji.swift │ ├── SYMetadataMakerMinolta.swift │ ├── SYMetadataMakerNikon.swift │ ├── SYMetadataMakerOlympus.swift │ ├── SYMetadataMakerPentax.swift │ ├── SYMetadataPNG.swift │ ├── SYMetadataRaw.swift │ └── SYMetadataTIFF.swift └── SYPictureMetadataTestAssets │ ├── MetadataKeys.swift │ ├── MetadataKeys │ ├── ImageIO.txt │ ├── Supported.txt │ └── Unsupported.txt │ ├── TestFile.swift │ └── TestFile │ ├── TEST_8BIM.psd │ ├── TEST_APPLE_GPS.JPG │ ├── TEST_CANON.cr2 │ ├── TEST_CIFF.CRW │ ├── TEST_DNG.dng │ ├── TEST_GIF.gif │ ├── TEST_IPTC.jpg │ ├── TEST_IPTC_2.jpg │ ├── TEST_IPTC_3.jpg │ ├── TEST_NIKON.nef │ ├── TEST_PICTURESTYLE.CR2 │ ├── TEST_PNG.png │ └── TEST_unreadable.txt ├── Tests └── SYPictureMetadataTests │ ├── Extensions │ ├── Dictionary+Extensions.swift │ ├── Foundation+Extensions.swift │ └── SYMetadata+Extensions.swift │ └── SYPictureMetadataTests.swift └── screenshots ├── screenshot_preview.png └── screenshot_set_analysis.png /.gitignore: -------------------------------------------------------------------------------- 1 | _TRANSLATIONS/ 2 | 3 | # Xcode 4 | .DS_Store 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | *.xcworkspace 15 | !default.xcworkspace 16 | xcuserdata 17 | profile 18 | *.moved-aside 19 | DerivedData 20 | .idea/ 21 | 22 | # Cocoapods 23 | Pods/ 24 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 97172AB923F04276007F2203 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97172AB823F04276007F2203 /* Main.storyboard */; }; 11 | 97172ABB23F0430D007F2203 /* SYImagesVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97172ABA23F0430D007F2203 /* SYImagesVC.swift */; }; 12 | 97172ABE23F045B3007F2203 /* SYImageVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97172ABC23F045B3007F2203 /* SYImageVC.swift */; }; 13 | 97172ABF23F045B3007F2203 /* SYImageVC.xib in Resources */ = {isa = PBXBuildFile; fileRef = 97172ABD23F045B3007F2203 /* SYImageVC.xib */; }; 14 | 97172AC323F051C2007F2203 /* SYSummaryCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97172AC223F051C2007F2203 /* SYSummaryCell.swift */; }; 15 | 97172AC923F052BB007F2203 /* SYSummaryVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97172AC823F052BB007F2203 /* SYSummaryVC.swift */; }; 16 | 97172ACD23F0731C007F2203 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97172ACB23F0731C007F2203 /* LaunchScreen.storyboard */; }; 17 | 97230E172BFD510500CDB64D /* SYPictureMetadata in Frameworks */ = {isa = PBXBuildFile; productRef = 97230E162BFD510500CDB64D /* SYPictureMetadata */; }; 18 | 97230E192BFD510500CDB64D /* SYPictureMetadataTestAssets in Frameworks */ = {isa = PBXBuildFile; productRef = 97230E182BFD510500CDB64D /* SYPictureMetadataTestAssets */; }; 19 | 97230E1E2BFD511500CDB64D /* Foundation+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97230E1B2BFD511500CDB64D /* Foundation+Extensions.swift */; }; 20 | 97230E1F2BFD511500CDB64D /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97230E1C2BFD511500CDB64D /* Dictionary+Extensions.swift */; }; 21 | 97230E202BFD511500CDB64D /* SYMetadata+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97230E1D2BFD511500CDB64D /* SYMetadata+Extensions.swift */; }; 22 | 975D0D2323D5F5CD00DFFC35 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 975D0D2223D5F5CD00DFFC35 /* AppDelegate.swift */; }; 23 | 975D0D2C23D5F5CF00DFFC35 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 975D0D2B23D5F5CF00DFFC35 /* Assets.xcassets */; }; 24 | 976E79552BFD3BB200336C5C /* SYPictureMetadata in Frameworks */ = {isa = PBXBuildFile; productRef = 976E79542BFD3BB200336C5C /* SYPictureMetadata */; }; 25 | 97CD867E25C9B8B00039F7ED /* SYOpenPhotoVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 97CD867D25C9B8B00039F7ED /* SYOpenPhotoVC.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 97172AB823F04276007F2203 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; }; 30 | 97172ABA23F0430D007F2203 /* SYImagesVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYImagesVC.swift; sourceTree = ""; }; 31 | 97172ABC23F045B3007F2203 /* SYImageVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYImageVC.swift; sourceTree = ""; }; 32 | 97172ABD23F045B3007F2203 /* SYImageVC.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SYImageVC.xib; sourceTree = ""; }; 33 | 97172AC223F051C2007F2203 /* SYSummaryCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYSummaryCell.swift; sourceTree = ""; }; 34 | 97172AC823F052BB007F2203 /* SYSummaryVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYSummaryVC.swift; sourceTree = ""; }; 35 | 97172ACA23F0731C007F2203 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 36 | 97172ACB23F0731C007F2203 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 37 | 97230E1B2BFD511500CDB64D /* Foundation+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Foundation+Extensions.swift"; sourceTree = ""; }; 38 | 97230E1C2BFD511500CDB64D /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = ""; }; 39 | 97230E1D2BFD511500CDB64D /* SYMetadata+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SYMetadata+Extensions.swift"; sourceTree = ""; }; 40 | 975D0D1F23D5F5CD00DFFC35 /* SYPictureMetadataExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SYPictureMetadataExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 975D0D2223D5F5CD00DFFC35 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 42 | 975D0D2B23D5F5CF00DFFC35 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 97B92E6925C9C2DF00EC7244 /* SYPictureMetadataExample.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SYPictureMetadataExample.entitlements; sourceTree = ""; }; 44 | 97CD867D25C9B8B00039F7ED /* SYOpenPhotoVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SYOpenPhotoVC.swift; sourceTree = ""; }; 45 | /* End PBXFileReference section */ 46 | 47 | /* Begin PBXFrameworksBuildPhase section */ 48 | 975D0D1C23D5F5CD00DFFC35 /* Frameworks */ = { 49 | isa = PBXFrameworksBuildPhase; 50 | buildActionMask = 2147483647; 51 | files = ( 52 | 97230E172BFD510500CDB64D /* SYPictureMetadata in Frameworks */, 53 | 976E79552BFD3BB200336C5C /* SYPictureMetadata in Frameworks */, 54 | 97230E192BFD510500CDB64D /* SYPictureMetadataTestAssets in Frameworks */, 55 | ); 56 | runOnlyForDeploymentPostprocessing = 0; 57 | }; 58 | /* End PBXFrameworksBuildPhase section */ 59 | 60 | /* Begin PBXGroup section */ 61 | 97230E1A2BFD511500CDB64D /* Extensions */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | 97230E1B2BFD511500CDB64D /* Foundation+Extensions.swift */, 65 | 97230E1C2BFD511500CDB64D /* Dictionary+Extensions.swift */, 66 | 97230E1D2BFD511500CDB64D /* SYMetadata+Extensions.swift */, 67 | ); 68 | name = Extensions; 69 | path = ../../Tests/SYPictureMetadataTests/Extensions; 70 | sourceTree = ""; 71 | }; 72 | 975D0D1623D5F5CD00DFFC35 = { 73 | isa = PBXGroup; 74 | children = ( 75 | 975D0D2123D5F5CD00DFFC35 /* SYPictureMetadataExample */, 76 | 975D0D2023D5F5CD00DFFC35 /* Products */, 77 | 976713922BFD4E5C00C946F5 /* Frameworks */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 975D0D2023D5F5CD00DFFC35 /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 975D0D1F23D5F5CD00DFFC35 /* SYPictureMetadataExample.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 975D0D2123D5F5CD00DFFC35 /* SYPictureMetadataExample */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 97B92E6925C9C2DF00EC7244 /* SYPictureMetadataExample.entitlements */, 93 | 975D0D2B23D5F5CF00DFFC35 /* Assets.xcassets */, 94 | 97230E1A2BFD511500CDB64D /* Extensions */, 95 | 976E79512BFD3B5300336C5C /* Cells */, 96 | 976E79522BFD3B5D00336C5C /* ViewControllers */, 97 | 975D0D2223D5F5CD00DFFC35 /* AppDelegate.swift */, 98 | 97172AB823F04276007F2203 /* Main.storyboard */, 99 | 97172ACB23F0731C007F2203 /* LaunchScreen.storyboard */, 100 | 97172ACA23F0731C007F2203 /* Info.plist */, 101 | ); 102 | path = SYPictureMetadataExample; 103 | sourceTree = ""; 104 | }; 105 | 976713922BFD4E5C00C946F5 /* Frameworks */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | ); 109 | name = Frameworks; 110 | sourceTree = ""; 111 | }; 112 | 976E79512BFD3B5300336C5C /* Cells */ = { 113 | isa = PBXGroup; 114 | children = ( 115 | 97172AC223F051C2007F2203 /* SYSummaryCell.swift */, 116 | ); 117 | path = Cells; 118 | sourceTree = ""; 119 | }; 120 | 976E79522BFD3B5D00336C5C /* ViewControllers */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 97172ABA23F0430D007F2203 /* SYImagesVC.swift */, 124 | 97172ABC23F045B3007F2203 /* SYImageVC.swift */, 125 | 97172ABD23F045B3007F2203 /* SYImageVC.xib */, 126 | 97CD867D25C9B8B00039F7ED /* SYOpenPhotoVC.swift */, 127 | 97172AC823F052BB007F2203 /* SYSummaryVC.swift */, 128 | ); 129 | path = ViewControllers; 130 | sourceTree = ""; 131 | }; 132 | /* End PBXGroup section */ 133 | 134 | /* Begin PBXNativeTarget section */ 135 | 975D0D1E23D5F5CD00DFFC35 /* SYPictureMetadataExample */ = { 136 | isa = PBXNativeTarget; 137 | buildConfigurationList = 975D0D3323D5F5CF00DFFC35 /* Build configuration list for PBXNativeTarget "SYPictureMetadataExample" */; 138 | buildPhases = ( 139 | 974D914F23EF97A500C770FA /* TODOs */, 140 | 974D914D23EF8E2800C770FA /* Generate unsupported keys */, 141 | 975D0D1B23D5F5CD00DFFC35 /* Sources */, 142 | 975D0D1C23D5F5CD00DFFC35 /* Frameworks */, 143 | 975D0D1D23D5F5CD00DFFC35 /* Resources */, 144 | ); 145 | buildRules = ( 146 | ); 147 | dependencies = ( 148 | ); 149 | name = SYPictureMetadataExample; 150 | packageProductDependencies = ( 151 | 976E79542BFD3BB200336C5C /* SYPictureMetadata */, 152 | 97230E162BFD510500CDB64D /* SYPictureMetadata */, 153 | 97230E182BFD510500CDB64D /* SYPictureMetadataTestAssets */, 154 | ); 155 | productName = SYPictureMetadataExample; 156 | productReference = 975D0D1F23D5F5CD00DFFC35 /* SYPictureMetadataExample.app */; 157 | productType = "com.apple.product-type.application"; 158 | }; 159 | /* End PBXNativeTarget section */ 160 | 161 | /* Begin PBXProject section */ 162 | 975D0D1723D5F5CD00DFFC35 /* Project object */ = { 163 | isa = PBXProject; 164 | attributes = { 165 | BuildIndependentTargetsInParallel = YES; 166 | LastSwiftUpdateCheck = 1140; 167 | LastUpgradeCheck = 1530; 168 | ORGANIZATIONNAME = Syan.me; 169 | TargetAttributes = { 170 | 975D0D1E23D5F5CD00DFFC35 = { 171 | CreatedOnToolsVersion = 11.3.1; 172 | }; 173 | }; 174 | }; 175 | buildConfigurationList = 975D0D1A23D5F5CD00DFFC35 /* Build configuration list for PBXProject "SYPictureMetadataExample" */; 176 | compatibilityVersion = "Xcode 9.3"; 177 | developmentRegion = en; 178 | hasScannedForEncodings = 0; 179 | knownRegions = ( 180 | en, 181 | Base, 182 | ); 183 | mainGroup = 975D0D1623D5F5CD00DFFC35; 184 | packageReferences = ( 185 | 97230E152BFD510500CDB64D /* XCLocalSwiftPackageReference ".." */, 186 | ); 187 | productRefGroup = 975D0D2023D5F5CD00DFFC35 /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | 975D0D1E23D5F5CD00DFFC35 /* SYPictureMetadataExample */, 192 | ); 193 | }; 194 | /* End PBXProject section */ 195 | 196 | /* Begin PBXResourcesBuildPhase section */ 197 | 975D0D1D23D5F5CD00DFFC35 /* Resources */ = { 198 | isa = PBXResourcesBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | 97172ABF23F045B3007F2203 /* SYImageVC.xib in Resources */, 202 | 975D0D2C23D5F5CF00DFFC35 /* Assets.xcassets in Resources */, 203 | 97172ACD23F0731C007F2203 /* LaunchScreen.storyboard in Resources */, 204 | 97172AB923F04276007F2203 /* Main.storyboard in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXShellScriptBuildPhase section */ 211 | 974D914D23EF8E2800C770FA /* Generate unsupported keys */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | alwaysOutOfDate = 1; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | inputFileListPaths = ( 218 | ); 219 | inputPaths = ( 220 | ); 221 | name = "Generate unsupported keys"; 222 | outputFileListPaths = ( 223 | ); 224 | outputPaths = ( 225 | ); 226 | runOnlyForDeploymentPostprocessing = 0; 227 | shellPath = /bin/sh; 228 | shellScript = "SDK_PATH=`xcrun --sdk iphoneos --show-sdk-path`\nHEADER_PATH=\"${SDK_PATH}/System/Library/Frameworks/ImageIO.framework/Headers/CGImageProperties.h\"\nPACKAGE=\"${SRCROOT}/..\"\nKEYS=\"${PACKAGE}/Sources/SYPictureMetadataTestAssets/MetadataKeys\"\n\ngrep \"kCGImageProperty\" \"${HEADER_PATH}\" | grep -o 'kCGImageProperty\\w*' | sort | uniq > \"${KEYS}/ImageIO.txt\"\ngrep -oh 'kCGImageProperty\\w*' \"${PACKAGE}/Sources/SYPictureMetadata/\"*.swift | sort | uniq > \"${KEYS}/Supported.txt\"\ngrep -v -f \"${KEYS}/Supported.txt\" \"${KEYS}/ImageIO.txt\" > \"${KEYS}/Unsupported.txt\"\n"; 229 | }; 230 | 974D914F23EF97A500C770FA /* TODOs */ = { 231 | isa = PBXShellScriptBuildPhase; 232 | alwaysOutOfDate = 1; 233 | buildActionMask = 2147483647; 234 | files = ( 235 | ); 236 | inputFileListPaths = ( 237 | ); 238 | inputPaths = ( 239 | ); 240 | name = TODOs; 241 | outputFileListPaths = ( 242 | ); 243 | outputPaths = ( 244 | ); 245 | runOnlyForDeploymentPostprocessing = 0; 246 | shellPath = /bin/sh; 247 | shellScript = "declare -a TAGS=(\"TODO:\" \"FIXME:\" \"LATER:\")\necho \"searching ${SRCROOT} for ${TAGS}\"\n\nfor TAG in ${TAGS[@]}; do\nfind \"${SRCROOT}/${PROJECT_NAME}\" \\( -name \"*.h\" -or -name \"*.m\" -or -name \"*.swift\" \\) -print0 | xargs -0 egrep --with-filename --line-number --only-matching \"($TAG).*\\$\" | perl -p -e \"s/($TAG)/ warning: \\$1/\"\ndone\n\nfor TAG in ${TAGS[@]}; do\nfind \"${SRCROOT}/SYPictureMetadata\" \\( -name \"*.h\" -or -name \"*.m\" -or -name \"*.swift\" \\) -print0 | xargs -0 egrep --with-filename --line-number --only-matching \"($TAG).*\\$\" | perl -p -e \"s/($TAG)/ warning: \\$1/\"\ndone\n"; 248 | }; 249 | /* End PBXShellScriptBuildPhase section */ 250 | 251 | /* Begin PBXSourcesBuildPhase section */ 252 | 975D0D1B23D5F5CD00DFFC35 /* Sources */ = { 253 | isa = PBXSourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 97172AC323F051C2007F2203 /* SYSummaryCell.swift in Sources */, 257 | 97172ABB23F0430D007F2203 /* SYImagesVC.swift in Sources */, 258 | 97172AC923F052BB007F2203 /* SYSummaryVC.swift in Sources */, 259 | 97172ABE23F045B3007F2203 /* SYImageVC.swift in Sources */, 260 | 975D0D2323D5F5CD00DFFC35 /* AppDelegate.swift in Sources */, 261 | 97230E1E2BFD511500CDB64D /* Foundation+Extensions.swift in Sources */, 262 | 97CD867E25C9B8B00039F7ED /* SYOpenPhotoVC.swift in Sources */, 263 | 97230E202BFD511500CDB64D /* SYMetadata+Extensions.swift in Sources */, 264 | 97230E1F2BFD511500CDB64D /* Dictionary+Extensions.swift in Sources */, 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | /* End PBXSourcesBuildPhase section */ 269 | 270 | /* Begin XCBuildConfiguration section */ 271 | 975D0D3123D5F5CF00DFFC35 /* Debug */ = { 272 | isa = XCBuildConfiguration; 273 | buildSettings = { 274 | ALWAYS_SEARCH_USER_PATHS = NO; 275 | CLANG_ANALYZER_NONNULL = YES; 276 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 277 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 278 | CLANG_CXX_LIBRARY = "libc++"; 279 | CLANG_ENABLE_MODULES = YES; 280 | CLANG_ENABLE_OBJC_ARC = YES; 281 | CLANG_ENABLE_OBJC_WEAK = YES; 282 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 283 | CLANG_WARN_BOOL_CONVERSION = YES; 284 | CLANG_WARN_COMMA = YES; 285 | CLANG_WARN_CONSTANT_CONVERSION = YES; 286 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 287 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 288 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 289 | CLANG_WARN_EMPTY_BODY = YES; 290 | CLANG_WARN_ENUM_CONVERSION = YES; 291 | CLANG_WARN_INFINITE_RECURSION = YES; 292 | CLANG_WARN_INT_CONVERSION = YES; 293 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 294 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 295 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 296 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 297 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 298 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 299 | CLANG_WARN_STRICT_PROTOTYPES = YES; 300 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 301 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 302 | CLANG_WARN_UNREACHABLE_CODE = YES; 303 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 304 | COPY_PHASE_STRIP = NO; 305 | DEBUG_INFORMATION_FORMAT = dwarf; 306 | ENABLE_STRICT_OBJC_MSGSEND = YES; 307 | ENABLE_TESTABILITY = YES; 308 | GCC_C_LANGUAGE_STANDARD = gnu11; 309 | GCC_DYNAMIC_NO_PIC = NO; 310 | GCC_NO_COMMON_BLOCKS = YES; 311 | GCC_OPTIMIZATION_LEVEL = 0; 312 | GCC_PREPROCESSOR_DEFINITIONS = ( 313 | "DEBUG=1", 314 | "$(inherited)", 315 | ); 316 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 317 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 318 | GCC_WARN_UNDECLARED_SELECTOR = YES; 319 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 320 | GCC_WARN_UNUSED_FUNCTION = YES; 321 | GCC_WARN_UNUSED_VARIABLE = YES; 322 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 323 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 324 | MTL_FAST_MATH = YES; 325 | ONLY_ACTIVE_ARCH = YES; 326 | SDKROOT = iphoneos; 327 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 328 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 329 | }; 330 | name = Debug; 331 | }; 332 | 975D0D3223D5F5CF00DFFC35 /* Release */ = { 333 | isa = XCBuildConfiguration; 334 | buildSettings = { 335 | ALWAYS_SEARCH_USER_PATHS = NO; 336 | CLANG_ANALYZER_NONNULL = YES; 337 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 338 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 339 | CLANG_CXX_LIBRARY = "libc++"; 340 | CLANG_ENABLE_MODULES = YES; 341 | CLANG_ENABLE_OBJC_ARC = YES; 342 | CLANG_ENABLE_OBJC_WEAK = YES; 343 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 344 | CLANG_WARN_BOOL_CONVERSION = YES; 345 | CLANG_WARN_COMMA = YES; 346 | CLANG_WARN_CONSTANT_CONVERSION = YES; 347 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 348 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 349 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 356 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | COPY_PHASE_STRIP = NO; 366 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 367 | ENABLE_NS_ASSERTIONS = NO; 368 | ENABLE_STRICT_OBJC_MSGSEND = YES; 369 | GCC_C_LANGUAGE_STANDARD = gnu11; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 372 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 373 | GCC_WARN_UNDECLARED_SELECTOR = YES; 374 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 375 | GCC_WARN_UNUSED_FUNCTION = YES; 376 | GCC_WARN_UNUSED_VARIABLE = YES; 377 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 378 | MTL_ENABLE_DEBUG_INFO = NO; 379 | MTL_FAST_MATH = YES; 380 | SDKROOT = iphoneos; 381 | SWIFT_COMPILATION_MODE = wholemodule; 382 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 383 | VALIDATE_PRODUCT = YES; 384 | }; 385 | name = Release; 386 | }; 387 | 975D0D3423D5F5CF00DFFC35 /* Debug */ = { 388 | isa = XCBuildConfiguration; 389 | buildSettings = { 390 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 391 | CODE_SIGN_ENTITLEMENTS = SYPictureMetadataExample/SYPictureMetadataExample.entitlements; 392 | CODE_SIGN_STYLE = Automatic; 393 | DEVELOPMENT_TEAM = 79RY8264V4; 394 | INFOPLIST_FILE = SYPictureMetadataExample/Info.plist; 395 | LD_RUNPATH_SEARCH_PATHS = ( 396 | "$(inherited)", 397 | "@executable_path/Frameworks", 398 | ); 399 | OTHER_SWIFT_FLAGS = "-D EXAMPLE $(inherited)"; 400 | PRODUCT_BUNDLE_IDENTIFIER = me.syan.SYPictureMetadataExample; 401 | PRODUCT_NAME = "$(TARGET_NAME)"; 402 | SUPPORTS_MACCATALYST = YES; 403 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 404 | SWIFT_VERSION = 5.0; 405 | TARGETED_DEVICE_FAMILY = "1,2"; 406 | }; 407 | name = Debug; 408 | }; 409 | 975D0D3523D5F5CF00DFFC35 /* Release */ = { 410 | isa = XCBuildConfiguration; 411 | buildSettings = { 412 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 413 | CODE_SIGN_ENTITLEMENTS = SYPictureMetadataExample/SYPictureMetadataExample.entitlements; 414 | CODE_SIGN_STYLE = Automatic; 415 | DEVELOPMENT_TEAM = 79RY8264V4; 416 | INFOPLIST_FILE = SYPictureMetadataExample/Info.plist; 417 | LD_RUNPATH_SEARCH_PATHS = ( 418 | "$(inherited)", 419 | "@executable_path/Frameworks", 420 | ); 421 | OTHER_SWIFT_FLAGS = "-D EXAMPLE $(inherited)"; 422 | PRODUCT_BUNDLE_IDENTIFIER = me.syan.SYPictureMetadataExample; 423 | PRODUCT_NAME = "$(TARGET_NAME)"; 424 | SUPPORTS_MACCATALYST = YES; 425 | SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; 426 | SWIFT_VERSION = 5.0; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | }; 429 | name = Release; 430 | }; 431 | /* End XCBuildConfiguration section */ 432 | 433 | /* Begin XCConfigurationList section */ 434 | 975D0D1A23D5F5CD00DFFC35 /* Build configuration list for PBXProject "SYPictureMetadataExample" */ = { 435 | isa = XCConfigurationList; 436 | buildConfigurations = ( 437 | 975D0D3123D5F5CF00DFFC35 /* Debug */, 438 | 975D0D3223D5F5CF00DFFC35 /* Release */, 439 | ); 440 | defaultConfigurationIsVisible = 0; 441 | defaultConfigurationName = Release; 442 | }; 443 | 975D0D3323D5F5CF00DFFC35 /* Build configuration list for PBXNativeTarget "SYPictureMetadataExample" */ = { 444 | isa = XCConfigurationList; 445 | buildConfigurations = ( 446 | 975D0D3423D5F5CF00DFFC35 /* Debug */, 447 | 975D0D3523D5F5CF00DFFC35 /* Release */, 448 | ); 449 | defaultConfigurationIsVisible = 0; 450 | defaultConfigurationName = Release; 451 | }; 452 | /* End XCConfigurationList section */ 453 | 454 | /* Begin XCLocalSwiftPackageReference section */ 455 | 97230E152BFD510500CDB64D /* XCLocalSwiftPackageReference ".." */ = { 456 | isa = XCLocalSwiftPackageReference; 457 | relativePath = ..; 458 | }; 459 | /* End XCLocalSwiftPackageReference section */ 460 | 461 | /* Begin XCSwiftPackageProductDependency section */ 462 | 97230E162BFD510500CDB64D /* SYPictureMetadata */ = { 463 | isa = XCSwiftPackageProductDependency; 464 | productName = SYPictureMetadata; 465 | }; 466 | 97230E182BFD510500CDB64D /* SYPictureMetadataTestAssets */ = { 467 | isa = XCSwiftPackageProductDependency; 468 | productName = SYPictureMetadataTestAssets; 469 | }; 470 | 976E79542BFD3BB200336C5C /* SYPictureMetadata */ = { 471 | isa = XCSwiftPackageProductDependency; 472 | productName = SYPictureMetadata; 473 | }; 474 | /* End XCSwiftPackageProductDependency section */ 475 | }; 476 | rootObject = 975D0D1723D5F5CD00DFFC35 /* Project object */; 477 | } 478 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample.xcodeproj/xcshareddata/xcschemes/SYPictureMetadataExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 44 | 46 | 52 | 53 | 54 | 55 | 61 | 63 | 69 | 70 | 71 | 72 | 74 | 75 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample.xcodeproj/xcshareddata/xcschemes/SYPictureMetadataTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 20/01/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SYPictureMetadata 11 | import SYPictureMetadataTestAssets 12 | 13 | // TODO: use property wrappers when they can reference self 14 | // TODO: print stats of untested but supported keys (needs property wrappers?) 15 | 16 | @UIApplicationMain 17 | class AppDelegate: UIResponder, UIApplicationDelegate { 18 | 19 | var window: UIWindow? 20 | 21 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 22 | // Override point for customization after application launch. 23 | testAddIPTC() 24 | testStripAll() 25 | return true 26 | } 27 | 28 | // README Examples 29 | func testAddIPTC() { 30 | let imageURL = TestFile.iptc2.url 31 | 32 | // load metadata from original file (please handle errors, the type is SYMetadata.Error) 33 | let metadata = try! SYMetadata(fileURL: imageURL) 34 | 35 | // create IPTC container if not present 36 | if (metadata.metadataIPTC == nil) { 37 | metadata.metadataIPTC = SYMetadataIPTC() 38 | } 39 | 40 | // edit metadata 41 | metadata.metadataIPTC?.keywords = ["Some test keywords", "added by SYMetadata example app"]; 42 | metadata.metadataIPTC?.city = "Lyon"; 43 | metadata.metadataIPTC?.credit = "© Me 2017"; 44 | 45 | // create new image data with original image data and edited metadata 46 | let originalImageData = try! Data(contentsOf: imageURL) 47 | let imageDataWithMetadata = try! metadata.apply(to: originalImageData) 48 | 49 | // log the delta in file size 50 | print("File size delta:", imageDataWithMetadata.count - originalImageData.count); 51 | // File size delta: 60325 52 | 53 | // load metadata for newly cerated image 54 | let reloadedMetadata = try! SYMetadata(imageData: imageDataWithMetadata) 55 | 56 | // log the differences between files 57 | print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: true).jsonString) 58 | // Differences: 59 | // { 60 | // "{Exif}" : { 61 | // "ShutterSpeedValue" : "Updated: 8.643856 -> 8.643855995239512" 62 | // }, 63 | // "{IPTC}" : { 64 | // "City" : "Added: Lyon", 65 | // "Credit" : "Added: © Me 2017", 66 | // "Keywords" : "Updated: beach, baywatch, LA, sunset -> Some test keywords, added by SYMetadata example app" 67 | // }, 68 | // "{JFIF}" : { 69 | // "DensityUnit" : "Added: 0", 70 | // "JFIFVersion" : "Added: 1, 0, 1", 71 | // "XDensity" : "Added: 72", 72 | // "YDensity" : "Added: 72" 73 | // } 74 | // } 75 | } 76 | 77 | func testStripAll() { 78 | let imageURL = TestFile.iptc2.url 79 | 80 | // load metadata from original file (please handle errors, the type is SYMetadata.Error) 81 | let metadata = try! SYMetadata(fileURL: imageURL) 82 | 83 | // create new image data with original image data and strip all metadata 84 | let originalImageData = try! Data(contentsOf: imageURL) 85 | let imageDataWithoutMetadata = try! SYMetadata.stripAllMetadata(from: originalImageData) 86 | 87 | // log the delta in file size 88 | print("File size delta:", imageDataWithoutMetadata.count - originalImageData.count); 89 | // File size delta: 73491 90 | 91 | // load metadata for newly cerated image 92 | let reloadedMetadata = try! SYMetadata(imageData: imageDataWithoutMetadata) 93 | 94 | // log the differences between files 95 | print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: false).jsonString) 96 | // Differences: 97 | // { 98 | // "{Exif}" : { 99 | // "ApertureValue" : "Removed", 100 | // "ComponentsConfiguration" : "Removed", 101 | // "CompressedBitsPerPixel" : "Removed", 102 | // "Contrast" : "Removed", 103 | // "CustomRendered" : "Removed", 104 | // "DateTimeDigitized" : "Removed", 105 | // "DateTimeOriginal" : "Removed", 106 | // "DigitalZoomRatio" : "Removed", 107 | // "ExifVersion" : "Removed", 108 | // "ExposureBiasValue" : "Removed", 109 | // "ExposureMode" : "Removed", 110 | // "ExposureProgram" : "Removed", 111 | // "ExposureTime" : "Removed", 112 | // "FileSource" : "Removed", 113 | // "Flash" : "Removed", 114 | // "FlashPixVersion" : "Removed", 115 | // "FNumber" : "Removed", 116 | // "FocalLength" : "Removed", 117 | // "FocalLenIn35mmFilm" : "Removed", 118 | // "GainControl" : "Removed", 119 | // "ISOSpeedRatings" : "Removed", 120 | // "LightSource" : "Removed", 121 | // "MaxApertureValue" : "Removed", 122 | // "MeteringMode" : "Removed", 123 | // "Saturation" : "Removed", 124 | // "SceneCaptureType" : "Removed", 125 | // "SceneType" : "Removed", 126 | // "SensingMethod" : "Removed", 127 | // "Sharpness" : "Removed", 128 | // "ShutterSpeedValue" : "Removed", 129 | // "WhiteBalance" : "Removed" 130 | // }, 131 | // "{IPTC}" : { 132 | // "Byline" : "Removed", 133 | // "BylineTitle" : "Removed", 134 | // "Caption\/Abstract" : "Removed", 135 | // "CopyrightNotice" : "Removed", 136 | // "DateCreated" : "Removed", 137 | // "DigitalCreationDate" : "Removed", 138 | // "DigitalCreationTime" : "Removed", 139 | // "Keywords" : "Removed", 140 | // "ObjectName" : "Removed", 141 | // "TimeCreated" : "Removed" 142 | // }, 143 | // "{JFIF}" : { 144 | // "DensityUnit" : "Added", 145 | // "IsProgressive" : "Removed", 146 | // "JFIFVersion" : "Added", 147 | // "XDensity" : "Added", 148 | // "YDensity" : "Added" 149 | // }, 150 | // "{TIFF}" : { 151 | // "Artist" : "Removed", 152 | // "Copyright" : "Removed", 153 | // "DateTime" : "Removed", 154 | // "ImageDescription" : "Removed", 155 | // "Make" : "Removed", 156 | // "Model" : "Removed", 157 | // "PhotometricInterpretation" : "Removed", 158 | // "ResolutionUnit" : "Removed", 159 | // "Software" : "Removed", 160 | // "XResolution" : "Removed", 161 | // "YResolution" : "Removed" 162 | // }, 163 | // "DPIHeight" : "Removed", 164 | // "DPIWidth" : "Removed" 165 | // } 166 | 167 | // log kept metadata 168 | print("Kept metadata:\n", reloadedMetadata.originalDictionary.jsonString) 169 | // Kept metadata: 170 | // { 171 | // "{Exif}" : { 172 | // "ColorSpace" : 1, 173 | // "PixelXDimension" : 1920, 174 | // "PixelYDimension" : 1080 175 | // }, 176 | // "{JFIF}" : { 177 | // "DensityUnit" : 0, 178 | // "JFIFVersion" : [ 179 | // 1, 180 | // 0, 181 | // 1 182 | // ], 183 | // "XDensity" : 72, 184 | // "YDensity" : 72 185 | // }, 186 | // "{TIFF}" : { 187 | // "Orientation" : 1 188 | // }, 189 | // "ColorModel" : "RGB", 190 | // "Depth" : 8, 191 | // "Orientation" : 1, 192 | // "PixelHeight" : 1080, 193 | // "PixelWidth" : 1920, 194 | // "ProfileName" : "sRGB IEC61966-2.1" 195 | // } 196 | } 197 | } 198 | 199 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/Cells/SYSummaryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYSummaryCell.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 09/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SYSummaryCell: UITableViewCell { 12 | 13 | // MARK: Init 14 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 15 | super.init(style: .subtitle, reuseIdentifier: reuseIdentifier) 16 | textLabel?.numberOfLines = 0 17 | detailTextLabel?.numberOfLines = 0 18 | } 19 | 20 | required init?(coder: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | NSPhotoLibraryUsageDescription 45 | Example 46 | NSPhotoLibraryAddUsageDescription 47 | Example 48 | 49 | 50 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 160 | 161 | 162 | 163 | 164 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/SYPictureMetadataExample.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.network.client 8 | 9 | com.apple.security.personal-information.photos-library 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/ViewControllers/SYImageVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYImageVC.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 09/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SYPictureMetadata 11 | 12 | class SYImageVC: UIViewController { 13 | 14 | // MARK: ViewController 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | } 18 | 19 | override func viewWillAppear(_ animated: Bool) { 20 | super.viewWillAppear(animated) 21 | updateContent() 22 | } 23 | 24 | // MARK: Properties 25 | var pictureURL: URL? { 26 | didSet { 27 | updateContent() 28 | } 29 | } 30 | var index: Int = 0 31 | 32 | // MARK: Views 33 | @IBOutlet private var imageView: UIImageView! 34 | @IBOutlet private var textView: UITextView! 35 | 36 | // MARK: Actions 37 | 38 | // MARK: Content 39 | private func updateContent() { 40 | guard isViewLoaded else { return } 41 | guard let pictureURL = pictureURL else { return } 42 | 43 | imageView.image = UIImage(contentsOfFile: pictureURL.path) 44 | 45 | do { 46 | let metadata = try SYMetadata(fileURL: pictureURL) 47 | textView.text = metadata.originalDictionary.jsonString 48 | } 49 | catch { 50 | textView.text = "Couldn't open file: \(error)" 51 | } 52 | 53 | textView.scrollRectToVisible(CGRect(x: 0, y: 0, width: 1, height: 1), animated: false) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/ViewControllers/SYImageVC.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | Lorem ipsum dolor sit er elit lamet, consectetaur cillium adipisicing pecu, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Nam liber te conscient to factor tum poen legum odioque civiuda. 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/ViewControllers/SYImagesVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYImagesVC.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 09/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SYPictureMetadata 11 | import SYPictureMetadataTestAssets 12 | 13 | class SYImagesVC : UIPageViewController { 14 | // MARK: UIViewController 15 | override func viewDidLoad() { 16 | super.viewDidLoad() 17 | dataSource = self 18 | delegate = self 19 | loadURLs() 20 | } 21 | 22 | override func viewWillAppear(_ animated: Bool) { 23 | super.viewWillAppear(animated) 24 | openIndex(0, animated: false, forceForwardAnimation: false) 25 | } 26 | 27 | override var preferredStatusBarStyle: UIStatusBarStyle { 28 | return .lightContent 29 | } 30 | 31 | // MARK: Properties 32 | private var nextIndex: Int = 0 33 | private var currentIndex: Int = 0 { 34 | didSet { 35 | navigationItem.title = urls[currentIndex].lastPathComponent 36 | } 37 | } 38 | private var urls: [URL] = [] { 39 | didSet { 40 | openIndex(currentIndex, animated: false, forceForwardAnimation: false) 41 | } 42 | } 43 | 44 | // MARK: Content 45 | private func loadURLs() { 46 | self.urls = TestFile.allCases.map(\.url) 47 | } 48 | 49 | // MARK: Child ViewControllers 50 | private func viewController(at newIndex: Int) -> SYImageVC? { 51 | guard urls.count > 0 else { return nil } 52 | let index = (newIndex + urls.count) % urls.count 53 | guard index < urls.count else { return nil } 54 | let url = urls[index] 55 | 56 | let vc = SYImageVC() 57 | vc.pictureURL = url 58 | vc.index = index 59 | 60 | return vc 61 | } 62 | 63 | private func openIndex(_ index: Int, animated: Bool, forceForwardAnimation: Bool) { 64 | guard let vc = viewController(at: index) else { return } 65 | 66 | var direction = UIPageViewController.NavigationDirection.forward 67 | if (index < currentIndex && !forceForwardAnimation) { 68 | // going backwards 69 | direction = .reverse 70 | } 71 | 72 | currentIndex = index 73 | setViewControllers([vc], direction: direction, animated: animated, completion: nil) 74 | } 75 | } 76 | 77 | extension SYImagesVC : UIPageViewControllerDataSource { 78 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { 79 | // disable infinity scroll when only has a single item 80 | if urls.count < 2 { return nil } 81 | 82 | guard let prevVC = viewController as? SYImageVC else { return nil } 83 | return self.viewController(at: prevVC.index - 1) 84 | } 85 | 86 | func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { 87 | // disable infinity scroll when only has a single item 88 | if urls.count < 2 { return nil } 89 | 90 | guard let prevVC = viewController as? SYImageVC else { return nil } 91 | return self.viewController(at: prevVC.index + 1) 92 | } 93 | } 94 | 95 | extension SYImagesVC : UIPageViewControllerDelegate { 96 | func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { 97 | if let vc = pendingViewControllers.first as? SYImageVC { 98 | nextIndex = vc.index 99 | } 100 | } 101 | 102 | func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { 103 | if completed { 104 | currentIndex = nextIndex 105 | } 106 | nextIndex = 0 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/ViewControllers/SYOpenPhotoVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYOpenPhotoVC.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 02/02/2021. 6 | // Copyright © 2021 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import PhotosUI 11 | import SYPictureMetadata 12 | 13 | class SYOpenPhotoVC : UIViewController { 14 | 15 | // MARK: ViewController 16 | override func viewDidLoad() { 17 | super.viewDidLoad() 18 | updateContent() 19 | } 20 | 21 | // MARK: Properties 22 | private var asset: PHAsset? { 23 | didSet { 24 | updateContent() 25 | } 26 | } 27 | 28 | // MARK: Views 29 | @IBOutlet private var imageView: UIImageView! 30 | @IBOutlet private var textView: UITextView! 31 | 32 | // MARK: Actions 33 | @IBAction private func openButtonTap() { 34 | askPermission { 35 | self.showPicker() 36 | } 37 | } 38 | 39 | private func askPermission(success: @escaping () -> Void) { 40 | let status = PHPhotoLibrary.authorizationStatus() 41 | if status == .authorized { 42 | return success() 43 | } 44 | PHPhotoLibrary.requestAuthorization { (newStatus) in 45 | if (newStatus == PHAuthorizationStatus.authorized) { 46 | DispatchQueue.main.async { 47 | success() 48 | } 49 | } else { 50 | let alert = UIAlertController(title: "Access Denied", message: nil, preferredStyle: .alert) 51 | alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) 52 | self.present(alert, animated: true, completion: nil) 53 | } 54 | } 55 | } 56 | 57 | private func showPicker() { 58 | let picker = UIImagePickerController() 59 | picker.delegate = self 60 | picker.allowsEditing = false 61 | picker.sourceType = .photoLibrary 62 | picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary) ?? [] 63 | present(picker, animated: true, completion: nil) 64 | } 65 | 66 | // MARK: Content 67 | private func updateContent() { 68 | guard let asset = asset else { 69 | title = "Open Photo" 70 | imageView.image = nil 71 | textView.text = nil 72 | return 73 | } 74 | 75 | PHImageManager.default().requestImage(for: asset, targetSize: imageView.bounds.size, contentMode: .aspectFit, options: nil) { (image, _) in 76 | self.imageView.image = image 77 | } 78 | 79 | SYMetadata.metadata(asset: asset, networkAccessAllowed: false) { (metadata, error) in 80 | if let error = error { 81 | self.textView.text = error.localizedDescription 82 | } 83 | else if let metadata = metadata { 84 | self.textView.text = [ 85 | "CAPTION: \(asset.assetCaption ?? "")", 86 | metadata.originalDictionary.jsonString 87 | ].joined(separator: "\n\n") 88 | } 89 | } 90 | } 91 | } 92 | 93 | extension SYOpenPhotoVC : UIImagePickerControllerDelegate, UINavigationControllerDelegate { 94 | func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { 95 | dismiss(animated: true, completion: nil) 96 | self.asset = info[.phAsset] as? PHAsset 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/SYPictureMetadataExample/ViewControllers/SYSummaryVC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYSummaryVC.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 09/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SYPictureMetadata 11 | import SYPictureMetadataTestAssets 12 | import Photos 13 | 14 | class SYSummaryVC: UIViewController { 15 | 16 | // MARK: UIViewController 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | title = "Summary" 20 | tableView.register(SYSummaryCell.self, forCellReuseIdentifier: "SYSummaryCell") 21 | } 22 | 23 | override var preferredStatusBarStyle: UIStatusBarStyle { 24 | return .lightContent 25 | } 26 | 27 | // MARK: Views 28 | @IBOutlet private var tableView: UITableView! 29 | 30 | // MARK: Data 31 | private enum Section: Int, CaseIterable { 32 | case supportedKeys 33 | case dataSetAnalysis 34 | } 35 | 36 | // MARK: Image analysis 37 | 38 | // Supported keys 39 | private func detailedSupportedKeys() -> String { 40 | let imageIO = MetadataKeys.imageIO.read().count 41 | let supported = MetadataKeys.supported.read().count 42 | let unsupported = MetadataKeys.unsupported.read().count 43 | let ratio = Int(Float(supported) / Float(imageIO) * 100) 44 | 45 | var details = [String]() 46 | details.append("ImageIO keys: \(imageIO)") 47 | details.append("Supported keys: \(supported)") 48 | details.append("Unsupported keys: \(unsupported)") 49 | details.append("Current support: \(ratio)%") 50 | return details.joined(separator: "\n") 51 | } 52 | 53 | // Determine files that have the most data for tests 54 | private func detailedAnalyzeOfImagesSet() -> String { 55 | var details = [String]() 56 | 57 | // first we load all metadatas 58 | var metadatas = [URL: SYMetadata]() 59 | var unreadableFiles = [URL]() 60 | for file in TestFile.allCases { 61 | do { 62 | let metadata = try SYMetadata(fileURL: file.url) 63 | metadatas[file.url] = metadata 64 | } 65 | catch { 66 | unreadableFiles.append(file.url) 67 | } 68 | } 69 | 70 | // add to log 71 | if unreadableFiles.count > 0 { 72 | details.append("Couldn't read metadata for files:") 73 | details.append(contentsOf: unreadableFiles.map { " - " + $0.path }) 74 | details.append("") 75 | } 76 | 77 | // detect images that have the same list of keys 78 | var keySets = [[String]: [URL]]() 79 | for (url, metadata) in metadatas { 80 | let keys = metadata.originalDictionary.allKeyPaths.sorted() 81 | var urlsWithSameKeys = keySets[keys] ?? [] 82 | urlsWithSameKeys.append(url) 83 | keySets[keys] = urlsWithSameKeys 84 | } 85 | 86 | for (set, urls) in keySets { 87 | guard urls.count > 1 else { continue } 88 | 89 | // add to log 90 | details.append("Images using same set of metadata keys:") 91 | details.append(contentsOf: urls.map { " - " + $0.lastPathComponent }) 92 | details.append("") 93 | 94 | // remove from further analysis 95 | keySets.removeValue(forKey: set) 96 | } 97 | 98 | 99 | // now we compare each metadata to each other ones to determine if a file is 100 | // pertinent (i.e. it has some metadata that no other have) 101 | var readKeys = Set() 102 | var uniquesKeysByFile = [URL: Int]() 103 | 104 | for (url1, metadata1) in metadatas { 105 | var uniqueKeys = Set(metadata1.originalDictionary.allKeyPaths) 106 | readKeys = readKeys.union(uniqueKeys) 107 | 108 | for (url2, metadata2) in metadatas { 109 | guard url1 != url2 else { continue } 110 | 111 | let keys2 = Set(metadata2.originalDictionary.allKeyPaths) 112 | uniqueKeys = uniqueKeys.subtracting(keys2) 113 | } 114 | 115 | uniquesKeysByFile[url1] = uniqueKeys.count 116 | } 117 | 118 | // add useless files to log 119 | let uselessFiles = uniquesKeysByFile.filter { $0.value == 0 }.keys 120 | if uselessFiles.count > 0 { 121 | details.append("Images using keys all present in other files of the set (e.g. not relevant):") 122 | details.append(contentsOf: uselessFiles.map { " - " + $0.lastPathComponent }) 123 | details.append("") 124 | } 125 | uniquesKeysByFile = uniquesKeysByFile.filter { $0.value > 0 } 126 | 127 | // add unique keys count to log 128 | details.append("Number of unique keys by file:") 129 | details.append(contentsOf: uniquesKeysByFile.map { " - \($0.value) for \($0.key.lastPathComponent)" }) 130 | details.append("") 131 | 132 | // add total keys to log 133 | details.append("Currrent set of files uses \(readKeys.count) keys out of \(MetadataKeys.supported.read().count) supported") 134 | 135 | return details.joined(separator: "\n") 136 | } 137 | } 138 | 139 | extension SYSummaryVC : UITableViewDataSource { 140 | func numberOfSections(in tableView: UITableView) -> Int { 141 | return Section.allCases.count 142 | } 143 | 144 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 145 | switch Section(rawValue: section)! { 146 | case .dataSetAnalysis: return "Data set analysis" 147 | case .supportedKeys: return "Supported keys" 148 | } 149 | } 150 | 151 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 152 | switch Section(rawValue: section)! { 153 | case .dataSetAnalysis: return 1 154 | case .supportedKeys: return 1 155 | } 156 | } 157 | 158 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 159 | let cell = tableView.dequeueReusableCell(withIdentifier: "SYSummaryCell", for: indexPath) as! SYSummaryCell 160 | 161 | switch Section(rawValue: indexPath.section)! { 162 | case .supportedKeys: 163 | cell.textLabel?.text = nil 164 | cell.detailTextLabel?.text = detailedSupportedKeys() 165 | case .dataSetAnalysis: 166 | cell.textLabel?.text = nil 167 | cell.detailTextLabel?.text = detailedAnalyzeOfImagesSet() 168 | } 169 | return cell 170 | } 171 | } 172 | 173 | extension SYSummaryVC : UITableViewDelegate { 174 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 175 | return UITableView.automaticDimension 176 | } 177 | 178 | func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { 179 | return 100 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Use it as you like in every project you want, redistribute with mentions of my name and don't blame me if it breaks :) 2 | 3 | -- dvkch -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "SYPictureMetadata", 8 | platforms: [ 9 | .iOS(.v12), 10 | .tvOS(.v12), 11 | .macOS(.v10_14), 12 | .visionOS(.v1) 13 | ], 14 | products: [ 15 | .library(name: "SYPictureMetadata", targets: ["SYPictureMetadata"]), 16 | .library(name: "SYPictureMetadataTestAssets", targets: ["SYPictureMetadataTestAssets"]), 17 | ], 18 | dependencies: [], 19 | targets: [ 20 | .target(name: "SYPictureMetadata"), 21 | .target(name: "SYPictureMetadataTestAssets", dependencies: ["SYPictureMetadata"], resources: [.copy("MetadataKeys"), .copy("TestFile")]), 22 | .testTarget(name: "SYPictureMetadataTests", dependencies: ["SYPictureMetadata", "SYPictureMetadataTestAssets"]), 23 | ], 24 | swiftLanguageVersions: [ 25 | .v5 26 | ] 27 | ) 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SYPictureMetadata 2 | 3 | Read and write images metadata using `ImageIO` and easy to use models. 4 | 5 | ## Screenshots 6 | 7 | Data set analysis 8 | Metadata preview 9 | 10 | ## Metadata support 11 | 12 | #### ImageIO Keys 13 | 14 | A lot of keys have been added to ImageIO since iOS 11, they are mostly about DNG, HEIC and IPTC Ext data and are not currently supported. The full list is available in [Keys/Unsupported.txt](Keys/Unsupported.txt). Feel free to send a PR my way, ideally with a test case :) 15 | 16 | Currently we support 325 keys out of 628 defined keys in iOS 14.4, or about 51%. 17 | 18 | #### Test coverage 19 | 20 | There are basic tests in place currently testing around 32% of defined getters and 3% of defined setters. If you need to support specific attributes in your application you should test those too, and you're welcome to send a PR to increase coverage in this library. 21 | 22 | Xcode reports 36.4% of test coverage for this library. 23 | 24 | #### Photos captions (iOS 14) 25 | 26 | I have added preliminary support for iOS 14 photo captions. Since this data is not stored in the image data like any other metadata read but `ImageIO` until the image is exported, I made a quick `PHAsset` extension with a read-only property called `assetCaption` to obtain this information. 27 | 28 | More details in [SYMetadataExtensions.swift](https://github.com/dvkch/SYPictureMetadata/blob/main/Sources/SYPictureMetadata/SYMetadataExtensions.swift) 29 | 30 | ## Keep in mind 31 | 32 | When saving metadata to a file you can encouter the following issues: 33 | 34 | - metadata was not written 35 | - metadata was modified a bit 36 | - metadata was added 37 | - metadata should have been removed but was kept because ImageIO found a similar metadata in another namespace (e.g. removing TIFF.artist doesn't work if IPTC.byLine is present) 38 | 39 | This library uses `ImageIO` which has its own limitations and performs its own value checks. This is just a wrapper around the `NSDictionary` structure this framework exposes and is not perfect. Please heavily test your app if you use this library to edit metadata, a set of test images for tests can be found in the example app. This library DOES NOT garantee data integrity like `libexif` or `exiv2` would. 40 | 41 | ## Examples 42 | 43 | #### Adding IPTC data example: 44 | 45 | ``` 46 | // this is available in the Example project 47 | let imageURL = TestFile.iptc2.url! 48 | 49 | // load metadata from original file (please handle errors, the type is SYMetadata.Error) 50 | let metadata = try! SYMetadata(fileURL: imageURL) 51 | 52 | // create IPTC container if not present 53 | if (metadata.metadataIPTC == nil) { 54 | metadata.metadataIPTC = SYMetadataIPTC() 55 | } 56 | 57 | // edit metadata 58 | metadata.metadataIPTC?.keywords = ["Some test keywords", "added by SYMetadata example app"]; 59 | metadata.metadataIPTC?.city = "Lyon"; 60 | metadata.metadataIPTC?.credit = "© Me 2017"; 61 | 62 | // create new image data with original image data and edited metadata 63 | let originalImageData = try! Data(contentsOf: imageURL) 64 | let imageDataWithMetadata = try! metadata.apply(to: originalImageData) 65 | 66 | // log the delta in file size 67 | print("File size delta:", imageDataWithMetadata.count - originalImageData.count); 68 | // File size delta: 60325 69 | 70 | // load metadata for newly cerated image 71 | let reloadedMetadata = try! SYMetadata(imageData: imageDataWithMetadata) 72 | 73 | // log the differences between files 74 | print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: true).jsonString) 75 | // Differences: 76 | // { 77 | // "{Exif}" : { 78 | // "ShutterSpeedValue" : "Updated: 8.643856 -> 8.643855995239512" 79 | // }, 80 | // "{IPTC}" : { 81 | // "City" : "Added: Lyon", 82 | // "Credit" : "Added: © Me 2017", 83 | // "Keywords" : "Updated: beach, baywatch, LA, sunset -> Some test keywords, added by SYMetadata example app" 84 | // }, 85 | // "{JFIF}" : { 86 | // "DensityUnit" : "Added: 0", 87 | // "JFIFVersion" : "Added: 1, 0, 1", 88 | // "XDensity" : "Added: 72", 89 | // "YDensity" : "Added: 72" 90 | // } 91 | // } 92 | ``` 93 | 94 | #### Stripping all metadata 95 | 96 | ``` 97 | let imageURL = TestFile.iptc2.url! 98 | 99 | // load metadata from original file (please handle errors, the type is SYMetadata.Error) 100 | let metadata = try! SYMetadata(fileURL: imageURL) 101 | 102 | // create new image data with original image data and strip all metadata 103 | let originalImageData = try! Data(contentsOf: imageURL) 104 | let imageDataWithoutMetadata = try! SYMetadata.stripAllMetadata(from: originalImageData) 105 | 106 | // log the delta in file size 107 | print("File size delta:", imageDataWithoutMetadata.count - originalImageData.count); 108 | // File size delta: 73491 109 | 110 | // load metadata for newly cerated image 111 | let reloadedMetadata = try! SYMetadata(imageData: imageDataWithoutMetadata) 112 | 113 | // log the differences between files 114 | print("Differences:\n", reloadedMetadata.originalDictionary.metadataDifferences(from: metadata.originalDictionary, includeValuesInDiff: false).jsonString) 115 | // Differences: 116 | // { 117 | // "{Exif}" : { 118 | // "ApertureValue" : "Removed", 119 | // "ComponentsConfiguration" : "Removed", 120 | // "CompressedBitsPerPixel" : "Removed", 121 | // "Contrast" : "Removed", 122 | // "CustomRendered" : "Removed", 123 | // "DateTimeDigitized" : "Removed", 124 | // "DateTimeOriginal" : "Removed", 125 | // "DigitalZoomRatio" : "Removed", 126 | // "ExifVersion" : "Removed", 127 | // "ExposureBiasValue" : "Removed", 128 | // "ExposureMode" : "Removed", 129 | // "ExposureProgram" : "Removed", 130 | // "ExposureTime" : "Removed", 131 | // "FileSource" : "Removed", 132 | // "Flash" : "Removed", 133 | // "FlashPixVersion" : "Removed", 134 | // "FNumber" : "Removed", 135 | // "FocalLength" : "Removed", 136 | // "FocalLenIn35mmFilm" : "Removed", 137 | // "GainControl" : "Removed", 138 | // "ISOSpeedRatings" : "Removed", 139 | // "LightSource" : "Removed", 140 | // "MaxApertureValue" : "Removed", 141 | // "MeteringMode" : "Removed", 142 | // "Saturation" : "Removed", 143 | // "SceneCaptureType" : "Removed", 144 | // "SceneType" : "Removed", 145 | // "SensingMethod" : "Removed", 146 | // "Sharpness" : "Removed", 147 | // "ShutterSpeedValue" : "Removed", 148 | // "WhiteBalance" : "Removed" 149 | // }, 150 | // "{IPTC}" : { 151 | // "Byline" : "Removed", 152 | // "BylineTitle" : "Removed", 153 | // "Caption\/Abstract" : "Removed", 154 | // "CopyrightNotice" : "Removed", 155 | // "DateCreated" : "Removed", 156 | // "DigitalCreationDate" : "Removed", 157 | // "DigitalCreationTime" : "Removed", 158 | // "Keywords" : "Removed", 159 | // "ObjectName" : "Removed", 160 | // "TimeCreated" : "Removed" 161 | // }, 162 | // "{JFIF}" : { 163 | // "DensityUnit" : "Added", 164 | // "IsProgressive" : "Removed", 165 | // "JFIFVersion" : "Added", 166 | // "XDensity" : "Added", 167 | // "YDensity" : "Added" 168 | // }, 169 | // "{TIFF}" : { 170 | // "Artist" : "Removed", 171 | // "Copyright" : "Removed", 172 | // "DateTime" : "Removed", 173 | // "ImageDescription" : "Removed", 174 | // "Make" : "Removed", 175 | // "Model" : "Removed", 176 | // "PhotometricInterpretation" : "Removed", 177 | // "ResolutionUnit" : "Removed", 178 | // "Software" : "Removed", 179 | // "XResolution" : "Removed", 180 | // "YResolution" : "Removed" 181 | // }, 182 | // "DPIHeight" : "Removed", 183 | // "DPIWidth" : "Removed" 184 | // } 185 | 186 | // log kept metadata 187 | print("Kept metadata:\n", reloadedMetadata.originalDictionary.jsonString) 188 | // Kept metadata: 189 | // { 190 | // "{Exif}" : { 191 | // "ColorSpace" : 1, 192 | // "PixelXDimension" : 1920, 193 | // "PixelYDimension" : 1080 194 | // }, 195 | // "{JFIF}" : { 196 | // "DensityUnit" : 0, 197 | // "JFIFVersion" : [ 198 | // 1, 199 | // 0, 200 | // 1 201 | // ], 202 | // "XDensity" : 72, 203 | // "YDensity" : 72 204 | // }, 205 | // "{TIFF}" : { 206 | // "Orientation" : 1 207 | // }, 208 | // "ColorModel" : "RGB", 209 | // "Depth" : 8, 210 | // "Orientation" : 1, 211 | // "PixelHeight" : 1080, 212 | // "PixelWidth" : 1920, 213 | // "ProfileName" : "sRGB IEC61966-2.1" 214 | // } 215 | ``` 216 | 217 | ## Credits 218 | 219 | You'll find links that helped me a lot in comments inside this library where appropriate. Mainly: 220 | 221 | - 222 | - 223 | - 224 | - 225 | - 226 | - 227 | - 228 | - 229 | 230 | ## License 231 | 232 | Use it as you like in every project you want, redistribute with mentions of my name and don't blame me if it breaks :) 233 | 234 | -- syan 235 | 236 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/Extensions/ImageIO+SYPictureMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataExportedKeys.h 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // not defined in ImageIO but still read by iOS 12 | let kSYImagePropertyPictureStyle: CFString = "{PictureStyle}" as CFString 13 | let kSYImagePropertyExifAuxAutoFocusInfo: CFString = "AFInfo" as CFString 14 | let kSYImagePropertyExifAuxImageStabilization: CFString = "ImageStabilization" as CFString 15 | let kSYImagePropertyCIFFMaxAperture: CFString = "MaxAperture" as CFString 16 | let kSYImagePropertyCIFFMinAperture: CFString = "MinAperture" as CFString 17 | let kSYImagePropertyCIFFUniqueModelID: CFString = "UniqueModelID" as CFString 18 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadata.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadata.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 20/01/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | import Photos 12 | 13 | // http://www.sno.phy.queensu.ca/~phil/exiftool/TagNames/index.html 14 | // http://www.exiv2.org/tags.html 15 | 16 | public class SYMetadata: SYMetadataBase { 17 | 18 | public enum Error: Swift.Error, Equatable { 19 | case photoMissingData 20 | case notAFileURL 21 | case cannotCreateSource 22 | case cannotCreateDestination 23 | case cannotCopyPropertiesAtIndexZero 24 | case cannotDetermineSourceImageType 25 | case cannotCreateDataFromDestination 26 | case emptyData 27 | case unknown 28 | } 29 | 30 | // MARK: Init 31 | public required init(dictionary: Dictionary) { 32 | super.init(dictionary: dictionary) 33 | } 34 | 35 | @available(iOS 13.0, tvOS 13.0, macCatalyst 13.0, macOS 10.15, *) 36 | public static func metadata(asset: PHAsset, networkAccessAllowed: Bool, completion: @escaping (SYMetadata?, Error?) -> Void) { 37 | let options = PHImageRequestOptions() 38 | options.isNetworkAccessAllowed = networkAccessAllowed 39 | 40 | let completion = { (data: Data?) -> Void in 41 | guard let data = data else { return completion(nil, .photoMissingData) } 42 | do { 43 | let metadata = try SYMetadata(imageData: data) 44 | completion(metadata, nil) 45 | } 46 | catch { 47 | completion(nil, (error as! Error)) 48 | } 49 | } 50 | 51 | #if os(macOS) || targetEnvironment(macCatalyst) 52 | PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { (data, _, _, _) in 53 | completion(data) 54 | } 55 | #else 56 | PHImageManager.default().requestImageDataAndOrientation(for: asset, options: options) { data, _, _, _ in 57 | completion(data) 58 | } 59 | #endif 60 | } 61 | 62 | public convenience init(fileURL: URL) throws { 63 | guard fileURL.isFileURL else { throw Error.notAFileURL } 64 | guard let source = CGImageSourceCreateWithURL(fileURL as CFURL, nil) else { throw Error.cannotCreateSource } 65 | 66 | let options = [kCGImageSourceShouldCache.string: false] 67 | guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, options as CFDictionary) as? [String: Any] else { throw Error.cannotCopyPropertiesAtIndexZero } 68 | 69 | self.init(dictionary: properties) 70 | } 71 | 72 | public convenience init(imageData: Data) throws { 73 | guard imageData.count > 0 else { throw Error.emptyData } 74 | guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else { throw Error.cannotCreateSource } 75 | 76 | let options = [kCGImageSourceShouldCache.string: false] 77 | guard let properties = CGImageSourceCopyPropertiesAtIndex(source, 0, options as CFDictionary) as? [String: Any] else { throw Error.cannotCopyPropertiesAtIndexZero } 78 | 79 | self.init(dictionary: properties) 80 | } 81 | 82 | // MARK: Image Data 83 | public static func apply(metadata: [String: Any], to originalImageData: Data) throws -> Data { 84 | // https://github.com/Nikita2k/SimpleExif/blob/master/Classes/ios/UIImage%2BExif.m 85 | guard let source = CGImageSourceCreateWithData(originalImageData as CFData, nil) else { 86 | throw Error.cannotCreateSource 87 | } 88 | 89 | guard let sourceImageType = CGImageSourceGetType(source) else { 90 | throw Error.cannotDetermineSourceImageType 91 | } 92 | 93 | let count = CGImageSourceGetCount(source) 94 | 95 | // create a new data object and write the new image into it 96 | let data = NSMutableData() 97 | guard let destination = CGImageDestinationCreateWithData(data as CFMutableData, sourceImageType, count, nil) else { 98 | throw Error.cannotCreateDestination 99 | } 100 | 101 | // add the image contained in the image source to the destination, overidding the old metadata with our modified 102 | CGImageDestinationSetProperties(destination, metadata as CFDictionary) 103 | (0.. Data { 116 | return try SYMetadata.apply(metadata: currentDictionary, to: originalImageData) 117 | } 118 | 119 | public static func stripAllMetadata(from originalImageData: Data) throws -> Data { 120 | let metadata = try SYMetadata(imageData: originalImageData) 121 | // As per documentation, if you need a key removed, assign it kCFNull 122 | let keysToKeep = [kCGImagePropertyOrientation.string] 123 | let keysToClear = metadata.originalDictionary.keys.filter { k in !keysToKeep.contains(k) } 124 | let clearDic = keysToClear.reduce(into: [String: Any]()) { res, key in res[key] = kCFNull } 125 | return try SYMetadata.apply(metadata: clearDic, to: originalImageData) 126 | } 127 | 128 | // MARK: Children 129 | internal override class var childrenMappings: [String: SYMetadataBase.Type] { 130 | return [ 131 | kCGImagePropertyTIFFDictionary.string: SYMetadataTIFF.self, 132 | kCGImagePropertyExifDictionary.string: SYMetadataExif.self, 133 | kCGImagePropertyExifAuxDictionary.string: SYMetadataExifAux.self, 134 | kCGImagePropertyGIFDictionary.string: SYMetadataGIF.self, 135 | kCGImagePropertyJFIFDictionary.string: SYMetadataJFIF.self, 136 | kCGImagePropertyPNGDictionary.string: SYMetadataPNG.self, 137 | kCGImagePropertyIPTCDictionary.string: SYMetadataIPTC.self, 138 | kCGImagePropertyGPSDictionary.string: SYMetadataGPS.self, 139 | kCGImagePropertyRawDictionary.string: SYMetadataRaw.self, 140 | kCGImagePropertyCIFFDictionary.string: SYMetadataCIFF.self, 141 | kCGImagePropertyMakerAppleDictionary.string: SYMetadataMakerApple.self, 142 | kCGImagePropertyMakerCanonDictionary.string: SYMetadataMakerCanon.self, 143 | kCGImagePropertyMakerNikonDictionary.string: SYMetadataMakerNikon.self, 144 | kCGImagePropertyMakerMinoltaDictionary.string: SYMetadataMakerMinolta.self, 145 | kCGImagePropertyMakerFujiDictionary.string: SYMetadataMakerFuji.self, 146 | kCGImagePropertyMakerOlympusDictionary.string: SYMetadataMakerOlympus.self, 147 | kCGImagePropertyMakerPentaxDictionary.string: SYMetadataMakerPentax.self, 148 | kCGImageProperty8BIMDictionary.string: SYMetadata8BIM.self, 149 | kCGImagePropertyDNGDictionary.string: SYMetadataDNG.self 150 | ] 151 | } 152 | 153 | public var metadataTIFF: SYMetadataTIFF? { 154 | get { getChildren(key: kCGImagePropertyTIFFDictionary.string) } 155 | set { setChildren(key: kCGImagePropertyTIFFDictionary.string, value: newValue) } 156 | } 157 | 158 | public var metadataExif: SYMetadataExif? { 159 | get { getChildren(key: kCGImagePropertyExifDictionary.string) } 160 | set { setChildren(key: kCGImagePropertyExifDictionary.string, value: newValue) } 161 | } 162 | 163 | public var metadataExifAux: SYMetadataExifAux? { 164 | get { getChildren(key: kCGImagePropertyExifAuxDictionary.string) } 165 | set { setChildren(key: kCGImagePropertyExifAuxDictionary.string, value: newValue) } 166 | } 167 | 168 | public var metadataGIF: SYMetadataGIF? { 169 | get { getChildren(key: kCGImagePropertyGIFDictionary.string) } 170 | set { setChildren(key: kCGImagePropertyGIFDictionary.string, value: newValue) } 171 | } 172 | 173 | public var metadataJFIF: SYMetadataJFIF? { 174 | get { getChildren(key: kCGImagePropertyJFIFDictionary.string) } 175 | set { setChildren(key: kCGImagePropertyJFIFDictionary.string, value: newValue) } 176 | } 177 | 178 | public var metadataPNG: SYMetadataPNG? { 179 | get { getChildren(key: kCGImagePropertyPNGDictionary.string) } 180 | set { setChildren(key: kCGImagePropertyPNGDictionary.string, value: newValue) } 181 | } 182 | 183 | public var metadataIPTC: SYMetadataIPTC? { 184 | get { getChildren(key: kCGImagePropertyIPTCDictionary.string) } 185 | set { setChildren(key: kCGImagePropertyIPTCDictionary.string, value: newValue) } 186 | } 187 | 188 | public var metadataGPS: SYMetadataGPS? { 189 | get { getChildren(key: kCGImagePropertyGPSDictionary.string) } 190 | set { setChildren(key: kCGImagePropertyGPSDictionary.string, value: newValue) } 191 | } 192 | 193 | public var metadataRaw: SYMetadataRaw? { 194 | get { getChildren(key: kCGImagePropertyRawDictionary.string) } 195 | set { setChildren(key: kCGImagePropertyRawDictionary.string, value: newValue) } 196 | } 197 | 198 | public var metadataCIFF: SYMetadataCIFF? { 199 | get { getChildren(key: kCGImagePropertyCIFFDictionary.string) } 200 | set { setChildren(key: kCGImagePropertyCIFFDictionary.string, value: newValue) } 201 | } 202 | 203 | public var metadataMakerApple: SYMetadataMakerApple? { 204 | get { getChildren(key: kCGImagePropertyMakerAppleDictionary.string) } 205 | set { setChildren(key: kCGImagePropertyMakerAppleDictionary.string, value: newValue) } 206 | } 207 | 208 | public var metadataMakerCanon: SYMetadataMakerCanon? { 209 | get { getChildren(key: kCGImagePropertyMakerCanonDictionary.string) } 210 | set { setChildren(key: kCGImagePropertyMakerCanonDictionary.string, value: newValue) } 211 | } 212 | 213 | public var metadataMakerNikon: SYMetadataMakerNikon? { 214 | get { getChildren(key: kCGImagePropertyMakerNikonDictionary.string) } 215 | set { setChildren(key: kCGImagePropertyMakerNikonDictionary.string, value: newValue) } 216 | } 217 | 218 | public var metadataMakerMinolta: SYMetadataMakerMinolta? { 219 | get { getChildren(key: kCGImagePropertyMakerMinoltaDictionary.string) } 220 | set { setChildren(key: kCGImagePropertyMakerMinoltaDictionary.string, value: newValue) } 221 | } 222 | 223 | public var metadataMakerFuji: SYMetadataMakerFuji? { 224 | get { getChildren(key: kCGImagePropertyMakerFujiDictionary.string) } 225 | set { setChildren(key: kCGImagePropertyMakerFujiDictionary.string, value: newValue) } 226 | } 227 | 228 | public var metadataMakerOlympus: SYMetadataMakerOlympus? { 229 | get { getChildren(key: kCGImagePropertyMakerOlympusDictionary.string) } 230 | set { setChildren(key: kCGImagePropertyMakerOlympusDictionary.string, value: newValue) } 231 | } 232 | 233 | public var metadataMakerPentax: SYMetadataMakerPentax? { 234 | get { getChildren(key: kCGImagePropertyMakerPentaxDictionary.string) } 235 | set { setChildren(key: kCGImagePropertyMakerPentaxDictionary.string, value: newValue) } 236 | } 237 | 238 | public var metadata8BIM: SYMetadata8BIM? { 239 | get { getChildren(key: kCGImageProperty8BIMDictionary.string) } 240 | set { setChildren(key: kCGImageProperty8BIMDictionary.string, value: newValue) } 241 | } 242 | 243 | public var metadataDNG: SYMetadataDNG? { 244 | get { getChildren(key: kCGImagePropertyDNGDictionary.string) } 245 | set { setChildren(key: kCGImagePropertyDNGDictionary.string, value: newValue) } 246 | } 247 | 248 | // MARK: Types 249 | public enum ColorModel: String, CaseIterable { 250 | case rgb = "RGB" // kCGImagePropertyColorModelRGB 251 | case gray = "Gray" // kCGImagePropertyColorModelGray 252 | case cmyk = "CMYK" // kCGImagePropertyColorModelCMYK 253 | case lab = "Lab" // kCGImagePropertyColorModelLab 254 | } 255 | 256 | // MARK: Values 257 | public var fileSize: Int? { 258 | return getValue(key: kCGImagePropertyFileSize.string) 259 | } 260 | 261 | public var pixelHeight: Int? { 262 | return getValue(key: kCGImagePropertyPixelHeight.string) 263 | } 264 | 265 | public var pixelWidth: Int? { 266 | return getValue(key: kCGImagePropertyPixelWidth.string) 267 | } 268 | 269 | public var dpiHeight: Int? { 270 | return getValue(key: kCGImagePropertyDPIHeight.string) 271 | } 272 | 273 | public var dpiWidth: Int? { 274 | return getValue(key: kCGImagePropertyDPIWidth.string) 275 | } 276 | 277 | public var depth: Int? { 278 | return getValue(key: kCGImagePropertyDepth.string) 279 | } 280 | 281 | public var orientation: CGImagePropertyOrientation? { 282 | get { getValue(key: kCGImagePropertyOrientation.string) } 283 | set { setValue(key: kCGImagePropertyOrientation.string, value: newValue) } 284 | } 285 | 286 | public var isFloat: Bool? { 287 | return getValue(key: kCGImagePropertyIsFloat.string) 288 | } 289 | 290 | public var isIndexed: Bool? { 291 | return getValue(key: kCGImagePropertyIsIndexed.string) 292 | } 293 | 294 | public var hasAlpha: Bool? { 295 | return getValue(key: kCGImagePropertyHasAlpha.string) 296 | } 297 | 298 | public var colorModel: ColorModel? { 299 | return getValue(key: kCGImagePropertyColorModel.string) 300 | } 301 | 302 | public var profileName: String? { 303 | return getValue(key: kCGImagePropertyProfileName.string) 304 | } 305 | 306 | public var pictureStyle: Dictionary? { 307 | get { getValue(key: kSYImagePropertyPictureStyle.string) } 308 | set { setValue(key: kSYImagePropertyPictureStyle.string, value: newValue) } 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadata8BIM.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadata8BIM.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 08/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | public class SYMetadata8BIM : SYMetadataBase { 13 | 14 | // MARK: Values 15 | public var layerNames: Array? { 16 | get { getValue(key: kCGImageProperty8BIMLayerNames.string) } 17 | set { setValue(key: kCGImageProperty8BIMLayerNames.string, value: newValue) } 18 | } 19 | 20 | public var version: Int? { 21 | get { getValue(key: kCGImageProperty8BIMVersion.string) } 22 | set { setValue(key: kCGImageProperty8BIMVersion.string, value: newValue) } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataBase.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataBase.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public class SYMetadataBase : NSObject { 12 | 13 | // MARK: Init 14 | public required init(dictionary: Dictionary) { 15 | self.originalDictionary = dictionary 16 | self.valuesDictionary = dictionary 17 | 18 | for (key, type) in Self.childrenMappings { 19 | if let value = self.valuesDictionary[key] as? [String: Any] { 20 | self.childrenObjects[key] = type.init(dictionary: value) 21 | self.valuesDictionary.removeValue(forKey: key) 22 | } 23 | else if self.valuesDictionary.keys.contains(key) { 24 | fatalError("value for key \(key) is not a metadata dictionary") 25 | } 26 | } 27 | } 28 | 29 | public convenience override init() { 30 | self.init(dictionary: [:]) 31 | } 32 | 33 | // MARK: Properties 34 | public let originalDictionary: [String: Any] 35 | public var currentDictionary: [String: Any] { 36 | var dictionary = self.valuesDictionary 37 | for (key, object) in self.childrenObjects { 38 | dictionary[key] = object?.currentDictionary ?? NSNull() 39 | } 40 | return dictionary 41 | } 42 | private var valuesDictionary: [String: Any] 43 | 44 | // MARK: Children 45 | internal class var childrenMappings: [String: SYMetadataBase.Type] { 46 | return [:] 47 | } 48 | private var childrenObjects: [String: SYMetadataBase?] = [:] 49 | internal func getChildren(key: String) -> T? { 50 | guard let child = childrenObjects[key] else { return nil } 51 | return child as! T 52 | } 53 | internal func setChildren(key: String, value: T?) { 54 | // using the subscript would remove the value from the dictionary. we want to keep it, this is an intentional remove 55 | childrenObjects.updateValue(value, forKey: key) 56 | } 57 | 58 | // MARK: Debug 59 | #if DEBUG 60 | internal var debugReadKeys: Set = [] 61 | public var allDebugReadKeys: Set { 62 | var keys = debugReadKeys 63 | self.childrenObjects.forEach { (key, child) in 64 | if let childKeys = child?.allDebugReadKeys { 65 | keys.formUnion(childKeys.map { [key, $0].joined(separator: ".") }) 66 | } 67 | } 68 | return keys 69 | } 70 | internal var debugWrittenKeys: Set = [] 71 | public var allDebugWrittenKeys: Set { 72 | var keys = debugWrittenKeys 73 | self.childrenObjects.forEach { (key, child) in 74 | if let childKeys = child?.allDebugWrittenKeys { 75 | keys.formUnion(childKeys.map { [key, $0].joined(separator: ".") }) 76 | } 77 | } 78 | return keys 79 | } 80 | #endif 81 | 82 | // MARK: Values 83 | internal func getValue(key: String) -> T? { 84 | #if DEBUG 85 | debugReadKeys.insert(key) 86 | #endif 87 | 88 | if valuesDictionary[key] == nil || valuesDictionary[key] as? NSObject == kCFNull { 89 | return nil 90 | } 91 | guard let value = valuesDictionary[key] as? T else { 92 | fatalError("value for key \(key) is not of the expected type \(T.self)") 93 | } 94 | 95 | #if DEBUG 96 | if T.self == Array.self, let first = (value as! Array).first { 97 | let typeString = NSString(cString: first.objCType, encoding: String.Encoding.ascii.rawValue)! 98 | print("-> \(type(of: self)).\(key) is configured as Array, you could use '\(typeString)' instead") 99 | } 100 | if T.self == NSNumber.self { 101 | let typeString = NSString(cString: (value as! NSNumber).objCType, encoding: String.Encoding.ascii.rawValue)! 102 | print("-> \(type(of: self)).\(key) is configured as NSNumber, you could use '\(typeString)' instead") 103 | } 104 | if T.self == NSObject.self { 105 | print("-> \(type(of: self)).\(key) is configured as NSObject, you could use '\(type(of: value))' instead") 106 | } 107 | #endif 108 | return value 109 | } 110 | 111 | internal func getValue(key: String) -> T? { 112 | let rawValue: T.RawValue? = getValue(key: key) 113 | if let rawValue = rawValue { 114 | return T(rawValue: rawValue) 115 | } 116 | return nil 117 | } 118 | 119 | internal func setValue(key: String, value: T) { 120 | #if DEBUG 121 | debugWrittenKeys.insert(key) 122 | #endif 123 | 124 | if let value = value.value { 125 | valuesDictionary[key] = value 126 | } else { 127 | valuesDictionary[key] = NSNull() // needed to remove a value from the metadata 128 | } 129 | } 130 | 131 | internal func setValue(key: String, value: T?) { 132 | setValue(key: key, value: value?.rawValue) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataCIFF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataCIFF.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 08/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | // https://raw.githubusercontent.com/wiki/drewnoakes/metadata-extractor/docs/CIFFspecV1R04.pdf 13 | // Nota: this is assuming attributes from CIFF and MakerCanon use the same types 14 | 15 | @objcMembers public class SYMetadataCIFF : SYMetadataBase { 16 | 17 | // MARK: Types 18 | public enum ReleaseMethod: Int, CaseIterable { 19 | case singleShot = 0 20 | case continuousShooting = 2 21 | } 22 | 23 | public enum ReleaseTiming: Int, CaseIterable { 24 | case priorityOnShutter = 0 25 | case priorityOnFocus = 1 26 | } 27 | 28 | public enum MeteringMode: Int, CaseIterable { 29 | case `default` = 0 30 | case spot = 1 31 | case average = 2 32 | case evaluative = 3 33 | case partial = 4 34 | case centerWeightedAverage = 5 35 | } 36 | 37 | public enum WhiteBalance: Int, CaseIterable { 38 | case auto = 0 39 | case daylight = 1 40 | case cloudy = 2 41 | case tungsten = 3 42 | case fluorescent = 4 43 | case flash = 5 44 | case custom = 6 45 | case blackAndWhite = 7 46 | case shade = 8 47 | case manualTemperatureKelvin = 9 48 | case pcSet1 = 10 49 | case pcSet2 = 11 50 | case pcSet3 = 12 51 | case daylightFluorescent = 14 52 | case custom1 = 15 53 | case custom2 = 16 54 | case underwater = 17 55 | case custom3 = 18 56 | case custom4 = 19 57 | case pcSet4 = 20 58 | case pcSet5 = 21 59 | case autoAmbiencePriority = 23 60 | } 61 | 62 | public enum ContinousDrive: Int, CaseIterable { 63 | case single = 0 64 | case continuous = 1 65 | case movie = 2 66 | case continuousSpeedPriority = 3 67 | case continuousLow = 4 68 | case continuousHigh = 5 69 | case silentSingle = 6 70 | case singleSilent = 9 71 | case continuousSilent = 10 72 | } 73 | 74 | public enum FocusMode: Int, CaseIterable { 75 | case oneShotAF = 0 76 | case aiServoAF = 1 77 | case aiFocusAF = 2 78 | case manualFocus3 = 3 79 | case single = 4 80 | case continuous = 5 81 | case manualFocus6 = 6 82 | case panFocus = 16 83 | case af_mf = 256 84 | case movieSnapFocus = 512 85 | case movieServoAF = 519 86 | } 87 | 88 | // MARK: Values 89 | public var descr: String? { 90 | get { getValue(key: kCGImagePropertyCIFFDescription.string) } 91 | set { setValue(key: kCGImagePropertyCIFFDescription.string, value: newValue) } 92 | } 93 | 94 | public var firmware: String? { 95 | get { getValue(key: kCGImagePropertyCIFFFirmware.string) } 96 | set { setValue(key: kCGImagePropertyCIFFFirmware.string, value: newValue) } 97 | } 98 | 99 | public var ownerName: String? { 100 | get { getValue(key: kCGImagePropertyCIFFOwnerName.string) } 101 | set { setValue(key: kCGImagePropertyCIFFOwnerName.string, value: newValue) } 102 | } 103 | 104 | public var imageName: String? { 105 | get { getValue(key: kCGImagePropertyCIFFImageName.string) } 106 | set { setValue(key: kCGImagePropertyCIFFImageName.string, value: newValue) } 107 | } 108 | 109 | public var imageFileName: String? { 110 | get { getValue(key: kCGImagePropertyCIFFImageFileName.string) } 111 | set { setValue(key: kCGImagePropertyCIFFImageFileName.string, value: newValue) } 112 | } 113 | 114 | public var releaseMethod: ReleaseMethod? { 115 | get { getValue(key: kCGImagePropertyCIFFReleaseMethod.string) } 116 | set { setValue(key: kCGImagePropertyCIFFReleaseMethod.string, value: newValue) } 117 | } 118 | 119 | public var releaseTiming: ReleaseTiming? { 120 | get { getValue(key: kCGImagePropertyCIFFReleaseTiming.string) } 121 | set { setValue(key: kCGImagePropertyCIFFReleaseTiming.string, value: newValue) } 122 | } 123 | 124 | public var recordID: UInt? { 125 | get { getValue(key: kCGImagePropertyCIFFRecordID.string) } 126 | set { setValue(key: kCGImagePropertyCIFFRecordID.string, value: newValue) } 127 | } 128 | 129 | public var selfTimingTime: UInt? { 130 | get { getValue(key: kCGImagePropertyCIFFSelfTimingTime.string) } 131 | set { setValue(key: kCGImagePropertyCIFFSelfTimingTime.string, value: newValue) } 132 | } 133 | 134 | public var cameraSerialNumber: UInt? { 135 | get { getValue(key: kCGImagePropertyCIFFCameraSerialNumber.string) } 136 | set { setValue(key: kCGImagePropertyCIFFCameraSerialNumber.string, value: newValue) } 137 | } 138 | 139 | public var imageSerialNumber: UInt? { 140 | get { getValue(key: kCGImagePropertyCIFFImageSerialNumber.string) } 141 | set { setValue(key: kCGImagePropertyCIFFImageSerialNumber.string, value: newValue) } 142 | } 143 | 144 | public var continuousDrive: ContinousDrive? { 145 | get { getValue(key: kCGImagePropertyCIFFContinuousDrive.string) } 146 | set { setValue(key: kCGImagePropertyCIFFContinuousDrive.string, value: newValue) } 147 | } 148 | 149 | public var focusMode: FocusMode? { 150 | get { getValue(key: kCGImagePropertyCIFFFocusMode.string) } 151 | set { setValue(key: kCGImagePropertyCIFFFocusMode.string, value: newValue) } 152 | } 153 | 154 | public var meteringMode: MeteringMode? { 155 | get { getValue(key: kCGImagePropertyCIFFMeteringMode.string) } 156 | set { setValue(key: kCGImagePropertyCIFFMeteringMode.string, value: newValue) } 157 | } 158 | 159 | public var shootingMode: NSNumber? { 160 | get { getValue(key: kCGImagePropertyCIFFShootingMode.string) } 161 | set { setValue(key: kCGImagePropertyCIFFShootingMode.string, value: newValue) } 162 | } 163 | 164 | public var lensModel: String? { 165 | get { getValue(key: kCGImagePropertyCIFFLensModel.string) } 166 | set { setValue(key: kCGImagePropertyCIFFLensModel.string, value: newValue) } 167 | } 168 | 169 | public var lensMaxMM: Int? { 170 | get { getValue(key: kCGImagePropertyCIFFLensMaxMM.string) } 171 | set { setValue(key: kCGImagePropertyCIFFLensMaxMM.string, value: newValue) } 172 | } 173 | 174 | public var lensMinMM: Int? { 175 | get { getValue(key: kCGImagePropertyCIFFLensMinMM.string) } 176 | set { setValue(key: kCGImagePropertyCIFFLensMinMM.string, value: newValue) } 177 | } 178 | 179 | public var whiteBalance: WhiteBalance? { 180 | get { getValue(key: kCGImagePropertyCIFFWhiteBalanceIndex.string) } 181 | set { setValue(key: kCGImagePropertyCIFFWhiteBalanceIndex.string, value: newValue) } 182 | } 183 | 184 | public var flashExposureComp: Int? { 185 | get { getValue(key: kCGImagePropertyCIFFFlashExposureComp.string) } 186 | set { setValue(key: kCGImagePropertyCIFFFlashExposureComp.string, value: newValue) } 187 | } 188 | 189 | public var measuredEV: Double? { 190 | get { getValue(key: kCGImagePropertyCIFFMeasuredEV.string) } 191 | set { setValue(key: kCGImagePropertyCIFFMeasuredEV.string, value: newValue) } 192 | } 193 | 194 | public var uniqueModelID: UInt? { 195 | get { getValue(key: kSYImagePropertyCIFFUniqueModelID.string) } 196 | set { setValue(key: kSYImagePropertyCIFFUniqueModelID.string, value: newValue) } 197 | } 198 | 199 | public var maxAperture: Double? { 200 | get { getValue(key: kSYImagePropertyCIFFMaxAperture.string) } 201 | set { setValue(key: kSYImagePropertyCIFFMaxAperture.string, value: newValue) } 202 | } 203 | 204 | public var minAperture: Double? { 205 | get { getValue(key: kSYImagePropertyCIFFMinAperture.string) } 206 | set { setValue(key: kSYImagePropertyCIFFMinAperture.string, value: newValue) } 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataDNG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataDNG.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 08/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | // https://www.adobe.com/content/dam/Adobe/en/products/photoshop/pdfs/dng_spec_1.4.0.0.pdf 13 | 14 | @objcMembers public class SYMetadataDNG : SYMetadataBase { 15 | 16 | public enum LightSource: Int, CaseIterable { 17 | case unknown = 0 18 | case daylight = 1 19 | case fluorescent = 2 20 | case tungstenIncandescent = 3 21 | case flash = 4 22 | case fineWeather = 9 23 | case cloudy = 10 24 | case shade = 11 25 | case daylightFluorescent = 12 26 | case dayWhiteFluorescent = 13 27 | case coolWhiteFluorescent = 14 28 | case whiteFluorescent = 15 29 | case warmWhiteFluorescent = 16 30 | case standardLightA = 17 31 | case standardLightB = 18 32 | case standardLightC = 19 33 | case d55 = 20 34 | case d65 = 21 35 | case d75 = 22 36 | case d50 = 23 37 | case isoStudioTungsten = 24 38 | case other = 255 39 | } 40 | 41 | // MARK: Values 42 | public var version: Array? { 43 | get { getValue(key: kCGImagePropertyDNGVersion.string) } 44 | set { setValue(key: kCGImagePropertyDNGVersion.string, value: newValue) } 45 | } 46 | 47 | public var backwardVersion: Array? { 48 | get { getValue(key: kCGImagePropertyDNGBackwardVersion.string) } 49 | set { setValue(key: kCGImagePropertyDNGBackwardVersion.string, value: newValue) } 50 | } 51 | 52 | public var uniqueCameraModel: String? { 53 | get { getValue(key: kCGImagePropertyDNGUniqueCameraModel.string) } 54 | set { setValue(key: kCGImagePropertyDNGUniqueCameraModel.string, value: newValue) } 55 | } 56 | 57 | public var localizedCameraModel: String? { 58 | get { getValue(key: kCGImagePropertyDNGLocalizedCameraModel.string) } 59 | set { setValue(key: kCGImagePropertyDNGLocalizedCameraModel.string, value: newValue) } 60 | } 61 | 62 | public var cameraSerialNumber: String? { 63 | get { getValue(key: kCGImagePropertyDNGCameraSerialNumber.string) } 64 | set { setValue(key: kCGImagePropertyDNGCameraSerialNumber.string, value: newValue) } 65 | } 66 | 67 | public var lensInfo: Array? { 68 | get { getValue(key: kCGImagePropertyDNGLensInfo.string) } 69 | set { setValue(key: kCGImagePropertyDNGLensInfo.string, value: newValue) } 70 | } 71 | 72 | public var blackLevel: Array? { 73 | get { getValue(key: kCGImagePropertyDNGBlackLevel.string) } 74 | set { setValue(key: kCGImagePropertyDNGBlackLevel.string, value: newValue) } 75 | } 76 | 77 | public var whiteLevel: Array? { 78 | get { getValue(key: kCGImagePropertyDNGWhiteLevel.string) } 79 | set { setValue(key: kCGImagePropertyDNGWhiteLevel.string, value: newValue) } 80 | } 81 | 82 | public var calibrationIlluminant1: LightSource? { 83 | get { getValue(key: kCGImagePropertyDNGCalibrationIlluminant1.string) } 84 | set { setValue(key: kCGImagePropertyDNGCalibrationIlluminant1.string, value: newValue) } 85 | } 86 | 87 | public var calibrationIlluminant2: LightSource? { 88 | get { getValue(key: kCGImagePropertyDNGCalibrationIlluminant2.string) } 89 | set { setValue(key: kCGImagePropertyDNGCalibrationIlluminant2.string, value: newValue) } 90 | } 91 | 92 | public var colorMatrix1: Array? { 93 | get { getValue(key: kCGImagePropertyDNGColorMatrix1.string) } 94 | set { setValue(key: kCGImagePropertyDNGColorMatrix1.string, value: newValue) } 95 | } 96 | 97 | public var colorMatrix2: Array? { 98 | get { getValue(key: kCGImagePropertyDNGColorMatrix2.string) } 99 | set { setValue(key: kCGImagePropertyDNGColorMatrix2.string, value: newValue) } 100 | } 101 | 102 | public var cameraCalibration1: Array? { 103 | get { getValue(key: kCGImagePropertyDNGCameraCalibration1.string) } 104 | set { setValue(key: kCGImagePropertyDNGCameraCalibration1.string, value: newValue) } 105 | } 106 | 107 | public var cameraCalibration2: Array? { 108 | get { getValue(key: kCGImagePropertyDNGCameraCalibration2.string) } 109 | set { setValue(key: kCGImagePropertyDNGCameraCalibration2.string, value: newValue) } 110 | } 111 | 112 | public var asShotNeutral: Array? { 113 | get { getValue(key: kCGImagePropertyDNGAsShotNeutral.string) } 114 | set { setValue(key: kCGImagePropertyDNGAsShotNeutral.string, value: newValue) } 115 | } 116 | 117 | public var asShotWhiteXY: Array? { 118 | get { getValue(key: kCGImagePropertyDNGAsShotWhiteXY.string) } 119 | set { setValue(key: kCGImagePropertyDNGAsShotWhiteXY.string, value: newValue) } 120 | } 121 | 122 | public var baselineExposure: Float? { 123 | get { getValue(key: kCGImagePropertyDNGBaselineExposure.string) } 124 | set { setValue(key: kCGImagePropertyDNGBaselineExposure.string, value: newValue) } 125 | } 126 | 127 | public var baselineNoise: Float? { 128 | get { getValue(key: kCGImagePropertyDNGBaselineNoise.string) } 129 | set { setValue(key: kCGImagePropertyDNGBaselineNoise.string, value: newValue) } 130 | } 131 | 132 | public var baselineSharpness: Float? { 133 | get { getValue(key: kCGImagePropertyDNGBaselineSharpness.string) } 134 | set { setValue(key: kCGImagePropertyDNGBaselineSharpness.string, value: newValue) } 135 | } 136 | 137 | public var privateData: NSObject? { 138 | get { getValue(key: kCGImagePropertyDNGPrivateData.string) } 139 | set { setValue(key: kCGImagePropertyDNGPrivateData.string, value: newValue) } 140 | } 141 | 142 | public var cameraCalibrationSignature: String? { 143 | get { getValue(key: kCGImagePropertyDNGCameraCalibrationSignature.string) } 144 | set { setValue(key: kCGImagePropertyDNGCameraCalibrationSignature.string, value: newValue) } 145 | } 146 | 147 | public var profileCalibrationSignature: String? { 148 | get { getValue(key: kCGImagePropertyDNGProfileCalibrationSignature.string) } 149 | set { setValue(key: kCGImagePropertyDNGProfileCalibrationSignature.string, value: newValue) } 150 | } 151 | 152 | public var noiseProfile: Array? { 153 | get { getValue(key: kCGImagePropertyDNGNoiseProfile.string) } 154 | set { setValue(key: kCGImagePropertyDNGNoiseProfile.string, value: newValue) } 155 | } 156 | 157 | public var warpRectilinear: Array? { 158 | get { getValue(key: kCGImagePropertyDNGWarpRectilinear.string) } 159 | set { setValue(key: kCGImagePropertyDNGWarpRectilinear.string, value: newValue) } 160 | } 161 | 162 | public var warpFisheye: Array? { 163 | get { getValue(key: kCGImagePropertyDNGWarpFisheye.string) } 164 | set { setValue(key: kCGImagePropertyDNGWarpFisheye.string, value: newValue) } 165 | } 166 | 167 | public var fixVignetteRadial: Array? { 168 | get { getValue(key: kCGImagePropertyDNGFixVignetteRadial.string) } 169 | set { setValue(key: kCGImagePropertyDNGFixVignetteRadial.string, value: newValue) } 170 | } 171 | } 172 | 173 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataExifAux.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataExifAux.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataExifAux : SYMetadataBase { 13 | 14 | // MARK: Values 15 | public var lensInfo: Array? { 16 | get { getValue(key: kCGImagePropertyExifAuxLensInfo.string) } 17 | set { setValue(key: kCGImagePropertyExifAuxLensInfo.string, value: newValue) } 18 | } 19 | 20 | public var lensModel: String? { 21 | get { getValue(key: kCGImagePropertyExifAuxLensModel.string) } 22 | set { setValue(key: kCGImagePropertyExifAuxLensModel.string, value: newValue) } 23 | } 24 | 25 | public var serialNumber: String? { 26 | get { getValue(key: kCGImagePropertyExifAuxSerialNumber.string) } 27 | set { setValue(key: kCGImagePropertyExifAuxSerialNumber.string, value: newValue) } 28 | } 29 | 30 | public var lensID: NSNumber? { 31 | get { getValue(key: kCGImagePropertyExifAuxLensID.string) } 32 | set { setValue(key: kCGImagePropertyExifAuxLensID.string, value: newValue) } 33 | } 34 | 35 | public var lensSerialNumber: String? { 36 | get { getValue(key: kCGImagePropertyExifAuxLensSerialNumber.string) } 37 | set { setValue(key: kCGImagePropertyExifAuxLensSerialNumber.string, value: newValue) } 38 | } 39 | 40 | public var imageNumber: NSNumber? { 41 | get { getValue(key: kCGImagePropertyExifAuxImageNumber.string) } 42 | set { setValue(key: kCGImagePropertyExifAuxImageNumber.string, value: newValue) } 43 | } 44 | 45 | public var flashCompensation: NSObject? { 46 | get { getValue(key: kCGImagePropertyExifAuxFlashCompensation.string) } 47 | set { setValue(key: kCGImagePropertyExifAuxFlashCompensation.string, value: newValue) } 48 | } 49 | 50 | public var ownerName: String? { 51 | get { getValue(key: kCGImagePropertyExifAuxOwnerName.string) } 52 | set { setValue(key: kCGImagePropertyExifAuxOwnerName.string, value: newValue) } 53 | } 54 | 55 | public var firmware: NSObject? { 56 | get { getValue(key: kCGImagePropertyExifAuxFirmware.string) } 57 | set { setValue(key: kCGImagePropertyExifAuxFirmware.string, value: newValue) } 58 | } 59 | 60 | public var afInfo: NSArray? { 61 | get { getValue(key: kSYImagePropertyExifAuxAutoFocusInfo.string) } 62 | set { setValue(key: kSYImagePropertyExifAuxAutoFocusInfo.string, value: newValue) } 63 | } 64 | 65 | public var imageStabilization: NSNumber? { 66 | get { getValue(key: kSYImagePropertyExifAuxImageStabilization.string) } 67 | set { setValue(key: kSYImagePropertyExifAuxImageStabilization.string, value: newValue) } 68 | } 69 | 70 | // iOS reads the data but the keys are not publicly declared, we use the Nikon equivalents 71 | public var focusMode: NSNumber? { 72 | get { getValue(key: kCGImagePropertyMakerNikonFocusMode.string) } 73 | set { setValue(key: kCGImagePropertyMakerNikonFocusMode.string, value: newValue) } 74 | } 75 | 76 | public var focusDistance: NSNumber? { 77 | get { getValue(key: kCGImagePropertyMakerNikonFocusDistance.string) } 78 | set { setValue(key: kCGImagePropertyMakerNikonFocusDistance.string, value: newValue) } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataExtensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataExtensions.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import Photos 11 | 12 | extension CFString { 13 | internal var string: String { 14 | return (self as NSString) as String 15 | } 16 | } 17 | 18 | internal protocol OptionalType { 19 | associatedtype Wrapped 20 | func map(_ f: (Wrapped) throws -> U) rethrows -> U? 21 | var value: Wrapped? { get } 22 | } 23 | 24 | extension Optional: OptionalType { 25 | internal var value: (Wrapped)? { 26 | switch self { 27 | case .none: return nil 28 | case .some(let value): return value 29 | } 30 | } 31 | } 32 | 33 | public extension PHAsset { 34 | /* 35 | Asset captions are a new feature in iOS 14.0. 36 | They are not available via any public API, at least in iOS 14.5, and they are not saved into the image metadata, so SYMetadata can't obtain them easily. 37 | When you export the picture, via AirDrop for instance, the caption is added into the IPTC > Caption/Description and Tiff > Image Description. 38 | 39 | For the sake of convenience I have added support for getting the caption, but will not integrate into the library any way to update it as it involves 40 | updating the CoreData record for the PHAsset and I have not spent any reasonable time playing with it to have a solid solution. 41 | 42 | Here is a starting point if you ever need to look into it. Keep in mind it could cause crashes in CoreData since we don't know the appropriate thread to 43 | send updates, it could crash on older version of iOS or could break the device's photo library beyond repair! 44 | 45 | import CoreData 46 | func setAssetCaption(_ value: String?) { 47 | guard responds(to: NSSelectorFromString("objectID")), responds(to: NSSelectorFromString("managedObjectContextForFetchingResources")) else { return } 48 | 49 | guard let objectID = perform(NSSelectorFromString("objectID"))?.takeUnretainedValue() as? NSManagedObjectID else { return } 50 | guard let context = perform(NSSelectorFromString("managedObjectContextForFetchingResources"))?.takeUnretainedValue() as? NSManagedObjectContext else { return } 51 | 52 | let plManagedAsset = context.object(with: objectID) 53 | plManagedAsset.perform(NSSelectorFromString("setLongDescription:"), with: value) 54 | 55 | // > NOT UPDATED!! Error Domain=NSCocoaErrorDomain Code=134092 "(null)" 56 | // could be a missing entitlement issue 57 | do { 58 | try context.save() 59 | print("UPDATED!!") 60 | } 61 | catch { 62 | print("NOT UPDATED!!", error) 63 | } 64 | } 65 | */ 66 | 67 | var assetCaption: String? { 68 | // The easy way: 69 | // let caption = (asset.value(forKey: "descriptionProperties") as? NSObject)?.value(forKey: "assetDescription") 70 | 71 | // Hiding private API calls and avoiding crashes: 72 | let propertiesSelector = ["description", String("seitreporP".reversed())].joined() 73 | guard responds(to: NSSelectorFromString(propertiesSelector)) else { return nil } 74 | guard let descriptionProperties = value(forKey: propertiesSelector) as? NSObject else { return nil } 75 | 76 | let descriptionSelector = ["asset", String("noitpircseD".reversed())].joined() 77 | guard descriptionProperties.responds(to: NSSelectorFromString(descriptionSelector)) else { return nil } 78 | return descriptionProperties.value(forKey: descriptionSelector) as? String 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataGIF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataGIF.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataGIF : SYMetadataBase { 13 | 14 | // MARK: Values 15 | public var loopCount: Int? { 16 | get { getValue(key: kCGImagePropertyGIFLoopCount.string) } 17 | set { setValue(key: kCGImagePropertyGIFLoopCount.string, value: newValue) } 18 | } 19 | 20 | public var delayTime: Double? { 21 | get { getValue(key: kCGImagePropertyGIFDelayTime.string) } 22 | set { setValue(key: kCGImagePropertyGIFDelayTime.string, value: newValue) } 23 | } 24 | 25 | public var imageColorMap: Array? { 26 | get { getValue(key: kCGImagePropertyGIFImageColorMap.string) } 27 | set { setValue(key: kCGImagePropertyGIFImageColorMap.string, value: newValue) } 28 | } 29 | 30 | public var hasGlobalColorMap: Bool? { 31 | get { getValue(key: kCGImagePropertyGIFHasGlobalColorMap.string) } 32 | set { setValue(key: kCGImagePropertyGIFHasGlobalColorMap.string, value: newValue) } 33 | } 34 | 35 | public var unclampedDelayTime: Double? { 36 | get { getValue(key: kCGImagePropertyGIFUnclampedDelayTime.string) } 37 | set { setValue(key: kCGImagePropertyGIFUnclampedDelayTime.string, value: newValue) } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataGPS.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataGPS.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 08/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataGPS : SYMetadataBase { 13 | 14 | // MARK: Types 15 | public enum LatitudeRef: String, CaseIterable { 16 | case north = "N" 17 | case south = "S" 18 | } 19 | public enum LongitudeRef: String, CaseIterable { 20 | case east = "E" 21 | case west = "W" 22 | } 23 | public enum AltitudeRef: Int, CaseIterable { 24 | case aboveSeaLevel = 0 25 | case belowSeaLevel = 1 26 | } 27 | public enum DirectionRef: String, CaseIterable { 28 | case trueDirection = "T" 29 | case magneticDirection = "M" 30 | } 31 | public enum DistanceRef: String, CaseIterable { 32 | case kilometers = "K" 33 | case miles = "M" 34 | case knots = "N" 35 | } 36 | public enum SpeedRef: String, CaseIterable { 37 | case kilometersPerHour = "K" 38 | case milesPerHour = "M" 39 | case knots = "N" 40 | } 41 | public enum MeasureMode: String, CaseIterable { 42 | case twoDimensional = "2" 43 | case threeDimensional = "3" 44 | } 45 | public enum Status: String, CaseIterable { 46 | case active = "A" 47 | case void = "V" 48 | } 49 | 50 | // MARK: Values 51 | public var version: Array? { 52 | get { getValue(key: kCGImagePropertyGPSVersion.string) } 53 | set { setValue(key: kCGImagePropertyGPSVersion.string, value: newValue) } 54 | } 55 | 56 | public var latitudeRef: LatitudeRef? { 57 | get { getValue(key: kCGImagePropertyGPSLatitudeRef.string) } 58 | set { setValue(key: kCGImagePropertyGPSLatitudeRef.string, value: newValue) } 59 | } 60 | 61 | public var latitude: Double? { 62 | get { getValue(key: kCGImagePropertyGPSLatitude.string) } 63 | set { setValue(key: kCGImagePropertyGPSLatitude.string, value: newValue) } 64 | } 65 | 66 | public var longitudeRef: LongitudeRef? { 67 | get { getValue(key: kCGImagePropertyGPSLongitudeRef.string) } 68 | set { setValue(key: kCGImagePropertyGPSLongitudeRef.string, value: newValue) } 69 | } 70 | 71 | public var longitude: Double? { 72 | get { getValue(key: kCGImagePropertyGPSLongitude.string) } 73 | set { setValue(key: kCGImagePropertyGPSLongitude.string, value: newValue) } 74 | } 75 | 76 | public var altitudeRef: AltitudeRef? { 77 | get { getValue(key: kCGImagePropertyGPSAltitudeRef.string) } 78 | set { setValue(key: kCGImagePropertyGPSAltitudeRef.string, value: newValue) } 79 | } 80 | 81 | public var altitude: Double? { 82 | get { getValue(key: kCGImagePropertyGPSAltitude.string) } 83 | set { setValue(key: kCGImagePropertyGPSAltitude.string, value: newValue) } 84 | } 85 | 86 | public var timeStamp: String? { 87 | get { getValue(key: kCGImagePropertyGPSTimeStamp.string) } 88 | set { setValue(key: kCGImagePropertyGPSTimeStamp.string, value: newValue) } 89 | } 90 | 91 | public var satellites: String? { 92 | get { getValue(key: kCGImagePropertyGPSSatellites.string) } 93 | set { setValue(key: kCGImagePropertyGPSSatellites.string, value: newValue) } 94 | } 95 | 96 | public var status: Status? { 97 | get { getValue(key: kCGImagePropertyGPSStatus.string) } 98 | set { setValue(key: kCGImagePropertyGPSStatus.string, value: newValue) } 99 | } 100 | 101 | public var measureMode: MeasureMode? { 102 | get { getValue(key: kCGImagePropertyGPSMeasureMode.string) } 103 | set { setValue(key: kCGImagePropertyGPSMeasureMode.string, value: newValue) } 104 | } 105 | 106 | public var dop: Double? { 107 | get { getValue(key: kCGImagePropertyGPSDOP.string) } 108 | set { setValue(key: kCGImagePropertyGPSDOP.string, value: newValue) } 109 | } 110 | 111 | public var speedRef: SpeedRef? { 112 | get { getValue(key: kCGImagePropertyGPSSpeedRef.string) } 113 | set { setValue(key: kCGImagePropertyGPSSpeedRef.string, value: newValue) } 114 | } 115 | 116 | public var speed: Double? { 117 | get { getValue(key: kCGImagePropertyGPSSpeed.string) } 118 | set { setValue(key: kCGImagePropertyGPSSpeed.string, value: newValue) } 119 | } 120 | 121 | public var trackRef: DirectionRef? { 122 | get { getValue(key: kCGImagePropertyGPSTrackRef.string) } 123 | set { setValue(key: kCGImagePropertyGPSTrackRef.string, value: newValue) } 124 | } 125 | 126 | public var track: Double? { 127 | get { getValue(key: kCGImagePropertyGPSTrack.string) } 128 | set { setValue(key: kCGImagePropertyGPSTrack.string, value: newValue) } 129 | } 130 | 131 | public var imgDirectionRef: DirectionRef? { 132 | get { getValue(key: kCGImagePropertyGPSImgDirectionRef.string) } 133 | set { setValue(key: kCGImagePropertyGPSImgDirectionRef.string, value: newValue) } 134 | } 135 | 136 | public var imgDirection: Double? { 137 | get { getValue(key: kCGImagePropertyGPSImgDirection.string) } 138 | set { setValue(key: kCGImagePropertyGPSImgDirection.string, value: newValue) } 139 | } 140 | 141 | public var mapDatum: String? { 142 | get { getValue(key: kCGImagePropertyGPSMapDatum.string) } 143 | set { setValue(key: kCGImagePropertyGPSMapDatum.string, value: newValue) } 144 | } 145 | 146 | public var destLatitudeRef: LatitudeRef? { 147 | get { getValue(key: kCGImagePropertyGPSDestLatitudeRef.string) } 148 | set { setValue(key: kCGImagePropertyGPSDestLatitudeRef.string, value: newValue) } 149 | } 150 | 151 | public var destLatitude: Double? { 152 | get { getValue(key: kCGImagePropertyGPSDestLatitude.string) } 153 | set { setValue(key: kCGImagePropertyGPSDestLatitude.string, value: newValue) } 154 | } 155 | 156 | public var destLongitudeRef: LongitudeRef? { 157 | get { getValue(key: kCGImagePropertyGPSDestLongitudeRef.string) } 158 | set { setValue(key: kCGImagePropertyGPSDestLongitudeRef.string, value: newValue) } 159 | } 160 | 161 | public var destLongitude: Double? { 162 | get { getValue(key: kCGImagePropertyGPSDestLongitude.string) } 163 | set { setValue(key: kCGImagePropertyGPSDestLongitude.string, value: newValue) } 164 | } 165 | 166 | public var destBearingRef: DirectionRef? { 167 | get { getValue(key: kCGImagePropertyGPSDestBearingRef.string) } 168 | set { setValue(key: kCGImagePropertyGPSDestBearingRef.string, value: newValue) } 169 | } 170 | 171 | public var destBearing: Double? { 172 | get { getValue(key: kCGImagePropertyGPSDestBearing.string) } 173 | set { setValue(key: kCGImagePropertyGPSDestBearing.string, value: newValue) } 174 | } 175 | 176 | public var destDistanceRef: DistanceRef? { 177 | get { getValue(key: kCGImagePropertyGPSDestDistanceRef.string) } 178 | set { setValue(key: kCGImagePropertyGPSDestDistanceRef.string, value: newValue) } 179 | } 180 | 181 | public var destDistance: Double? { 182 | get { getValue(key: kCGImagePropertyGPSDestDistance.string) } 183 | set { setValue(key: kCGImagePropertyGPSDestDistance.string, value: newValue) } 184 | } 185 | 186 | public var processingMethod: NSObject? { 187 | get { getValue(key: kCGImagePropertyGPSProcessingMethod.string) } 188 | set { setValue(key: kCGImagePropertyGPSProcessingMethod.string, value: newValue) } 189 | } 190 | 191 | public var areaInformation: NSObject? { 192 | get { getValue(key: kCGImagePropertyGPSAreaInformation.string) } 193 | set { setValue(key: kCGImagePropertyGPSAreaInformation.string, value: newValue) } 194 | } 195 | 196 | public var dateStamp: String? { 197 | get { getValue(key: kCGImagePropertyGPSDateStamp.string) } 198 | set { setValue(key: kCGImagePropertyGPSDateStamp.string, value: newValue) } 199 | } 200 | 201 | public var differental: Double? { 202 | get { getValue(key: kCGImagePropertyGPSDifferental.string) } 203 | set { setValue(key: kCGImagePropertyGPSDifferental.string, value: newValue) } 204 | } 205 | 206 | public var hPositioningError: Double? { 207 | get { getValue(key: kCGImagePropertyGPSHPositioningError.string) } 208 | set { setValue(key: kCGImagePropertyGPSHPositioningError.string, value: newValue) } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataIPTC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataIPTC.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 08/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataIPTC : SYMetadataBase { 13 | 14 | // MARK: Children 15 | internal override class var childrenMappings: [String: SYMetadataBase.Type] { 16 | return [kCGImagePropertyIPTCCreatorContactInfo.string: SYMetadataIPTCContactInfo.self] 17 | } 18 | 19 | public var creatorContactInfo: SYMetadataIPTCContactInfo? { 20 | get { getChildren(key: kCGImagePropertyIPTCCreatorContactInfo.string) } 21 | set { setChildren(key: kCGImagePropertyIPTCCreatorContactInfo.string, value: newValue) } 22 | } 23 | 24 | // MARK: Values 25 | public var objectTypeReference: String? { 26 | get { getValue(key: kCGImagePropertyIPTCObjectTypeReference.string) } 27 | set { setValue(key: kCGImagePropertyIPTCObjectTypeReference.string, value: newValue) } 28 | } 29 | 30 | public var objectAttributeReference: Array? { 31 | get { getValue(key: kCGImagePropertyIPTCObjectAttributeReference.string) } 32 | set { setValue(key: kCGImagePropertyIPTCObjectAttributeReference.string, value: newValue) } 33 | } 34 | 35 | public var objectName: String? { 36 | get { getValue(key: kCGImagePropertyIPTCObjectName.string) } 37 | set { setValue(key: kCGImagePropertyIPTCObjectName.string, value: newValue) } 38 | } 39 | 40 | public var editStatus: String? { 41 | get { getValue(key: kCGImagePropertyIPTCEditStatus.string) } 42 | set { setValue(key: kCGImagePropertyIPTCEditStatus.string, value: newValue) } 43 | } 44 | 45 | public var editorialUpdate: String? { 46 | get { getValue(key: kCGImagePropertyIPTCEditorialUpdate.string) } 47 | set { setValue(key: kCGImagePropertyIPTCEditorialUpdate.string, value: newValue) } 48 | } 49 | 50 | public var urgency: String? { 51 | get { getValue(key: kCGImagePropertyIPTCUrgency.string) } 52 | set { setValue(key: kCGImagePropertyIPTCUrgency.string, value: newValue) } 53 | } 54 | 55 | public var subjectReference: Array? { 56 | get { getValue(key: kCGImagePropertyIPTCSubjectReference.string) } 57 | set { setValue(key: kCGImagePropertyIPTCSubjectReference.string, value: newValue) } 58 | } 59 | 60 | public var category: String? { 61 | get { getValue(key: kCGImagePropertyIPTCCategory.string) } 62 | set { setValue(key: kCGImagePropertyIPTCCategory.string, value: newValue) } 63 | } 64 | 65 | public var supplementalCategory: Array? { 66 | get { getValue(key: kCGImagePropertyIPTCSupplementalCategory.string) } 67 | set { setValue(key: kCGImagePropertyIPTCSupplementalCategory.string, value: newValue) } 68 | } 69 | 70 | public var fixtureIdentifier: String? { 71 | get { getValue(key: kCGImagePropertyIPTCFixtureIdentifier.string) } 72 | set { setValue(key: kCGImagePropertyIPTCFixtureIdentifier.string, value: newValue) } 73 | } 74 | 75 | public var keywords: Array? { 76 | get { getValue(key: kCGImagePropertyIPTCKeywords.string) } 77 | set { setValue(key: kCGImagePropertyIPTCKeywords.string, value: newValue) } 78 | } 79 | 80 | public var contentLocationCode: Array? { 81 | get { getValue(key: kCGImagePropertyIPTCContentLocationCode.string) } 82 | set { setValue(key: kCGImagePropertyIPTCContentLocationCode.string, value: newValue) } 83 | } 84 | 85 | public var contentLocationName: Array? { 86 | get { getValue(key: kCGImagePropertyIPTCContentLocationName.string) } 87 | set { setValue(key: kCGImagePropertyIPTCContentLocationName.string, value: newValue) } 88 | } 89 | 90 | public var releaseDate: String? { 91 | get { getValue(key: kCGImagePropertyIPTCReleaseDate.string) } 92 | set { setValue(key: kCGImagePropertyIPTCReleaseDate.string, value: newValue) } 93 | } 94 | 95 | public var releaseTime: String? { 96 | get { getValue(key: kCGImagePropertyIPTCReleaseTime.string) } 97 | set { setValue(key: kCGImagePropertyIPTCReleaseTime.string, value: newValue) } 98 | } 99 | 100 | public var expirationDate: String? { 101 | get { getValue(key: kCGImagePropertyIPTCExpirationDate.string) } 102 | set { setValue(key: kCGImagePropertyIPTCExpirationDate.string, value: newValue) } 103 | } 104 | 105 | public var expirationTime: String? { 106 | get { getValue(key: kCGImagePropertyIPTCExpirationTime.string) } 107 | set { setValue(key: kCGImagePropertyIPTCExpirationTime.string, value: newValue) } 108 | } 109 | 110 | public var specialInstructions: String? { 111 | get { getValue(key: kCGImagePropertyIPTCSpecialInstructions.string) } 112 | set { setValue(key: kCGImagePropertyIPTCSpecialInstructions.string, value: newValue) } 113 | } 114 | 115 | public var actionAdvised: String? { 116 | get { getValue(key: kCGImagePropertyIPTCActionAdvised.string) } 117 | set { setValue(key: kCGImagePropertyIPTCActionAdvised.string, value: newValue) } 118 | } 119 | 120 | public var referenceService: Array? { 121 | get { getValue(key: kCGImagePropertyIPTCReferenceService.string) } 122 | set { setValue(key: kCGImagePropertyIPTCReferenceService.string, value: newValue) } 123 | } 124 | 125 | public var referenceDate: Array? { 126 | get { getValue(key: kCGImagePropertyIPTCReferenceDate.string) } 127 | set { setValue(key: kCGImagePropertyIPTCReferenceDate.string, value: newValue) } 128 | } 129 | 130 | public var referenceNumber: Array? { 131 | get { getValue(key: kCGImagePropertyIPTCReferenceNumber.string) } 132 | set { setValue(key: kCGImagePropertyIPTCReferenceNumber.string, value: newValue) } 133 | } 134 | 135 | public var dateCreated: String? { 136 | get { getValue(key: kCGImagePropertyIPTCDateCreated.string) } 137 | set { setValue(key: kCGImagePropertyIPTCDateCreated.string, value: newValue) } 138 | } 139 | 140 | public var timeCreated: String? { 141 | get { getValue(key: kCGImagePropertyIPTCTimeCreated.string) } 142 | set { setValue(key: kCGImagePropertyIPTCTimeCreated.string, value: newValue) } 143 | } 144 | 145 | public var digitalCreationDate: String? { 146 | get { getValue(key: kCGImagePropertyIPTCDigitalCreationDate.string) } 147 | set { setValue(key: kCGImagePropertyIPTCDigitalCreationDate.string, value: newValue) } 148 | } 149 | 150 | public var digitalCreationTime: String? { 151 | get { getValue(key: kCGImagePropertyIPTCDigitalCreationTime.string) } 152 | set { setValue(key: kCGImagePropertyIPTCDigitalCreationTime.string, value: newValue) } 153 | } 154 | 155 | public var originatingProgram: String? { 156 | get { getValue(key: kCGImagePropertyIPTCOriginatingProgram.string) } 157 | set { setValue(key: kCGImagePropertyIPTCOriginatingProgram.string, value: newValue) } 158 | } 159 | 160 | public var programVersion: String? { 161 | get { getValue(key: kCGImagePropertyIPTCProgramVersion.string) } 162 | set { setValue(key: kCGImagePropertyIPTCProgramVersion.string, value: newValue) } 163 | } 164 | 165 | public var objectCycle: String? { 166 | get { getValue(key: kCGImagePropertyIPTCObjectCycle.string) } 167 | set { setValue(key: kCGImagePropertyIPTCObjectCycle.string, value: newValue) } 168 | } 169 | 170 | public var byline: Array? { 171 | get { getValue(key: kCGImagePropertyIPTCByline.string) } 172 | set { setValue(key: kCGImagePropertyIPTCByline.string, value: newValue) } 173 | } 174 | 175 | public var bylineTitle: Array? { 176 | get { getValue(key: kCGImagePropertyIPTCBylineTitle.string) } 177 | set { setValue(key: kCGImagePropertyIPTCBylineTitle.string, value: newValue) } 178 | } 179 | 180 | public var city: String? { 181 | get { getValue(key: kCGImagePropertyIPTCCity.string) } 182 | set { setValue(key: kCGImagePropertyIPTCCity.string, value: newValue) } 183 | } 184 | 185 | public var subLocation: String? { 186 | get { getValue(key: kCGImagePropertyIPTCSubLocation.string) } 187 | set { setValue(key: kCGImagePropertyIPTCSubLocation.string, value: newValue) } 188 | } 189 | 190 | public var provinceState: String? { 191 | get { getValue(key: kCGImagePropertyIPTCProvinceState.string) } 192 | set { setValue(key: kCGImagePropertyIPTCProvinceState.string, value: newValue) } 193 | } 194 | 195 | public var countryPrimaryLocationCode: String? { 196 | get { getValue(key: kCGImagePropertyIPTCCountryPrimaryLocationCode.string) } 197 | set { setValue(key: kCGImagePropertyIPTCCountryPrimaryLocationCode.string, value: newValue) } 198 | } 199 | 200 | public var countryPrimaryLocationName: String? { 201 | get { getValue(key: kCGImagePropertyIPTCCountryPrimaryLocationName.string) } 202 | set { setValue(key: kCGImagePropertyIPTCCountryPrimaryLocationName.string, value: newValue) } 203 | } 204 | 205 | public var originalTransmissionReference: String? { 206 | get { getValue(key: kCGImagePropertyIPTCOriginalTransmissionReference.string) } 207 | set { setValue(key: kCGImagePropertyIPTCOriginalTransmissionReference.string, value: newValue) } 208 | } 209 | 210 | public var headline: String? { 211 | get { getValue(key: kCGImagePropertyIPTCHeadline.string) } 212 | set { setValue(key: kCGImagePropertyIPTCHeadline.string, value: newValue) } 213 | } 214 | 215 | public var credit: String? { 216 | get { getValue(key: kCGImagePropertyIPTCCredit.string) } 217 | set { setValue(key: kCGImagePropertyIPTCCredit.string, value: newValue) } 218 | } 219 | 220 | public var source: String? { 221 | get { getValue(key: kCGImagePropertyIPTCSource.string) } 222 | set { setValue(key: kCGImagePropertyIPTCSource.string, value: newValue) } 223 | } 224 | 225 | public var copyrightNotice: String? { 226 | get { getValue(key: kCGImagePropertyIPTCCopyrightNotice.string) } 227 | set { setValue(key: kCGImagePropertyIPTCCopyrightNotice.string, value: newValue) } 228 | } 229 | 230 | public var contact: Array? { 231 | get { getValue(key: kCGImagePropertyIPTCContact.string) } 232 | set { setValue(key: kCGImagePropertyIPTCContact.string, value: newValue) } 233 | } 234 | 235 | public var captionAbstract: String? { 236 | get { getValue(key: kCGImagePropertyIPTCCaptionAbstract.string) } 237 | set { setValue(key: kCGImagePropertyIPTCCaptionAbstract.string, value: newValue) } 238 | } 239 | 240 | public var writerEditor: Array? { 241 | get { getValue(key: kCGImagePropertyIPTCWriterEditor.string) } 242 | set { setValue(key: kCGImagePropertyIPTCWriterEditor.string, value: newValue) } 243 | } 244 | 245 | public var imageType: String? { 246 | get { getValue(key: kCGImagePropertyIPTCImageType.string) } 247 | set { setValue(key: kCGImagePropertyIPTCImageType.string, value: newValue) } 248 | } 249 | 250 | public var imageOrientation: String? { 251 | get { getValue(key: kCGImagePropertyIPTCImageOrientation.string) } 252 | set { setValue(key: kCGImagePropertyIPTCImageOrientation.string, value: newValue) } 253 | } 254 | 255 | public var languageIdentifier: String? { 256 | get { getValue(key: kCGImagePropertyIPTCLanguageIdentifier.string) } 257 | set { setValue(key: kCGImagePropertyIPTCLanguageIdentifier.string, value: newValue) } 258 | } 259 | 260 | public var starRating: Double? { 261 | get { getValue(key: kCGImagePropertyIPTCStarRating.string) } 262 | set { setValue(key: kCGImagePropertyIPTCStarRating.string, value: newValue) } 263 | } 264 | 265 | public var rightsUsageTerms: String? { 266 | get { getValue(key: kCGImagePropertyIPTCRightsUsageTerms.string) } 267 | set { setValue(key: kCGImagePropertyIPTCRightsUsageTerms.string, value: newValue) } 268 | } 269 | 270 | public var scene: String? { 271 | get { getValue(key: kCGImagePropertyIPTCScene.string) } 272 | set { setValue(key: kCGImagePropertyIPTCScene.string, value: newValue) } 273 | } 274 | 275 | public var digitalSourceType: String? { 276 | get { getValue(key: kCGImagePropertyIPTCExtDigitalSourceType.string) } 277 | set { setValue(key: kCGImagePropertyIPTCExtDigitalSourceType.string, value: newValue) } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataIPTCContactInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataIPTCContactInfo.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataIPTCContactInfo : SYMetadataBase { 13 | 14 | // MARK: Values 15 | public var city: String? { 16 | get { getValue(key: kCGImagePropertyIPTCContactInfoCity.string) } 17 | set { setValue(key: kCGImagePropertyIPTCContactInfoCity.string, value: newValue) } 18 | } 19 | 20 | public var country: String? { 21 | get { getValue(key: kCGImagePropertyIPTCContactInfoCountry.string) } 22 | set { setValue(key: kCGImagePropertyIPTCContactInfoCountry.string, value: newValue) } 23 | } 24 | 25 | public var address: String? { 26 | get { getValue(key: kCGImagePropertyIPTCContactInfoAddress.string) } 27 | set { setValue(key: kCGImagePropertyIPTCContactInfoAddress.string, value: newValue) } 28 | } 29 | 30 | public var postalCode: String? { 31 | get { getValue(key: kCGImagePropertyIPTCContactInfoPostalCode.string) } 32 | set { setValue(key: kCGImagePropertyIPTCContactInfoPostalCode.string, value: newValue) } 33 | } 34 | 35 | public var stateProvince: String? { 36 | get { getValue(key: kCGImagePropertyIPTCContactInfoStateProvince.string) } 37 | set { setValue(key: kCGImagePropertyIPTCContactInfoStateProvince.string, value: newValue) } 38 | } 39 | 40 | public var emails: String? { 41 | get { getValue(key: kCGImagePropertyIPTCContactInfoEmails.string) } 42 | set { setValue(key: kCGImagePropertyIPTCContactInfoEmails.string, value: newValue) } 43 | } 44 | 45 | public var phones: String? { 46 | get { getValue(key: kCGImagePropertyIPTCContactInfoPhones.string) } 47 | set { setValue(key: kCGImagePropertyIPTCContactInfoPhones.string, value: newValue) } 48 | } 49 | 50 | public var webURLs: String? { 51 | get { getValue(key: kCGImagePropertyIPTCContactInfoWebURLs.string) } 52 | set { setValue(key: kCGImagePropertyIPTCContactInfoWebURLs.string, value: newValue) } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataJFIF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataJFIF.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataJFIF : SYMetadataBase { 13 | 14 | // MARK: Values 15 | public var version: Array? { 16 | get { getValue(key: kCGImagePropertyJFIFVersion.string) } 17 | set { setValue(key: kCGImagePropertyJFIFVersion.string, value: newValue) } 18 | } 19 | 20 | public var xDensity: Int? { 21 | get { getValue(key: kCGImagePropertyJFIFXDensity.string) } 22 | set { setValue(key: kCGImagePropertyJFIFXDensity.string, value: newValue) } 23 | } 24 | 25 | public var yDensity: Int? { 26 | get { getValue(key: kCGImagePropertyJFIFYDensity.string) } 27 | set { setValue(key: kCGImagePropertyJFIFYDensity.string, value: newValue) } 28 | } 29 | 30 | public var densityUnit: NSNumber? { 31 | get { getValue(key: kCGImagePropertyJFIFDensityUnit.string) } 32 | set { setValue(key: kCGImagePropertyJFIFDensityUnit.string, value: newValue) } 33 | } 34 | 35 | public var isProgressive: Bool? { 36 | get { getValue(key: kCGImagePropertyJFIFIsProgressive.string) } 37 | set { setValue(key: kCGImagePropertyJFIFIsProgressive.string, value: newValue) } 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerApple.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerApple.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 02/02/2021. 6 | // 7 | 8 | import Foundation 9 | import ImageIO 10 | 11 | @objcMembers public class SYMetadataMakerApple : SYMetadataBase { 12 | } 13 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerCanon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerCanon.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerCanon : SYMetadataBase { 13 | 14 | // MARK: Types 15 | @objc public enum AspectRatio: Int, CaseIterable { 16 | case ratio_3_2 = 0 17 | case ratio_1_1 = 1 18 | case ratio_4_3 = 2 19 | case ratio_16_9 = 7 20 | case ratio_4_5 = 8 21 | case ratio_3_2_apshCrop = 12 22 | case ratio_3_2_apscCrop = 13 23 | } 24 | 25 | // MARK: Values 26 | public var ownerName: String? { 27 | get { getValue(key: kCGImagePropertyMakerCanonOwnerName.string) } 28 | set { setValue(key: kCGImagePropertyMakerCanonOwnerName.string, value: newValue) } 29 | } 30 | 31 | public var cameraSerialNumber: Int? { 32 | get { getValue(key: kCGImagePropertyMakerCanonCameraSerialNumber.string) } 33 | set { setValue(key: kCGImagePropertyMakerCanonCameraSerialNumber.string, value: newValue) } 34 | } 35 | 36 | public var imageSerialNumber: Int? { 37 | get { getValue(key: kCGImagePropertyMakerCanonImageSerialNumber.string) } 38 | set { setValue(key: kCGImagePropertyMakerCanonImageSerialNumber.string, value: newValue) } 39 | } 40 | 41 | public var flashExposureComp: NSNumber? { 42 | get { getValue(key: kCGImagePropertyMakerCanonFlashExposureComp.string) } 43 | set { setValue(key: kCGImagePropertyMakerCanonFlashExposureComp.string, value: newValue) } 44 | } 45 | 46 | public var continuousDrive: SYMetadataCIFF.ContinousDrive? { 47 | get { getValue(key: kCGImagePropertyMakerCanonContinuousDrive.string) } 48 | set { setValue(key: kCGImagePropertyMakerCanonContinuousDrive.string, value: newValue) } 49 | } 50 | 51 | public var lensModel: String? { 52 | get { getValue(key: kCGImagePropertyMakerCanonLensModel.string) } 53 | set { setValue(key: kCGImagePropertyMakerCanonLensModel.string, value: newValue) } 54 | } 55 | 56 | public var firmware: String? { 57 | get { getValue(key: kCGImagePropertyMakerCanonFirmware.string) } 58 | set { setValue(key: kCGImagePropertyMakerCanonFirmware.string, value: newValue) } 59 | } 60 | 61 | public var aspectRatioInfo: AspectRatio? { 62 | get { getValue(key: kCGImagePropertyMakerCanonAspectRatioInfo.string) } 63 | set { setValue(key: kCGImagePropertyMakerCanonAspectRatioInfo.string, value: newValue) } 64 | } 65 | 66 | // data is read by iOS but is not accessible via a publicly declared key. we use the ones from CIFF 67 | public var whiteBalance: SYMetadataCIFF.WhiteBalance? { 68 | get { getValue(key: kCGImagePropertyCIFFWhiteBalanceIndex.string) } 69 | set { setValue(key: kCGImagePropertyCIFFWhiteBalanceIndex.string, value: newValue) } 70 | } 71 | 72 | public var uniqueModelID: Int? { 73 | get { getValue(key: kSYImagePropertyCIFFUniqueModelID.string) } 74 | set { setValue(key: kSYImagePropertyCIFFUniqueModelID.string, value: newValue) } 75 | } 76 | 77 | public var maxAperture: Double? { 78 | get { getValue(key: kSYImagePropertyCIFFMaxAperture.string) } 79 | set { setValue(key: kSYImagePropertyCIFFMaxAperture.string, value: newValue) } 80 | } 81 | 82 | public var minAperture: Double? { 83 | get { getValue(key: kSYImagePropertyCIFFMinAperture.string) } 84 | set { setValue(key: kSYImagePropertyCIFFMinAperture.string, value: newValue) } 85 | } 86 | } 87 | 88 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerFuji.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerFuji.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerFuji : SYMetadataBase { 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerMinolta.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerMinolta.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerMinolta : SYMetadataBase { 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerNikon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerNikon.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerNikon : SYMetadataBase { 13 | 14 | // MARK: Types 15 | public struct ShootingMode : OptionSet { 16 | public let rawValue: Int32 17 | public init(rawValue: Int32) { self.rawValue = rawValue } 18 | public static let continuous = ShootingMode(rawValue: 1 << 0) 19 | public static let delay = ShootingMode(rawValue: 1 << 1) 20 | public static let pcControl = ShootingMode(rawValue: 1 << 2) 21 | public static let selfTimer = ShootingMode(rawValue: 1 << 3) 22 | public static let exposureBracketing = ShootingMode(rawValue: 1 << 4) 23 | public static let autoISO = ShootingMode(rawValue: 1 << 5) 24 | public static let whiteBalanceBracketing = ShootingMode(rawValue: 1 << 6) 25 | public static let irControl = ShootingMode(rawValue: 1 << 7) 26 | public static let dLightingBracketing = ShootingMode(rawValue: 1 << 8) 27 | } 28 | 29 | public struct LensType : OptionSet { 30 | public let rawValue: Int32 31 | public init(rawValue: Int32) { self.rawValue = rawValue } 32 | public static let MF = LensType(rawValue: 1 << 0) 33 | public static let D = LensType(rawValue: 1 << 1) 34 | public static let G = LensType(rawValue: 1 << 2) 35 | public static let VR = LensType(rawValue: 1 << 3) 36 | } 37 | 38 | // MARK: Values 39 | public var isoSetting: Array? { 40 | get { getValue(key: kCGImagePropertyMakerNikonISOSetting.string) } 41 | set { setValue(key: kCGImagePropertyMakerNikonISOSetting.string, value: newValue) } 42 | } 43 | 44 | public var colorMode: String? { 45 | get { getValue(key: kCGImagePropertyMakerNikonColorMode.string) } 46 | set { setValue(key: kCGImagePropertyMakerNikonColorMode.string, value: newValue) } 47 | } 48 | 49 | public var quality: String? { 50 | get { getValue(key: kCGImagePropertyMakerNikonQuality.string) } 51 | set { setValue(key: kCGImagePropertyMakerNikonQuality.string, value: newValue) } 52 | } 53 | 54 | public var whiteBalanceMode: String? { 55 | get { getValue(key: kCGImagePropertyMakerNikonWhiteBalanceMode.string) } 56 | set { setValue(key: kCGImagePropertyMakerNikonWhiteBalanceMode.string, value: newValue) } 57 | } 58 | 59 | public var sharpenMode: String? { 60 | get { getValue(key: kCGImagePropertyMakerNikonSharpenMode.string) } 61 | set { setValue(key: kCGImagePropertyMakerNikonSharpenMode.string, value: newValue) } 62 | } 63 | 64 | public var focusMode: String? { 65 | get { getValue(key: kCGImagePropertyMakerNikonFocusMode.string) } 66 | set { setValue(key: kCGImagePropertyMakerNikonFocusMode.string, value: newValue) } 67 | } 68 | 69 | public var flashSetting: String? { 70 | get { getValue(key: kCGImagePropertyMakerNikonFlashSetting.string) } 71 | set { setValue(key: kCGImagePropertyMakerNikonFlashSetting.string, value: newValue) } 72 | } 73 | 74 | public var isoSelection: String? { 75 | get { getValue(key: kCGImagePropertyMakerNikonISOSelection.string) } 76 | set { setValue(key: kCGImagePropertyMakerNikonISOSelection.string, value: newValue) } 77 | } 78 | 79 | public var flashExposureComp: NSObject? { 80 | get { getValue(key: kCGImagePropertyMakerNikonFlashExposureComp.string) } 81 | set { setValue(key: kCGImagePropertyMakerNikonFlashExposureComp.string, value: newValue) } 82 | } 83 | 84 | public var imageAdjustment: String? { 85 | get { getValue(key: kCGImagePropertyMakerNikonImageAdjustment.string) } 86 | set { setValue(key: kCGImagePropertyMakerNikonImageAdjustment.string, value: newValue) } 87 | } 88 | 89 | public var lensAdapter: NSObject? { 90 | get { getValue(key: kCGImagePropertyMakerNikonLensAdapter.string) } 91 | set { setValue(key: kCGImagePropertyMakerNikonLensAdapter.string, value: newValue) } 92 | } 93 | 94 | public var lensType: LensType? { 95 | get { getValue(key: kCGImagePropertyMakerNikonLensType.string) } 96 | set { setValue(key: kCGImagePropertyMakerNikonLensType.string, value: newValue) } 97 | } 98 | 99 | public var lensInfo: NSObject? { 100 | get { getValue(key: kCGImagePropertyMakerNikonLensInfo.string) } 101 | set { setValue(key: kCGImagePropertyMakerNikonLensInfo.string, value: newValue) } 102 | } 103 | 104 | public var focusDistance: NSNumber? { 105 | get { getValue(key: kCGImagePropertyMakerNikonFocusDistance.string) } 106 | set { setValue(key: kCGImagePropertyMakerNikonFocusDistance.string, value: newValue) } 107 | } 108 | 109 | public var digitalZoom: NSNumber? { 110 | get { getValue(key: kCGImagePropertyMakerNikonDigitalZoom.string) } 111 | set { setValue(key: kCGImagePropertyMakerNikonDigitalZoom.string, value: newValue) } 112 | } 113 | 114 | public var shootingMode: ShootingMode? { 115 | get { getValue(key: kCGImagePropertyMakerNikonShootingMode.string) } 116 | set { setValue(key: kCGImagePropertyMakerNikonShootingMode.string, value: newValue) } 117 | } 118 | 119 | public var cameraSerialNumber: String? { 120 | get { getValue(key: kCGImagePropertyMakerNikonCameraSerialNumber.string) } 121 | set { setValue(key: kCGImagePropertyMakerNikonCameraSerialNumber.string, value: newValue) } 122 | } 123 | 124 | public var shutterCount: Int? { 125 | get { getValue(key: kCGImagePropertyMakerNikonShutterCount.string) } 126 | set { setValue(key: kCGImagePropertyMakerNikonShutterCount.string, value: newValue) } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerOlympus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerOlympus.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerOlympus : SYMetadataBase { 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataMakerPentax.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataMakerPentax.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataMakerPentax : SYMetadataBase { 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataPNG.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataPNG.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataPNG : SYMetadataBase { 13 | 14 | // MARK: Types 15 | @objc public enum sRGBIntent : Int, CaseIterable { 16 | case perceptual = 0 17 | case relativeColorimetric = 1 18 | case saturation = 2 19 | case absoluteColorimetric = 3 20 | } 21 | 22 | public struct CompressionFilter : OptionSet { 23 | public let rawValue: Int32 24 | public init(rawValue: Int32) { self.rawValue = rawValue } 25 | static let noFilters = CompressionFilter(rawValue: IMAGEIO_PNG_NO_FILTERS) 26 | static let none = CompressionFilter(rawValue: IMAGEIO_PNG_FILTER_NONE) 27 | static let sub = CompressionFilter(rawValue: IMAGEIO_PNG_FILTER_SUB) 28 | static let up = CompressionFilter(rawValue: IMAGEIO_PNG_FILTER_UP) 29 | static let avg = CompressionFilter(rawValue: IMAGEIO_PNG_FILTER_AVG) 30 | static let paeth = CompressionFilter(rawValue: IMAGEIO_PNG_FILTER_PAETH) 31 | static let all = [CompressionFilter.none, sub, .up, .avg, .paeth] // IMAGEIO_PNG_ALL_FILTERS 32 | } 33 | 34 | public enum InterlaceType: Int, CaseIterable { 35 | case nonInterlaced = 0 36 | case adam7Interlace = 1 37 | } 38 | 39 | // MARK: Values 40 | public var gamma: NSNumber? { 41 | get { getValue(key: kCGImagePropertyPNGGamma.string) } 42 | set { setValue(key: kCGImagePropertyPNGGamma.string, value: newValue) } 43 | } 44 | 45 | public var interlaceType: InterlaceType? { 46 | get { getValue(key: kCGImagePropertyPNGInterlaceType.string) } 47 | set { setValue(key: kCGImagePropertyPNGInterlaceType.string, value: newValue) } 48 | } 49 | 50 | public var xPixelsPerMeter: Int? { 51 | get { getValue(key: kCGImagePropertyPNGXPixelsPerMeter.string) } 52 | set { setValue(key: kCGImagePropertyPNGXPixelsPerMeter.string, value: newValue) } 53 | } 54 | 55 | public var yPixelsPerMeter: Int? { 56 | get { getValue(key: kCGImagePropertyPNGYPixelsPerMeter.string) } 57 | set { setValue(key: kCGImagePropertyPNGYPixelsPerMeter.string, value: newValue) } 58 | } 59 | 60 | public var sRGBIntent: sRGBIntent? { 61 | get { getValue(key: kCGImagePropertyPNGsRGBIntent.string) } 62 | set { setValue(key: kCGImagePropertyPNGsRGBIntent.string, value: newValue) } 63 | } 64 | 65 | public var chromaticities: Array? { 66 | get { getValue(key: kCGImagePropertyPNGChromaticities.string) } 67 | set { setValue(key: kCGImagePropertyPNGChromaticities.string, value: newValue) } 68 | } 69 | 70 | public var author: String? { 71 | get { getValue(key: kCGImagePropertyPNGAuthor.string) } 72 | set { setValue(key: kCGImagePropertyPNGAuthor.string, value: newValue) } 73 | } 74 | 75 | public var copyright: String? { 76 | get { getValue(key: kCGImagePropertyPNGCopyright.string) } 77 | set { setValue(key: kCGImagePropertyPNGCopyright.string, value: newValue) } 78 | } 79 | 80 | public var creationTime: String? { 81 | get { getValue(key: kCGImagePropertyPNGCreationTime.string) } 82 | set { setValue(key: kCGImagePropertyPNGCreationTime.string, value: newValue) } 83 | } 84 | 85 | public var descr: String? { 86 | get { getValue(key: kCGImagePropertyPNGDescription.string) } 87 | set { setValue(key: kCGImagePropertyPNGDescription.string, value: newValue) } 88 | } 89 | 90 | public var modificationTime: String? { 91 | get { getValue(key: kCGImagePropertyPNGModificationTime.string) } 92 | set { setValue(key: kCGImagePropertyPNGModificationTime.string, value: newValue) } 93 | } 94 | 95 | public var software: String? { 96 | get { getValue(key: kCGImagePropertyPNGSoftware.string) } 97 | set { setValue(key: kCGImagePropertyPNGSoftware.string, value: newValue) } 98 | } 99 | 100 | public var title: String? { 101 | get { getValue(key: kCGImagePropertyPNGTitle.string) } 102 | set { setValue(key: kCGImagePropertyPNGTitle.string, value: newValue) } 103 | } 104 | 105 | public var loopCount: Int? { 106 | get { getValue(key: kCGImagePropertyAPNGLoopCount.string) } 107 | set { setValue(key: kCGImagePropertyAPNGLoopCount.string, value: newValue) } 108 | } 109 | 110 | public var delayTime: Double? { 111 | get { getValue(key: kCGImagePropertyAPNGDelayTime.string) } 112 | set { setValue(key: kCGImagePropertyAPNGDelayTime.string, value: newValue) } 113 | } 114 | 115 | public var unclampedDelayTime: Double? { 116 | get { getValue(key: kCGImagePropertyAPNGUnclampedDelayTime.string) } 117 | set { setValue(key: kCGImagePropertyAPNGUnclampedDelayTime.string, value: newValue) } 118 | } 119 | 120 | public var compressionFilter: CompressionFilter? { 121 | get { getValue(key: kCGImagePropertyPNGCompressionFilter.string) } 122 | set { setValue(key: kCGImagePropertyPNGCompressionFilter.string, value: newValue) } 123 | } 124 | } 125 | 126 | 127 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataRaw.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataRaw.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 07/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataRaw : SYMetadataBase { 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadata/SYMetadataTIFF.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadataTIFF.swift 3 | // SYPictureMetadata 4 | // 5 | // Created by Stanislas Chevallier on 20/01/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import ImageIO 11 | 12 | @objcMembers public class SYMetadataTIFF : SYMetadataBase { 13 | 14 | // MARK: Types 15 | @objc public enum PhotometricInterpretation : Int, CaseIterable { 16 | case MINISWHITE = 0 17 | case MINISBLACK = 1 18 | case RGB = 2 19 | case PALETTE = 3 20 | case MASK = 4 21 | case SEPARATED = 5 22 | case YCBCR = 6 23 | case CIELAB = 8 24 | case ICCLAB = 9 25 | case ITULAB = 10 26 | case LOGL = 32844 27 | case LOGLUV = 32845 28 | } 29 | 30 | // MARK: Values 31 | public var compression: String? { 32 | get { getValue(key: kCGImagePropertyTIFFCompression.string) } 33 | set { setValue(key: kCGImagePropertyTIFFCompression.string, value: newValue) } 34 | } 35 | 36 | public var photometricInterpretation: PhotometricInterpretation? { 37 | get { getValue(key: kCGImagePropertyTIFFPhotometricInterpretation.string) } 38 | set { setValue(key: kCGImagePropertyTIFFPhotometricInterpretation.string, value: newValue) } 39 | } 40 | 41 | public var documentName: String? { 42 | get { getValue(key: kCGImagePropertyTIFFDocumentName.string) } 43 | set { setValue(key: kCGImagePropertyTIFFDocumentName.string, value: newValue) } 44 | } 45 | 46 | public var imageDescription: String? { 47 | get { getValue(key: kCGImagePropertyTIFFImageDescription.string) } 48 | set { setValue(key: kCGImagePropertyTIFFImageDescription.string, value: newValue) } 49 | } 50 | 51 | public var make: String? { 52 | get { getValue(key: kCGImagePropertyTIFFMake.string) } 53 | set { setValue(key: kCGImagePropertyTIFFMake.string, value: newValue) } 54 | } 55 | 56 | public var model: String? { 57 | get { getValue(key: kCGImagePropertyTIFFModel.string) } 58 | set { setValue(key: kCGImagePropertyTIFFModel.string, value: newValue) } 59 | } 60 | 61 | public var orientation: CGImagePropertyOrientation? { 62 | get { getValue(key: kCGImagePropertyTIFFOrientation.string) } 63 | set { setValue(key: kCGImagePropertyTIFFOrientation.string, value: newValue) } 64 | } 65 | 66 | public var xResolution: Int? { 67 | get { getValue(key: kCGImagePropertyTIFFXResolution.string) } 68 | set { setValue(key: kCGImagePropertyTIFFXResolution.string, value: newValue) } 69 | } 70 | 71 | public var yResolution: Int? { 72 | get { getValue(key: kCGImagePropertyTIFFYResolution.string) } 73 | set { setValue(key: kCGImagePropertyTIFFYResolution.string, value: newValue) } 74 | } 75 | 76 | public var resolutionUnit: Int? { 77 | get { getValue(key: kCGImagePropertyTIFFResolutionUnit.string) } 78 | set { setValue(key: kCGImagePropertyTIFFResolutionUnit.string, value: newValue) } 79 | } 80 | 81 | public var software: String? { 82 | get { getValue(key: kCGImagePropertyTIFFSoftware.string) } 83 | set { setValue(key: kCGImagePropertyTIFFSoftware.string, value: newValue) } 84 | } 85 | 86 | public var transferFunction: Array? { 87 | get { getValue(key: kCGImagePropertyTIFFTransferFunction.string) } 88 | set { setValue(key: kCGImagePropertyTIFFTransferFunction.string, value: newValue) } 89 | } 90 | 91 | public var dateTime: String? { 92 | get { getValue(key: kCGImagePropertyTIFFDateTime.string) } 93 | set { setValue(key: kCGImagePropertyTIFFDateTime.string, value: newValue) } 94 | } 95 | 96 | public var artist: String? { 97 | get { getValue(key: kCGImagePropertyTIFFArtist.string) } 98 | set { setValue(key: kCGImagePropertyTIFFArtist.string, value: newValue) } 99 | } 100 | 101 | public var hostComputer: String? { 102 | get { getValue(key: kCGImagePropertyTIFFHostComputer.string) } 103 | set { setValue(key: kCGImagePropertyTIFFHostComputer.string, value: newValue) } 104 | } 105 | 106 | public var copyright: String? { 107 | get { getValue(key: kCGImagePropertyTIFFCopyright.string) } 108 | set { setValue(key: kCGImagePropertyTIFFCopyright.string, value: newValue) } 109 | } 110 | 111 | public var whitePoint: Array? { 112 | get { getValue(key: kCGImagePropertyTIFFWhitePoint.string) } 113 | set { setValue(key: kCGImagePropertyTIFFWhitePoint.string, value: newValue) } 114 | } 115 | 116 | public var primaryChromaticities: Array? { 117 | get { getValue(key: kCGImagePropertyTIFFPrimaryChromaticities.string) } 118 | set { setValue(key: kCGImagePropertyTIFFPrimaryChromaticities.string, value: newValue) } 119 | } 120 | 121 | public var tileWidth: Int? { 122 | get { getValue(key: kCGImagePropertyTIFFTileWidth.string) } 123 | set { setValue(key: kCGImagePropertyTIFFTileWidth.string, value: newValue) } 124 | } 125 | 126 | public var tileLength: Int? { 127 | get { getValue(key: kCGImagePropertyTIFFTileLength.string) } 128 | set { setValue(key: kCGImagePropertyTIFFTileLength.string, value: newValue) } 129 | } 130 | } 131 | 132 | 133 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/MetadataKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MetadataKeys.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 02/02/2021. 6 | // Copyright © 2021 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | private class EmptyClass: NSObject {} 12 | 13 | public enum MetadataKeys: String, CaseIterable { 14 | case imageIO = "ImageIO.txt" 15 | case supported = "Supported.txt" 16 | case unsupported = "Unsupported.txt" 17 | 18 | public var url: URL { 19 | return Bundle.module.url(forResource: rawValue, withExtension: nil, subdirectory: "MetadataKeys")! 20 | } 21 | 22 | public func read() -> [String] { 23 | try! String(contentsOf: url).components(separatedBy: "\n") 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/MetadataKeys/Supported.txt: -------------------------------------------------------------------------------- 1 | kCGImageProperty8BIMDictionary 2 | kCGImageProperty8BIMLayerNames 3 | kCGImageProperty8BIMVersion 4 | kCGImagePropertyAPNGDelayTime 5 | kCGImagePropertyAPNGLoopCount 6 | kCGImagePropertyAPNGUnclampedDelayTime 7 | kCGImagePropertyCIFFCameraSerialNumber 8 | kCGImagePropertyCIFFContinuousDrive 9 | kCGImagePropertyCIFFDescription 10 | kCGImagePropertyCIFFDictionary 11 | kCGImagePropertyCIFFFirmware 12 | kCGImagePropertyCIFFFlashExposureComp 13 | kCGImagePropertyCIFFFocusMode 14 | kCGImagePropertyCIFFImageFileName 15 | kCGImagePropertyCIFFImageName 16 | kCGImagePropertyCIFFImageSerialNumber 17 | kCGImagePropertyCIFFLensMaxMM 18 | kCGImagePropertyCIFFLensMinMM 19 | kCGImagePropertyCIFFLensModel 20 | kCGImagePropertyCIFFMeasuredEV 21 | kCGImagePropertyCIFFMeteringMode 22 | kCGImagePropertyCIFFOwnerName 23 | kCGImagePropertyCIFFRecordID 24 | kCGImagePropertyCIFFReleaseMethod 25 | kCGImagePropertyCIFFReleaseTiming 26 | kCGImagePropertyCIFFSelfTimingTime 27 | kCGImagePropertyCIFFShootingMode 28 | kCGImagePropertyCIFFWhiteBalanceIndex 29 | kCGImagePropertyColorModel 30 | kCGImagePropertyColorModelCMYK 31 | kCGImagePropertyColorModelGray 32 | kCGImagePropertyColorModelLab 33 | kCGImagePropertyColorModelRGB 34 | kCGImagePropertyDNGAsShotNeutral 35 | kCGImagePropertyDNGAsShotWhiteXY 36 | kCGImagePropertyDNGBackwardVersion 37 | kCGImagePropertyDNGBaselineExposure 38 | kCGImagePropertyDNGBaselineNoise 39 | kCGImagePropertyDNGBaselineSharpness 40 | kCGImagePropertyDNGBlackLevel 41 | kCGImagePropertyDNGCalibrationIlluminant1 42 | kCGImagePropertyDNGCalibrationIlluminant2 43 | kCGImagePropertyDNGCameraCalibration1 44 | kCGImagePropertyDNGCameraCalibration2 45 | kCGImagePropertyDNGCameraCalibrationSignature 46 | kCGImagePropertyDNGCameraSerialNumber 47 | kCGImagePropertyDNGColorMatrix1 48 | kCGImagePropertyDNGColorMatrix2 49 | kCGImagePropertyDNGDictionary 50 | kCGImagePropertyDNGFixVignetteRadial 51 | kCGImagePropertyDNGLensInfo 52 | kCGImagePropertyDNGLocalizedCameraModel 53 | kCGImagePropertyDNGNoiseProfile 54 | kCGImagePropertyDNGPrivateData 55 | kCGImagePropertyDNGProfileCalibrationSignature 56 | kCGImagePropertyDNGUniqueCameraModel 57 | kCGImagePropertyDNGVersion 58 | kCGImagePropertyDNGWarpFisheye 59 | kCGImagePropertyDNGWarpRectilinear 60 | kCGImagePropertyDNGWhiteLevel 61 | kCGImagePropertyDPIHeight 62 | kCGImagePropertyDPIWidth 63 | kCGImagePropertyDepth 64 | kCGImagePropertyExifApertureValue 65 | kCGImagePropertyExifAuxDictionary 66 | kCGImagePropertyExifAuxFirmware 67 | kCGImagePropertyExifAuxFlashCompensation 68 | kCGImagePropertyExifAuxImageNumber 69 | kCGImagePropertyExifAuxLensID 70 | kCGImagePropertyExifAuxLensInfo 71 | kCGImagePropertyExifAuxLensModel 72 | kCGImagePropertyExifAuxLensSerialNumber 73 | kCGImagePropertyExifAuxOwnerName 74 | kCGImagePropertyExifAuxSerialNumber 75 | kCGImagePropertyExifBodySerialNumber 76 | kCGImagePropertyExifBrightnessValue 77 | kCGImagePropertyExifCFAPattern 78 | kCGImagePropertyExifCameraOwnerName 79 | kCGImagePropertyExifColorSpace 80 | kCGImagePropertyExifComponentsConfiguration 81 | kCGImagePropertyExifCompressedBitsPerPixel 82 | kCGImagePropertyExifContrast 83 | kCGImagePropertyExifCustomRendered 84 | kCGImagePropertyExifDateTimeDigitized 85 | kCGImagePropertyExifDateTimeOriginal 86 | kCGImagePropertyExifDeviceSettingDescription 87 | kCGImagePropertyExifDictionary 88 | kCGImagePropertyExifDigitalZoomRatio 89 | kCGImagePropertyExifExposureBiasValue 90 | kCGImagePropertyExifExposureIndex 91 | kCGImagePropertyExifExposureMode 92 | kCGImagePropertyExifExposureProgram 93 | kCGImagePropertyExifExposureTime 94 | kCGImagePropertyExifFNumber 95 | kCGImagePropertyExifFileSource 96 | kCGImagePropertyExifFlash 97 | kCGImagePropertyExifFlashEnergy 98 | kCGImagePropertyExifFlashPixVersion 99 | kCGImagePropertyExifFocalLenIn35mmFilm 100 | kCGImagePropertyExifFocalLength 101 | kCGImagePropertyExifFocalPlaneResolutionUnit 102 | kCGImagePropertyExifFocalPlaneXResolution 103 | kCGImagePropertyExifFocalPlaneYResolution 104 | kCGImagePropertyExifISOSpeed 105 | kCGImagePropertyExifISOSpeedLatitudeyyy 106 | kCGImagePropertyExifISOSpeedLatitudezzz 107 | kCGImagePropertyExifISOSpeedRatings 108 | kCGImagePropertyExifImageUniqueID 109 | kCGImagePropertyExifLensMake 110 | kCGImagePropertyExifLensModel 111 | kCGImagePropertyExifLensSerialNumber 112 | kCGImagePropertyExifLensSpecification 113 | kCGImagePropertyExifLightSource 114 | kCGImagePropertyExifMakerNote 115 | kCGImagePropertyExifMaxApertureValue 116 | kCGImagePropertyExifMeteringMode 117 | kCGImagePropertyExifOECF 118 | kCGImagePropertyExifPixelXDimension 119 | kCGImagePropertyExifPixelYDimension 120 | kCGImagePropertyExifRecommendedExposureIndex 121 | kCGImagePropertyExifRelatedSoundFile 122 | kCGImagePropertyExifSaturation 123 | kCGImagePropertyExifSceneCaptureType 124 | kCGImagePropertyExifSceneType 125 | kCGImagePropertyExifSensingMethod 126 | kCGImagePropertyExifSensitivityType 127 | kCGImagePropertyExifSharpness 128 | kCGImagePropertyExifShutterSpeedValue 129 | kCGImagePropertyExifSpatialFrequencyResponse 130 | kCGImagePropertyExifSpectralSensitivity 131 | kCGImagePropertyExifStandardOutputSensitivity 132 | kCGImagePropertyExifSubjectArea 133 | kCGImagePropertyExifSubjectDistRange 134 | kCGImagePropertyExifSubjectDistance 135 | kCGImagePropertyExifSubjectLocation 136 | kCGImagePropertyExifSubsecTime 137 | kCGImagePropertyExifSubsecTimeDigitized 138 | kCGImagePropertyExifSubsecTimeOriginal 139 | kCGImagePropertyExifUserComment 140 | kCGImagePropertyExifVersion 141 | kCGImagePropertyExifWhiteBalance 142 | kCGImagePropertyFileSize 143 | kCGImagePropertyGIFDelayTime 144 | kCGImagePropertyGIFDictionary 145 | kCGImagePropertyGIFHasGlobalColorMap 146 | kCGImagePropertyGIFImageColorMap 147 | kCGImagePropertyGIFLoopCount 148 | kCGImagePropertyGIFUnclampedDelayTime 149 | kCGImagePropertyGPSAltitude 150 | kCGImagePropertyGPSAltitudeRef 151 | kCGImagePropertyGPSAreaInformation 152 | kCGImagePropertyGPSDOP 153 | kCGImagePropertyGPSDateStamp 154 | kCGImagePropertyGPSDestBearing 155 | kCGImagePropertyGPSDestBearingRef 156 | kCGImagePropertyGPSDestDistance 157 | kCGImagePropertyGPSDestDistanceRef 158 | kCGImagePropertyGPSDestLatitude 159 | kCGImagePropertyGPSDestLatitudeRef 160 | kCGImagePropertyGPSDestLongitude 161 | kCGImagePropertyGPSDestLongitudeRef 162 | kCGImagePropertyGPSDictionary 163 | kCGImagePropertyGPSDifferental 164 | kCGImagePropertyGPSHPositioningError 165 | kCGImagePropertyGPSImgDirection 166 | kCGImagePropertyGPSImgDirectionRef 167 | kCGImagePropertyGPSLatitude 168 | kCGImagePropertyGPSLatitudeRef 169 | kCGImagePropertyGPSLongitude 170 | kCGImagePropertyGPSLongitudeRef 171 | kCGImagePropertyGPSMapDatum 172 | kCGImagePropertyGPSMeasureMode 173 | kCGImagePropertyGPSProcessingMethod 174 | kCGImagePropertyGPSSatellites 175 | kCGImagePropertyGPSSpeed 176 | kCGImagePropertyGPSSpeedRef 177 | kCGImagePropertyGPSStatus 178 | kCGImagePropertyGPSTimeStamp 179 | kCGImagePropertyGPSTrack 180 | kCGImagePropertyGPSTrackRef 181 | kCGImagePropertyGPSVersion 182 | kCGImagePropertyHasAlpha 183 | kCGImagePropertyIPTCActionAdvised 184 | kCGImagePropertyIPTCByline 185 | kCGImagePropertyIPTCBylineTitle 186 | kCGImagePropertyIPTCCaptionAbstract 187 | kCGImagePropertyIPTCCategory 188 | kCGImagePropertyIPTCCity 189 | kCGImagePropertyIPTCContact 190 | kCGImagePropertyIPTCContactInfoAddress 191 | kCGImagePropertyIPTCContactInfoCity 192 | kCGImagePropertyIPTCContactInfoCountry 193 | kCGImagePropertyIPTCContactInfoEmails 194 | kCGImagePropertyIPTCContactInfoPhones 195 | kCGImagePropertyIPTCContactInfoPostalCode 196 | kCGImagePropertyIPTCContactInfoStateProvince 197 | kCGImagePropertyIPTCContactInfoWebURLs 198 | kCGImagePropertyIPTCContentLocationCode 199 | kCGImagePropertyIPTCContentLocationName 200 | kCGImagePropertyIPTCCopyrightNotice 201 | kCGImagePropertyIPTCCountryPrimaryLocationCode 202 | kCGImagePropertyIPTCCountryPrimaryLocationName 203 | kCGImagePropertyIPTCCreatorContactInfo 204 | kCGImagePropertyIPTCCredit 205 | kCGImagePropertyIPTCDateCreated 206 | kCGImagePropertyIPTCDictionary 207 | kCGImagePropertyIPTCDigitalCreationDate 208 | kCGImagePropertyIPTCDigitalCreationTime 209 | kCGImagePropertyIPTCEditStatus 210 | kCGImagePropertyIPTCEditorialUpdate 211 | kCGImagePropertyIPTCExpirationDate 212 | kCGImagePropertyIPTCExpirationTime 213 | kCGImagePropertyIPTCFixtureIdentifier 214 | kCGImagePropertyIPTCHeadline 215 | kCGImagePropertyIPTCImageOrientation 216 | kCGImagePropertyIPTCImageType 217 | kCGImagePropertyIPTCKeywords 218 | kCGImagePropertyIPTCLanguageIdentifier 219 | kCGImagePropertyIPTCObjectAttributeReference 220 | kCGImagePropertyIPTCObjectCycle 221 | kCGImagePropertyIPTCObjectName 222 | kCGImagePropertyIPTCObjectTypeReference 223 | kCGImagePropertyIPTCOriginalTransmissionReference 224 | kCGImagePropertyIPTCOriginatingProgram 225 | kCGImagePropertyIPTCProgramVersion 226 | kCGImagePropertyIPTCProvinceState 227 | kCGImagePropertyIPTCReferenceDate 228 | kCGImagePropertyIPTCReferenceNumber 229 | kCGImagePropertyIPTCReferenceService 230 | kCGImagePropertyIPTCReleaseDate 231 | kCGImagePropertyIPTCReleaseTime 232 | kCGImagePropertyIPTCRightsUsageTerms 233 | kCGImagePropertyIPTCScene 234 | kCGImagePropertyIPTCSource 235 | kCGImagePropertyIPTCSpecialInstructions 236 | kCGImagePropertyIPTCStarRating 237 | kCGImagePropertyIPTCSubLocation 238 | kCGImagePropertyIPTCSubjectReference 239 | kCGImagePropertyIPTCSupplementalCategory 240 | kCGImagePropertyIPTCTimeCreated 241 | kCGImagePropertyIPTCUrgency 242 | kCGImagePropertyIPTCWriterEditor 243 | kCGImagePropertyIsFloat 244 | kCGImagePropertyIsIndexed 245 | kCGImagePropertyJFIFDensityUnit 246 | kCGImagePropertyJFIFDictionary 247 | kCGImagePropertyJFIFIsProgressive 248 | kCGImagePropertyJFIFVersion 249 | kCGImagePropertyJFIFXDensity 250 | kCGImagePropertyJFIFYDensity 251 | kCGImagePropertyMakerAppleDictionary 252 | kCGImagePropertyMakerCanonAspectRatioInfo 253 | kCGImagePropertyMakerCanonCameraSerialNumber 254 | kCGImagePropertyMakerCanonContinuousDrive 255 | kCGImagePropertyMakerCanonDictionary 256 | kCGImagePropertyMakerCanonFirmware 257 | kCGImagePropertyMakerCanonFlashExposureComp 258 | kCGImagePropertyMakerCanonImageSerialNumber 259 | kCGImagePropertyMakerCanonLensModel 260 | kCGImagePropertyMakerCanonOwnerName 261 | kCGImagePropertyMakerFujiDictionary 262 | kCGImagePropertyMakerMinoltaDictionary 263 | kCGImagePropertyMakerNikonCameraSerialNumber 264 | kCGImagePropertyMakerNikonColorMode 265 | kCGImagePropertyMakerNikonDictionary 266 | kCGImagePropertyMakerNikonDigitalZoom 267 | kCGImagePropertyMakerNikonFlashExposureComp 268 | kCGImagePropertyMakerNikonFlashSetting 269 | kCGImagePropertyMakerNikonFocusDistance 270 | kCGImagePropertyMakerNikonFocusMode 271 | kCGImagePropertyMakerNikonISOSelection 272 | kCGImagePropertyMakerNikonISOSetting 273 | kCGImagePropertyMakerNikonImageAdjustment 274 | kCGImagePropertyMakerNikonLensAdapter 275 | kCGImagePropertyMakerNikonLensInfo 276 | kCGImagePropertyMakerNikonLensType 277 | kCGImagePropertyMakerNikonQuality 278 | kCGImagePropertyMakerNikonSharpenMode 279 | kCGImagePropertyMakerNikonShootingMode 280 | kCGImagePropertyMakerNikonShutterCount 281 | kCGImagePropertyMakerNikonWhiteBalanceMode 282 | kCGImagePropertyMakerOlympusDictionary 283 | kCGImagePropertyMakerPentaxDictionary 284 | kCGImagePropertyOrientation 285 | kCGImagePropertyPNGAuthor 286 | kCGImagePropertyPNGChromaticities 287 | kCGImagePropertyPNGCompressionFilter 288 | kCGImagePropertyPNGCopyright 289 | kCGImagePropertyPNGCreationTime 290 | kCGImagePropertyPNGDescription 291 | kCGImagePropertyPNGDictionary 292 | kCGImagePropertyPNGGamma 293 | kCGImagePropertyPNGInterlaceType 294 | kCGImagePropertyPNGModificationTime 295 | kCGImagePropertyPNGSoftware 296 | kCGImagePropertyPNGTitle 297 | kCGImagePropertyPNGXPixelsPerMeter 298 | kCGImagePropertyPNGYPixelsPerMeter 299 | kCGImagePropertyPNGsRGBIntent 300 | kCGImagePropertyPixelHeight 301 | kCGImagePropertyPixelWidth 302 | kCGImagePropertyProfileName 303 | kCGImagePropertyRawDictionary 304 | kCGImagePropertyTIFFArtist 305 | kCGImagePropertyTIFFCompression 306 | kCGImagePropertyTIFFCopyright 307 | kCGImagePropertyTIFFDateTime 308 | kCGImagePropertyTIFFDictionary 309 | kCGImagePropertyTIFFDocumentName 310 | kCGImagePropertyTIFFHostComputer 311 | kCGImagePropertyTIFFImageDescription 312 | kCGImagePropertyTIFFMake 313 | kCGImagePropertyTIFFModel 314 | kCGImagePropertyTIFFOrientation 315 | kCGImagePropertyTIFFPhotometricInterpretation 316 | kCGImagePropertyTIFFPrimaryChromaticities 317 | kCGImagePropertyTIFFResolutionUnit 318 | kCGImagePropertyTIFFSoftware 319 | kCGImagePropertyTIFFTileLength 320 | kCGImagePropertyTIFFTileWidth 321 | kCGImagePropertyTIFFTransferFunction 322 | kCGImagePropertyTIFFWhitePoint 323 | kCGImagePropertyTIFFXResolution 324 | kCGImagePropertyTIFFYResolution 325 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/MetadataKeys/Unsupported.txt: -------------------------------------------------------------------------------- 1 | kCGImagePropertyAPNGCanvasPixelHeight 2 | kCGImagePropertyAPNGCanvasPixelWidth 3 | kCGImagePropertyAPNGFrameInfoArray 4 | kCGImagePropertyAVISDictionary 5 | kCGImagePropertyAuxiliaryData 6 | kCGImagePropertyAuxiliaryDataType 7 | kCGImagePropertyBytesPerRow 8 | kCGImagePropertyDNGActiveArea 9 | kCGImagePropertyDNGAnalogBalance 10 | kCGImagePropertyDNGAntiAliasStrength 11 | kCGImagePropertyDNGAsShotICCProfile 12 | kCGImagePropertyDNGAsShotPreProfileMatrix 13 | kCGImagePropertyDNGAsShotProfileName 14 | kCGImagePropertyDNGBayerGreenSplit 15 | kCGImagePropertyDNGBestQualityScale 16 | kCGImagePropertyDNGCFALayout 17 | kCGImagePropertyDNGCFAPlaneColor 18 | kCGImagePropertyDNGChromaBlurRadius 19 | kCGImagePropertyDNGColorimetricReference 20 | kCGImagePropertyDNGCurrentICCProfile 21 | kCGImagePropertyDNGCurrentPreProfileMatrix 22 | kCGImagePropertyDNGDefaultBlackRender 23 | kCGImagePropertyDNGDefaultCropOrigin 24 | kCGImagePropertyDNGDefaultCropSize 25 | kCGImagePropertyDNGDefaultScale 26 | kCGImagePropertyDNGDefaultUserCrop 27 | kCGImagePropertyDNGExtraCameraProfiles 28 | kCGImagePropertyDNGForwardMatrix1 29 | kCGImagePropertyDNGForwardMatrix2 30 | kCGImagePropertyDNGLinearResponseLimit 31 | kCGImagePropertyDNGLinearizationTable 32 | kCGImagePropertyDNGMakerNoteSafety 33 | kCGImagePropertyDNGMaskedAreas 34 | kCGImagePropertyDNGNewRawImageDigest 35 | kCGImagePropertyDNGNoiseReductionApplied 36 | kCGImagePropertyDNGOpcodeList1 37 | kCGImagePropertyDNGOpcodeList2 38 | kCGImagePropertyDNGOpcodeList3 39 | kCGImagePropertyDNGOriginalBestQualityFinalSize 40 | kCGImagePropertyDNGOriginalDefaultCropSize 41 | kCGImagePropertyDNGOriginalDefaultFinalSize 42 | kCGImagePropertyDNGOriginalRawFileData 43 | kCGImagePropertyDNGOriginalRawFileDigest 44 | kCGImagePropertyDNGOriginalRawFileName 45 | kCGImagePropertyDNGPreviewApplicationName 46 | kCGImagePropertyDNGPreviewApplicationVersion 47 | kCGImagePropertyDNGPreviewColorSpace 48 | kCGImagePropertyDNGPreviewDateTime 49 | kCGImagePropertyDNGPreviewSettingsDigest 50 | kCGImagePropertyDNGPreviewSettingsName 51 | kCGImagePropertyDNGProfileCopyright 52 | kCGImagePropertyDNGProfileEmbedPolicy 53 | kCGImagePropertyDNGProfileHueSatMapData1 54 | kCGImagePropertyDNGProfileHueSatMapData2 55 | kCGImagePropertyDNGProfileHueSatMapDims 56 | kCGImagePropertyDNGProfileHueSatMapEncoding 57 | kCGImagePropertyDNGProfileLookTableData 58 | kCGImagePropertyDNGProfileLookTableDims 59 | kCGImagePropertyDNGProfileLookTableEncoding 60 | kCGImagePropertyDNGProfileName 61 | kCGImagePropertyDNGProfileToneCurve 62 | kCGImagePropertyDNGRawDataUniqueID 63 | kCGImagePropertyDNGRawImageDigest 64 | kCGImagePropertyDNGRawToPreviewGain 65 | kCGImagePropertyDNGReductionMatrix1 66 | kCGImagePropertyDNGReductionMatrix2 67 | kCGImagePropertyDNGRowInterleaveFactor 68 | kCGImagePropertyDNGShadowScale 69 | kCGImagePropertyDNGSubTileBlockSize 70 | kCGImagePropertyExifCompositeImage 71 | kCGImagePropertyExifGainControl 72 | kCGImagePropertyExifGamma 73 | kCGImagePropertyExifOffsetTime 74 | kCGImagePropertyExifOffsetTimeDigitized 75 | kCGImagePropertyExifOffsetTimeOriginal 76 | kCGImagePropertyExifSourceExposureTimesOfCompositeImage 77 | kCGImagePropertyExifSourceImageNumberOfCompositeImage 78 | kCGImagePropertyFileContentsDictionary 79 | kCGImagePropertyGIFCanvasPixelHeight 80 | kCGImagePropertyGIFCanvasPixelWidth 81 | kCGImagePropertyGIFFrameInfoArray 82 | kCGImagePropertyGroupImageBaseline 83 | kCGImagePropertyGroupImageDisparityAdjustment 84 | kCGImagePropertyGroupImageIndexLeft 85 | kCGImagePropertyGroupImageIndexRight 86 | kCGImagePropertyGroupImageIsAlternateImage 87 | kCGImagePropertyGroupImageIsLeftImage 88 | kCGImagePropertyGroupImageIsRightImage 89 | kCGImagePropertyGroupImagesAlternate 90 | kCGImagePropertyGroupIndex 91 | kCGImagePropertyGroupType 92 | kCGImagePropertyGroupTypeAlternate 93 | kCGImagePropertyGroupTypeStereoPair 94 | kCGImagePropertyGroups 95 | kCGImagePropertyHEICSCanvasPixelHeight 96 | kCGImagePropertyHEICSCanvasPixelWidth 97 | kCGImagePropertyHEICSDelayTime 98 | kCGImagePropertyHEICSDictionary 99 | kCGImagePropertyHEICSFrameInfoArray 100 | kCGImagePropertyHEICSLoopCount 101 | kCGImagePropertyHEICSUnclampedDelayTime 102 | kCGImagePropertyHEIFDictionary 103 | kCGImagePropertyHeight 104 | kCGImagePropertyIPTCExtAboutCvTerm 105 | kCGImagePropertyIPTCExtAboutCvTermCvId 106 | kCGImagePropertyIPTCExtAboutCvTermId 107 | kCGImagePropertyIPTCExtAboutCvTermName 108 | kCGImagePropertyIPTCExtAboutCvTermRefinedAbout 109 | kCGImagePropertyIPTCExtAddlModelInfo 110 | kCGImagePropertyIPTCExtArtworkCircaDateCreated 111 | kCGImagePropertyIPTCExtArtworkContentDescription 112 | kCGImagePropertyIPTCExtArtworkContributionDescription 113 | kCGImagePropertyIPTCExtArtworkCopyrightNotice 114 | kCGImagePropertyIPTCExtArtworkCopyrightOwnerID 115 | kCGImagePropertyIPTCExtArtworkCopyrightOwnerName 116 | kCGImagePropertyIPTCExtArtworkCreator 117 | kCGImagePropertyIPTCExtArtworkCreatorID 118 | kCGImagePropertyIPTCExtArtworkDateCreated 119 | kCGImagePropertyIPTCExtArtworkLicensorID 120 | kCGImagePropertyIPTCExtArtworkLicensorName 121 | kCGImagePropertyIPTCExtArtworkOrObject 122 | kCGImagePropertyIPTCExtArtworkPhysicalDescription 123 | kCGImagePropertyIPTCExtArtworkSource 124 | kCGImagePropertyIPTCExtArtworkSourceInvURL 125 | kCGImagePropertyIPTCExtArtworkSourceInventoryNo 126 | kCGImagePropertyIPTCExtArtworkStylePeriod 127 | kCGImagePropertyIPTCExtArtworkTitle 128 | kCGImagePropertyIPTCExtAudioBitrate 129 | kCGImagePropertyIPTCExtAudioBitrateMode 130 | kCGImagePropertyIPTCExtAudioChannelCount 131 | kCGImagePropertyIPTCExtCircaDateCreated 132 | kCGImagePropertyIPTCExtContainerFormat 133 | kCGImagePropertyIPTCExtContainerFormatIdentifier 134 | kCGImagePropertyIPTCExtContainerFormatName 135 | kCGImagePropertyIPTCExtContributor 136 | kCGImagePropertyIPTCExtContributorIdentifier 137 | kCGImagePropertyIPTCExtContributorName 138 | kCGImagePropertyIPTCExtContributorRole 139 | kCGImagePropertyIPTCExtControlledVocabularyTerm 140 | kCGImagePropertyIPTCExtCopyrightYear 141 | kCGImagePropertyIPTCExtCreator 142 | kCGImagePropertyIPTCExtCreatorIdentifier 143 | kCGImagePropertyIPTCExtCreatorName 144 | kCGImagePropertyIPTCExtCreatorRole 145 | kCGImagePropertyIPTCExtDataOnScreen 146 | kCGImagePropertyIPTCExtDataOnScreenRegion 147 | kCGImagePropertyIPTCExtDataOnScreenRegionD 148 | kCGImagePropertyIPTCExtDataOnScreenRegionH 149 | kCGImagePropertyIPTCExtDataOnScreenRegionText 150 | kCGImagePropertyIPTCExtDataOnScreenRegionUnit 151 | kCGImagePropertyIPTCExtDataOnScreenRegionW 152 | kCGImagePropertyIPTCExtDataOnScreenRegionX 153 | kCGImagePropertyIPTCExtDataOnScreenRegionY 154 | kCGImagePropertyIPTCExtDigitalImageGUID 155 | kCGImagePropertyIPTCExtDigitalSourceFileType 156 | kCGImagePropertyIPTCExtDigitalSourceType 157 | kCGImagePropertyIPTCExtDopesheet 158 | kCGImagePropertyIPTCExtDopesheetLink 159 | kCGImagePropertyIPTCExtDopesheetLinkLink 160 | kCGImagePropertyIPTCExtDopesheetLinkLinkQualifier 161 | kCGImagePropertyIPTCExtEmbdEncRightsExpr 162 | kCGImagePropertyIPTCExtEmbeddedEncodedRightsExpr 163 | kCGImagePropertyIPTCExtEmbeddedEncodedRightsExprLangID 164 | kCGImagePropertyIPTCExtEmbeddedEncodedRightsExprType 165 | kCGImagePropertyIPTCExtEpisode 166 | kCGImagePropertyIPTCExtEpisodeIdentifier 167 | kCGImagePropertyIPTCExtEpisodeName 168 | kCGImagePropertyIPTCExtEpisodeNumber 169 | kCGImagePropertyIPTCExtEvent 170 | kCGImagePropertyIPTCExtExternalMetadataLink 171 | kCGImagePropertyIPTCExtFeedIdentifier 172 | kCGImagePropertyIPTCExtGenre 173 | kCGImagePropertyIPTCExtGenreCvId 174 | kCGImagePropertyIPTCExtGenreCvTermId 175 | kCGImagePropertyIPTCExtGenreCvTermName 176 | kCGImagePropertyIPTCExtGenreCvTermRefinedAbout 177 | kCGImagePropertyIPTCExtHeadline 178 | kCGImagePropertyIPTCExtIPTCLastEdited 179 | kCGImagePropertyIPTCExtLinkedEncRightsExpr 180 | kCGImagePropertyIPTCExtLinkedEncodedRightsExpr 181 | kCGImagePropertyIPTCExtLinkedEncodedRightsExprLangID 182 | kCGImagePropertyIPTCExtLinkedEncodedRightsExprType 183 | kCGImagePropertyIPTCExtLocationCity 184 | kCGImagePropertyIPTCExtLocationCountryCode 185 | kCGImagePropertyIPTCExtLocationCountryName 186 | kCGImagePropertyIPTCExtLocationCreated 187 | kCGImagePropertyIPTCExtLocationGPSAltitude 188 | kCGImagePropertyIPTCExtLocationGPSLatitude 189 | kCGImagePropertyIPTCExtLocationGPSLongitude 190 | kCGImagePropertyIPTCExtLocationIdentifier 191 | kCGImagePropertyIPTCExtLocationLocationId 192 | kCGImagePropertyIPTCExtLocationLocationName 193 | kCGImagePropertyIPTCExtLocationProvinceState 194 | kCGImagePropertyIPTCExtLocationShown 195 | kCGImagePropertyIPTCExtLocationSublocation 196 | kCGImagePropertyIPTCExtLocationWorldRegion 197 | kCGImagePropertyIPTCExtMaxAvailHeight 198 | kCGImagePropertyIPTCExtMaxAvailWidth 199 | kCGImagePropertyIPTCExtModelAge 200 | kCGImagePropertyIPTCExtOrganisationInImageCode 201 | kCGImagePropertyIPTCExtOrganisationInImageName 202 | kCGImagePropertyIPTCExtPersonHeard 203 | kCGImagePropertyIPTCExtPersonHeardIdentifier 204 | kCGImagePropertyIPTCExtPersonHeardName 205 | kCGImagePropertyIPTCExtPersonInImage 206 | kCGImagePropertyIPTCExtPersonInImageCharacteristic 207 | kCGImagePropertyIPTCExtPersonInImageCvTermCvId 208 | kCGImagePropertyIPTCExtPersonInImageCvTermId 209 | kCGImagePropertyIPTCExtPersonInImageCvTermName 210 | kCGImagePropertyIPTCExtPersonInImageCvTermRefinedAbout 211 | kCGImagePropertyIPTCExtPersonInImageDescription 212 | kCGImagePropertyIPTCExtPersonInImageId 213 | kCGImagePropertyIPTCExtPersonInImageName 214 | kCGImagePropertyIPTCExtPersonInImageWDetails 215 | kCGImagePropertyIPTCExtProductInImage 216 | kCGImagePropertyIPTCExtProductInImageDescription 217 | kCGImagePropertyIPTCExtProductInImageGTIN 218 | kCGImagePropertyIPTCExtProductInImageName 219 | kCGImagePropertyIPTCExtPublicationEvent 220 | kCGImagePropertyIPTCExtPublicationEventDate 221 | kCGImagePropertyIPTCExtPublicationEventIdentifier 222 | kCGImagePropertyIPTCExtPublicationEventName 223 | kCGImagePropertyIPTCExtRating 224 | kCGImagePropertyIPTCExtRatingRatingRegion 225 | kCGImagePropertyIPTCExtRatingRegionCity 226 | kCGImagePropertyIPTCExtRatingRegionCountryCode 227 | kCGImagePropertyIPTCExtRatingRegionCountryName 228 | kCGImagePropertyIPTCExtRatingRegionGPSAltitude 229 | kCGImagePropertyIPTCExtRatingRegionGPSLatitude 230 | kCGImagePropertyIPTCExtRatingRegionGPSLongitude 231 | kCGImagePropertyIPTCExtRatingRegionIdentifier 232 | kCGImagePropertyIPTCExtRatingRegionLocationId 233 | kCGImagePropertyIPTCExtRatingRegionLocationName 234 | kCGImagePropertyIPTCExtRatingRegionProvinceState 235 | kCGImagePropertyIPTCExtRatingRegionSublocation 236 | kCGImagePropertyIPTCExtRatingRegionWorldRegion 237 | kCGImagePropertyIPTCExtRatingScaleMaxValue 238 | kCGImagePropertyIPTCExtRatingScaleMinValue 239 | kCGImagePropertyIPTCExtRatingSourceLink 240 | kCGImagePropertyIPTCExtRatingValue 241 | kCGImagePropertyIPTCExtRatingValueLogoLink 242 | kCGImagePropertyIPTCExtRegistryEntryRole 243 | kCGImagePropertyIPTCExtRegistryID 244 | kCGImagePropertyIPTCExtRegistryItemID 245 | kCGImagePropertyIPTCExtRegistryOrganisationID 246 | kCGImagePropertyIPTCExtReleaseReady 247 | kCGImagePropertyIPTCExtSeason 248 | kCGImagePropertyIPTCExtSeasonIdentifier 249 | kCGImagePropertyIPTCExtSeasonName 250 | kCGImagePropertyIPTCExtSeasonNumber 251 | kCGImagePropertyIPTCExtSeries 252 | kCGImagePropertyIPTCExtSeriesIdentifier 253 | kCGImagePropertyIPTCExtSeriesName 254 | kCGImagePropertyIPTCExtShownEvent 255 | kCGImagePropertyIPTCExtShownEventIdentifier 256 | kCGImagePropertyIPTCExtShownEventName 257 | kCGImagePropertyIPTCExtStorylineIdentifier 258 | kCGImagePropertyIPTCExtStreamReady 259 | kCGImagePropertyIPTCExtStylePeriod 260 | kCGImagePropertyIPTCExtSupplyChainSource 261 | kCGImagePropertyIPTCExtSupplyChainSourceIdentifier 262 | kCGImagePropertyIPTCExtSupplyChainSourceName 263 | kCGImagePropertyIPTCExtTemporalCoverage 264 | kCGImagePropertyIPTCExtTemporalCoverageFrom 265 | kCGImagePropertyIPTCExtTemporalCoverageTo 266 | kCGImagePropertyIPTCExtTranscript 267 | kCGImagePropertyIPTCExtTranscriptLink 268 | kCGImagePropertyIPTCExtTranscriptLinkLink 269 | kCGImagePropertyIPTCExtTranscriptLinkLinkQualifier 270 | kCGImagePropertyIPTCExtVideoBitrate 271 | kCGImagePropertyIPTCExtVideoBitrateMode 272 | kCGImagePropertyIPTCExtVideoDisplayAspectRatio 273 | kCGImagePropertyIPTCExtVideoEncodingProfile 274 | kCGImagePropertyIPTCExtVideoShotType 275 | kCGImagePropertyIPTCExtVideoShotTypeIdentifier 276 | kCGImagePropertyIPTCExtVideoShotTypeName 277 | kCGImagePropertyIPTCExtVideoStreamsCount 278 | kCGImagePropertyIPTCExtVisualColor 279 | kCGImagePropertyIPTCExtWorkflowTag 280 | kCGImagePropertyIPTCExtWorkflowTagCvId 281 | kCGImagePropertyIPTCExtWorkflowTagCvTermId 282 | kCGImagePropertyIPTCExtWorkflowTagCvTermName 283 | kCGImagePropertyIPTCExtWorkflowTagCvTermRefinedAbout 284 | kCGImagePropertyImageCount 285 | kCGImagePropertyImageIndex 286 | kCGImagePropertyImages 287 | kCGImagePropertyNamedColorSpace 288 | kCGImagePropertyOpenEXRAspectRatio 289 | kCGImagePropertyOpenEXRDictionary 290 | kCGImagePropertyPNGComment 291 | kCGImagePropertyPNGDisclaimer 292 | kCGImagePropertyPNGPixelsAspectRatio 293 | kCGImagePropertyPNGSource 294 | kCGImagePropertyPNGTransparency 295 | kCGImagePropertyPNGWarning 296 | kCGImagePropertyPixelFormat 297 | kCGImagePropertyPrimaryImage 298 | kCGImagePropertyTGACompression 299 | kCGImagePropertyTGADictionary 300 | kCGImagePropertyThumbnailImages 301 | kCGImagePropertyWebPCanvasPixelHeight 302 | kCGImagePropertyWebPCanvasPixelWidth 303 | kCGImagePropertyWebPDelayTime 304 | kCGImagePropertyWebPDictionary 305 | kCGImagePropertyWebPFrameInfoArray 306 | kCGImagePropertyWebPLoopCount 307 | kCGImagePropertyWebPUnclampedDelayTime 308 | kCGImagePropertyWidth 309 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TestFile.swift 3 | // SYPictureMetadataTests 4 | // 5 | // Created by Stanislas Chevallier on 24/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SYPictureMetadata 11 | 12 | private class EmptyClass: NSObject {} 13 | 14 | public enum TestFile: String, CaseIterable { 15 | case appleGPS = "TEST_APPLE_GPS.JPG" 16 | case eightBim = "TEST_8BIM.psd" 17 | case canon = "TEST_CANON.cr2" 18 | case ciff = "TEST_CIFF.CRW" 19 | case dng = "TEST_DNG.dng" 20 | case gif = "TEST_GIF.gif" 21 | case iptc = "TEST_IPTC.jpg" 22 | case iptc2 = "TEST_IPTC_2.jpg" 23 | case iptc3 = "TEST_IPTC_3.jpg" 24 | case nikon = "TEST_NIKON.nef" 25 | case pictureStyle = "TEST_PICTURESTYLE.CR2" 26 | case png = "TEST_PNG.png" 27 | case unreadable = "TEST_unreadable.txt" 28 | 29 | public var url: URL { 30 | return Bundle.module.url(forResource: rawValue, withExtension: nil, subdirectory: "TestFile")! 31 | } 32 | 33 | public func read() throws -> Data { 34 | try Data(contentsOf: url) 35 | } 36 | 37 | public func readMetadata() throws -> SYMetadata { 38 | return try SYMetadata(fileURL: url) 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_8BIM.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_8BIM.psd -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_APPLE_GPS.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_APPLE_GPS.JPG -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_CANON.cr2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_CANON.cr2 -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_CIFF.CRW: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_CIFF.CRW -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_DNG.dng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_DNG.dng -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_GIF.gif -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC.jpg -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC_2.jpg -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_IPTC_3.jpg -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_NIKON.nef: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_NIKON.nef -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_PICTURESTYLE.CR2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_PICTURESTYLE.CR2 -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_PNG.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/Sources/SYPictureMetadataTestAssets/TestFile/TEST_PNG.png -------------------------------------------------------------------------------- /Sources/SYPictureMetadataTestAssets/TestFile/TEST_unreadable.txt: -------------------------------------------------------------------------------- 1 | not an image -------------------------------------------------------------------------------- /Tests/SYPictureMetadataTests/Extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dictionary+Extensions.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 13/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public extension Dictionary where Value == Any { 12 | var removingData: Dictionary { 13 | return self.mapValues { (value) -> Value in 14 | if let value = value as? Data { 15 | return "Data: \(value.base64EncodedString())" 16 | } 17 | if let value = value as? Self { 18 | return value.removingData 19 | } 20 | return value 21 | } 22 | } 23 | 24 | var jsonString: String { 25 | do { 26 | var options = JSONSerialization.WritingOptions.prettyPrinted 27 | if #available(iOS 11.0, *) { 28 | options.insert(.sortedKeys) 29 | } 30 | let data = try JSONSerialization.data(withJSONObject: self.removingData, options: options) 31 | return String(data: data, encoding: .utf8) ?? "" 32 | } 33 | catch { 34 | return "Couldn't convert to JSON: \(error)" 35 | } 36 | } 37 | } 38 | 39 | extension Dictionary where Key: CustomStringConvertible { 40 | var allKeyPaths: [String] { 41 | var keyPaths = [String]() 42 | 43 | for key in keys { 44 | let keyString = key.description 45 | keyPaths.append(keyString) 46 | 47 | if let subDic = self[key] as? [Key: Any] { 48 | let subKeyPaths = subDic.allKeyPaths.map { keyString + "." + $0 } 49 | keyPaths.append(contentsOf: subKeyPaths) 50 | } 51 | } 52 | 53 | return keyPaths 54 | } 55 | } 56 | 57 | extension Dictionary where Key == String { 58 | func metadataDifferences(from dictionaryOld: [Key: Value], includeValuesInDiff: Bool) -> [Key: Any] { 59 | let dictionaryNew = self 60 | 61 | let formatAdded = (includeValuesInDiff ? "Added: %@" : "Added" ) 62 | let formatUpdated = (includeValuesInDiff ? "Updated: %@ -> %@" : "Updated") 63 | let formatRemoved = (includeValuesInDiff ? "Removed: %@" : "Removed") 64 | 65 | var allKeys = Set() 66 | allKeys = allKeys.union(dictionaryOld.keys) 67 | allKeys = allKeys.union(dictionaryNew.keys) 68 | 69 | var diffs = [String: Any]() 70 | for key in allKeys { 71 | let valueOld = dictionaryOld[key] 72 | let valueNew = dictionaryNew[key] 73 | 74 | if anyEqual(valueOld, valueNew) { continue } 75 | 76 | let oldIsNilOrDic = valueOld == nil || (valueOld as? Self) != nil 77 | let newIsNilOrDic = valueNew == nil || (valueNew as? Self) != nil 78 | 79 | if (oldIsNilOrDic && newIsNilOrDic) { 80 | let subDicOld = valueOld as? Self ?? [:] 81 | let subDicNew = valueNew as? Self ?? [:] 82 | diffs[key] = subDicNew.metadataDifferences(from: subDicOld, includeValuesInDiff: includeValuesInDiff) 83 | continue 84 | } 85 | 86 | var valueOldString = anyDescription(for: valueOld) 87 | var valueNewString = anyDescription(for: valueNew) 88 | 89 | if let array = valueOld as? [Any] { 90 | valueOldString = array.map { anyDescription(for: $0) }.joined(separator: ", ") 91 | } 92 | if let array = valueNew as? [Any] { 93 | valueNewString = array.map { anyDescription(for: $0) }.joined(separator: ", ") 94 | } 95 | 96 | if (valueOld != nil && valueNew == nil) { 97 | diffs[key] = String(format: formatRemoved, valueOldString) 98 | continue 99 | } 100 | 101 | if (valueOld == nil && valueNew != nil) { 102 | diffs[key] = String(format: formatAdded, valueNewString) 103 | continue 104 | } 105 | 106 | diffs[key] = String(format: formatUpdated, valueOldString, valueNewString) 107 | } 108 | 109 | return diffs 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Tests/SYPictureMetadataTests/Extensions/Foundation+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Foundation+Extensions.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 13/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func anyDescription(for value: Any?) -> String { 12 | if let value = value as? CustomStringConvertible { 13 | return value.description 14 | } 15 | if let value = value { 16 | return "\(value)" 17 | } 18 | return "" 19 | } 20 | 21 | func anyEqual(_ a: Any?, _ b: Any?) -> Bool { 22 | if a == nil && b == nil { return true } 23 | if a == nil && b != nil { return false } 24 | if a != nil && b == nil { return false } 25 | if let a = a as? NSObject, let b = b as? NSObject { return a == b } 26 | print("anyEqual: unhandled types \(anyDescription(for: a)) and \(anyDescription(for: b))") 27 | return false 28 | } 29 | 30 | -------------------------------------------------------------------------------- /Tests/SYPictureMetadataTests/Extensions/SYMetadata+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SYMetadata+Extensions.swift 3 | // SYPictureMetadataExample 4 | // 5 | // Created by Stanislas Chevallier on 13/02/2020. 6 | // Copyright © 2020 Syan.me. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import SYPictureMetadata 11 | 12 | extension SYMetadata { 13 | public var differencesFromOriginalMetadata: Dictionary { 14 | return self.currentDictionary.metadataDifferences(from: self.originalDictionary, includeValuesInDiff: true) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /screenshots/screenshot_preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/screenshots/screenshot_preview.png -------------------------------------------------------------------------------- /screenshots/screenshot_set_analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dvkch/SYPictureMetadata/8cd8293e9792f3564158991bf0970ff91ca8c9ec/screenshots/screenshot_set_analysis.png --------------------------------------------------------------------------------