@lodev09/react-native-exify 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/android/build.gradle +1 -1
- package/android/src/main/AndroidManifest.xml +1 -1
- package/android/src/main/java/com/{exify → lodev09/exify}/ExifyModule.kt +2 -2
- package/android/src/main/java/com/{exify → lodev09/exify}/ExifyPackage.kt +1 -1
- package/android/src/main/java/com/{exify → lodev09/exify}/ExifyTags.kt +1 -1
- package/android/src/main/java/com/{exify → lodev09/exify}/ExifyUtils.kt +1 -1
- package/ios/Exify.swift +20 -23
- package/ios/ExifyUtils.swift +27 -25
- package/package.json +3 -3
- package/ios/Exify.xcodeproj/project.xcworkspace/contents.xcworkspacedata +0 -7
- package/ios/Exify.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist +0 -8
- package/ios/Exify.xcodeproj/project.xcworkspace/xcuserdata/lodev09.xcuserdatad/UserInterfaceState.xcuserstate +0 -0
- package/ios/Exify.xcodeproj/xcuserdata/lodev09.xcuserdatad/xcschemes/xcschememanagement.plist +0 -14
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# React Native Exify
|
|
2
2
|
|
|
3
|
+
[](https://github.com/lodev09/react-native-exify/actions/workflows/ci.yml)
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
3
7
|
A simple library to read and write image Exif metadata in React Native. Inspired from [this thread](https://github.com/mrousavy/react-native-vision-camera/issues/780).
|
|
4
8
|
|
|
5
9
|
## Features
|
|
@@ -42,11 +46,11 @@ const result = await writeAsync(uri, newTags)
|
|
|
42
46
|
console.log(result.tags)
|
|
43
47
|
```
|
|
44
48
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
> **Note**:
|
|
50
|
+
> On IOS, writing exif into an Asset file will duplicate the image. IOS does not allow writing exif into an Asset file directly.
|
|
51
|
+
> If you're getting the photo from a [camera](https://github.com/mrousavy/react-native-vision-camera/), write it into the output file first before saving to the Asset library!
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
See [example](example) for more detailed usage.
|
|
50
54
|
|
|
51
55
|
## Contributing
|
|
52
56
|
Contributions are welcome!
|
package/android/build.gradle
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
package com.exify
|
|
1
|
+
package com.lodev09.exify
|
|
2
2
|
|
|
3
3
|
import android.content.Context
|
|
4
4
|
import android.net.Uri
|
|
5
5
|
import androidx.exifinterface.media.ExifInterface
|
|
6
|
-
import com.exify.ExifyUtils.formatTags
|
|
6
|
+
import com.lodev09.exify.ExifyUtils.formatTags
|
|
7
7
|
import com.facebook.react.bridge.Arguments
|
|
8
8
|
import com.facebook.react.bridge.Promise
|
|
9
9
|
import com.facebook.react.bridge.ReactApplicationContext
|
package/ios/Exify.swift
CHANGED
|
@@ -11,19 +11,18 @@ import PhotosUI
|
|
|
11
11
|
|
|
12
12
|
@objc(Exify)
|
|
13
13
|
class Exify: NSObject {
|
|
14
|
-
|
|
15
|
-
func readExif(uri: String, resolve: @escaping RCTPromiseResolveBlock) -> Void {
|
|
14
|
+
func readExif(uri: String, resolve: @escaping RCTPromiseResolveBlock) {
|
|
16
15
|
guard let url = URL(string: uri) else {
|
|
17
16
|
resolve(nil)
|
|
18
17
|
return
|
|
19
18
|
}
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
readExifTags(from: url) { tags in
|
|
22
21
|
resolve(tags)
|
|
23
22
|
}
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
func readExif(assetId: String, resolve: @escaping RCTPromiseResolveBlock)
|
|
25
|
+
func readExif(assetId: String, resolve: @escaping RCTPromiseResolveBlock) {
|
|
27
26
|
guard let asset = getAssetBy(id: assetId) else {
|
|
28
27
|
resolve(nil)
|
|
29
28
|
return
|
|
@@ -32,62 +31,61 @@ class Exify: NSObject {
|
|
|
32
31
|
let imageOptions = PHContentEditingInputRequestOptions()
|
|
33
32
|
imageOptions.isNetworkAccessAllowed = true
|
|
34
33
|
|
|
35
|
-
asset.requestContentEditingInput(with: imageOptions) { contentInput,
|
|
34
|
+
asset.requestContentEditingInput(with: imageOptions) { contentInput, _ in
|
|
36
35
|
guard let url = contentInput?.fullSizeImageURL else {
|
|
37
36
|
resolve(nil)
|
|
38
37
|
return
|
|
39
38
|
}
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
readExifTags(from: url) { tags in
|
|
42
41
|
resolve(tags)
|
|
43
42
|
}
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
|
-
|
|
47
|
-
func writeExif(assetId: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock)
|
|
45
|
+
|
|
46
|
+
func writeExif(assetId: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
48
47
|
guard let asset = getAssetBy(id: assetId) else {
|
|
49
48
|
reject("Error", "Cannot retrieve asset.", nil)
|
|
50
49
|
return
|
|
51
50
|
}
|
|
52
|
-
|
|
51
|
+
|
|
53
52
|
let imageOptions = PHContentEditingInputRequestOptions()
|
|
54
53
|
imageOptions.isNetworkAccessAllowed = true
|
|
55
|
-
|
|
54
|
+
|
|
56
55
|
asset.requestContentEditingInput(with: imageOptions) { contentInput, _ in
|
|
57
56
|
guard let contentInput, let url = contentInput.fullSizeImageURL else {
|
|
58
57
|
reject("Error", "Unable to read metadata from asset", nil)
|
|
59
58
|
return
|
|
60
59
|
}
|
|
61
|
-
|
|
60
|
+
|
|
62
61
|
updateMetadata(url: url, with: tags) { metadata, data in
|
|
63
62
|
guard let metadata, let data else {
|
|
64
63
|
reject("Error", "Could not update metadata", nil)
|
|
65
64
|
return
|
|
66
65
|
}
|
|
67
|
-
|
|
66
|
+
|
|
68
67
|
do {
|
|
69
|
-
try PHPhotoLibrary.shared().performChangesAndWait{
|
|
68
|
+
try PHPhotoLibrary.shared().performChangesAndWait {
|
|
70
69
|
let request = PHAssetCreationRequest.forAsset()
|
|
71
70
|
request.addResource(with: .photo, data: data, options: nil)
|
|
72
71
|
request.creationDate = Date()
|
|
73
|
-
|
|
72
|
+
|
|
74
73
|
let newAssetId = request.placeholderForCreatedAsset!.localIdentifier
|
|
75
74
|
resolve([
|
|
76
75
|
"uri": "ph://\(newAssetId)",
|
|
77
76
|
"assetId": newAssetId,
|
|
78
77
|
"tags": getExifTags(from: metadata),
|
|
79
78
|
])
|
|
80
|
-
|
|
81
79
|
}
|
|
82
|
-
} catch
|
|
80
|
+
} catch {
|
|
83
81
|
reject("Error", "Could not save to image file", nil)
|
|
84
82
|
print(error)
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
86
|
}
|
|
89
|
-
|
|
90
|
-
func writeExif(uri: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock)
|
|
87
|
+
|
|
88
|
+
func writeExif(uri: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
91
89
|
guard let url = URL(string: uri) else {
|
|
92
90
|
reject("Error", "Invalid URL", nil)
|
|
93
91
|
return
|
|
@@ -98,7 +96,7 @@ class Exify: NSObject {
|
|
|
98
96
|
reject("Error", "Could not update metadata", nil)
|
|
99
97
|
return
|
|
100
98
|
}
|
|
101
|
-
|
|
99
|
+
|
|
102
100
|
do {
|
|
103
101
|
// Write to the current file
|
|
104
102
|
try data.write(to: url, options: .atomic)
|
|
@@ -107,8 +105,7 @@ class Exify: NSObject {
|
|
|
107
105
|
"uri": uri,
|
|
108
106
|
"tags": getExifTags(from: metadata),
|
|
109
107
|
])
|
|
110
|
-
|
|
111
|
-
} catch let error {
|
|
108
|
+
} catch {
|
|
112
109
|
reject("Error", "Could not save to image file", nil)
|
|
113
110
|
print(error)
|
|
114
111
|
}
|
|
@@ -116,7 +113,7 @@ class Exify: NSObject {
|
|
|
116
113
|
}
|
|
117
114
|
|
|
118
115
|
@objc(readAsync:withResolver:withRejecter:)
|
|
119
|
-
func readAsync(uri: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock)
|
|
116
|
+
func readAsync(uri: String, resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) {
|
|
120
117
|
if uri.starts(with: "ph://") {
|
|
121
118
|
let assetId = String(uri[uri.index(uri.startIndex, offsetBy: 5)...])
|
|
122
119
|
readExif(assetId: assetId, resolve: resolve)
|
|
@@ -126,7 +123,7 @@ class Exify: NSObject {
|
|
|
126
123
|
}
|
|
127
124
|
|
|
128
125
|
@objc(writeAsync:withExif:withResolver:withRejecter:)
|
|
129
|
-
func writeAsync(uri: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock)
|
|
126
|
+
func writeAsync(uri: String, tags: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
|
130
127
|
if uri.starts(with: "ph://") {
|
|
131
128
|
let assetId = String(uri[uri.index(uri.startIndex, offsetBy: 5)...])
|
|
132
129
|
writeExif(assetId: assetId, tags: tags, resolve: resolve, reject: reject)
|
package/ios/ExifyUtils.swift
CHANGED
|
@@ -27,7 +27,7 @@ func getAssetsBy(assetIds: [String]) -> PHFetchResult<PHAsset> {
|
|
|
27
27
|
return PHAsset.fetchAssets(withLocalIdentifiers: assetIds, options: options)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
func addTagEntries(from dictionary: CFString, metadata: NSDictionary, to tags: NSMutableDictionary)
|
|
30
|
+
func addTagEntries(from dictionary: CFString, metadata: NSDictionary, to tags: NSMutableDictionary) {
|
|
31
31
|
if let entries = metadata[dictionary] as? [String: Any] {
|
|
32
32
|
tags.addEntries(from: entries)
|
|
33
33
|
}
|
|
@@ -35,54 +35,56 @@ func addTagEntries(from dictionary: CFString, metadata: NSDictionary, to tags: N
|
|
|
35
35
|
|
|
36
36
|
func getExifTags(from metadata: NSDictionary) -> [String: Any] {
|
|
37
37
|
let tags: NSMutableDictionary = [:]
|
|
38
|
-
|
|
38
|
+
|
|
39
39
|
// Add root non-dictionary properties
|
|
40
40
|
for (key, value) in metadata {
|
|
41
41
|
if value as? [String: Any] == nil {
|
|
42
42
|
tags[key] = value
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
// Append all {Exif} properties
|
|
47
47
|
addTagEntries(from: kCGImagePropertyExifDictionary, metadata: metadata, to: tags)
|
|
48
|
-
|
|
48
|
+
|
|
49
49
|
// Prefix {GPS} dictionary with "GPS"
|
|
50
50
|
if let gps = metadata[kCGImagePropertyGPSDictionary as String] as? [String: Any] {
|
|
51
51
|
for (key, value) in gps {
|
|
52
52
|
tags["GPS" + key] = value
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
// Include tags from formats
|
|
57
57
|
addTagEntries(from: kCGImagePropertyTIFFDictionary, metadata: metadata, to: tags)
|
|
58
58
|
addTagEntries(from: kCGImagePropertyPNGDictionary, metadata: metadata, to: tags)
|
|
59
59
|
addTagEntries(from: kCGImagePropertyHEICSDictionary, metadata: metadata, to: tags)
|
|
60
|
-
|
|
61
|
-
return tags as
|
|
60
|
+
|
|
61
|
+
return tags as? [String: Any] ?? [:]
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
func readExifTags(from url: URL?, completionHandler: ([String: Any]?) -> Void)
|
|
64
|
+
func readExifTags(from url: URL?, completionHandler: ([String: Any]?) -> Void) {
|
|
65
65
|
guard let url, let sourceImage = CGImageSourceCreateWithURL(url as CFURL, nil),
|
|
66
66
|
let metadataDict = CGImageSourceCopyPropertiesAtIndex(sourceImage, 0, nil) else {
|
|
67
|
+
completionHandler(nil)
|
|
67
68
|
return
|
|
68
69
|
}
|
|
69
|
-
|
|
70
|
+
|
|
70
71
|
let tags = getExifTags(from: metadataDict)
|
|
71
72
|
completionHandler(tags)
|
|
72
73
|
}
|
|
73
74
|
|
|
74
|
-
func updateMetadata(url: URL, with tags: [String: Any], completionHanlder: (NSDictionary?, Data?) -> Void)
|
|
75
|
+
func updateMetadata(url: URL, with tags: [String: Any], completionHanlder: (NSDictionary?, Data?) -> Void) {
|
|
75
76
|
guard let cgImageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else {
|
|
77
|
+
completionHanlder(nil, nil)
|
|
76
78
|
return
|
|
77
79
|
}
|
|
78
|
-
|
|
80
|
+
|
|
79
81
|
let metadataDict = CGImageSourceCopyPropertiesAtIndex(cgImageSource, 0, nil) ?? [:] as CFDictionary
|
|
80
82
|
let metadata = NSMutableDictionary(dictionary: metadataDict)
|
|
81
83
|
|
|
82
84
|
// Append additional Exif data
|
|
83
85
|
let exifDict = metadata[kCGImagePropertyExifDictionary as String] as? NSMutableDictionary
|
|
84
|
-
exifDict
|
|
85
|
-
|
|
86
|
+
exifDict?.addEntries(from: tags)
|
|
87
|
+
|
|
86
88
|
// Handle GPS Tags
|
|
87
89
|
var gpsDict = [String: Any]()
|
|
88
90
|
|
|
@@ -100,15 +102,14 @@ func updateMetadata(url: URL, with tags: [String: Any], completionHanlder: (NSDi
|
|
|
100
102
|
gpsDict[kCGImagePropertyGPSAltitude as String] = abs(altitude)
|
|
101
103
|
gpsDict[kCGImagePropertyGPSAltitudeRef as String] = altitude >= 0 ? 0 : 1
|
|
102
104
|
}
|
|
103
|
-
|
|
105
|
+
|
|
104
106
|
if let gpsDate = tags["GPSDateStamp"] as? String {
|
|
105
107
|
gpsDict[kCGImagePropertyGPSDateStamp as String] = gpsDate
|
|
106
108
|
}
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
if let gpsTime = tags["GPSTimeStamp"] as? String {
|
|
109
111
|
gpsDict[kCGImagePropertyGPSTimeStamp as String] = gpsTime
|
|
110
112
|
}
|
|
111
|
-
|
|
112
113
|
|
|
113
114
|
if metadata[kCGImagePropertyGPSDictionary as String] == nil {
|
|
114
115
|
metadata[kCGImagePropertyGPSDictionary as String] = gpsDict
|
|
@@ -117,19 +118,20 @@ func updateMetadata(url: URL, with tags: [String: Any], completionHanlder: (NSDi
|
|
|
117
118
|
metadataGpsDict.addEntries(from: gpsDict)
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
metadata.setObject(NSNumber(value: 1), forKey: kCGImageDestinationLossyCompressionQuality as NSString)
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
let destinationData = NSMutableData()
|
|
124
125
|
|
|
125
|
-
guard let
|
|
126
|
-
|
|
126
|
+
guard let uiImage = UIImage(contentsOfFile: url.path),
|
|
127
|
+
let sourceType = CGImageSourceGetType(cgImageSource),
|
|
128
|
+
let destination = CGImageDestinationCreateWithData(destinationData, sourceType, 1, nil) else {
|
|
129
|
+
completionHanlder(nil, nil)
|
|
127
130
|
return
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
completionHanlder(metadata, destinationData as Data)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
133
|
+
CGImageDestinationAddImage(destination, uiImage.cgImage!, metadata)
|
|
134
|
+
CGImageDestinationFinalize(destination)
|
|
135
135
|
|
|
136
|
+
completionHanlder(metadata, destinationData as Data)
|
|
137
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lodev09/react-native-exify",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "Read and write exif data into an image
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "Read and write exif data into an image 🏷️",
|
|
5
5
|
"main": "lib/commonjs/index",
|
|
6
6
|
"module": "lib/module/index",
|
|
7
7
|
"types": "lib/typescript/src/index.d.ts",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit",
|
|
32
32
|
"lint": "eslint --fix \"**/*.{js,ts,tsx}\"",
|
|
33
33
|
"format": "prettier --write \"**/*.{js,ts,tsx}\"",
|
|
34
|
-
"tidy": "yarn typecheck && yarn lint && yarn format",
|
|
34
|
+
"tidy": "yarn typecheck && yarn lint && yarn format && scripts/swiftlint.sh && scripts/swiftformat.sh",
|
|
35
35
|
"clean": "del-cli android/build lib && yarn workspace exify-example clean",
|
|
36
36
|
"prepare": "bob build",
|
|
37
37
|
"release": "release-it"
|
|
Binary file
|
package/ios/Exify.xcodeproj/xcuserdata/lodev09.xcuserdatad/xcschemes/xcschememanagement.plist
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
-
<plist version="1.0">
|
|
4
|
-
<dict>
|
|
5
|
-
<key>SchemeUserState</key>
|
|
6
|
-
<dict>
|
|
7
|
-
<key>Exify.xcscheme_^#shared#^_</key>
|
|
8
|
-
<dict>
|
|
9
|
-
<key>orderHint</key>
|
|
10
|
-
<integer>0</integer>
|
|
11
|
-
</dict>
|
|
12
|
-
</dict>
|
|
13
|
-
</dict>
|
|
14
|
-
</plist>
|