@lodev09/react-native-exify 1.0.2 → 1.0.3
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/android/build.gradle +1 -1
- package/android/src/main/java/com/lodev09/exify/ExifFallback.kt +245 -0
- package/android/src/main/java/com/lodev09/exify/ExifyModule.kt +68 -20
- package/android/src/main/java/com/lodev09/exify/ExifyTags.kt +20 -1
- package/android/src/main/java/com/lodev09/exify/ExifyUtils.kt +13 -3
- package/ios/Exify.mm +11 -0
- package/lib/typescript/src/types.d.ts +2 -1
- package/lib/typescript/src/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/types.ts +2 -1
package/android/build.gradle
CHANGED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
package com.lodev09.exify
|
|
2
|
+
|
|
3
|
+
import java.io.InputStream
|
|
4
|
+
import java.nio.ByteBuffer
|
|
5
|
+
import java.nio.ByteOrder
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* IFD0 tags that ExifInterface may miss when an image editor
|
|
9
|
+
* (e.g. ON1 Photo RAW) places them inside the ExifIFD instead of IFD0.
|
|
10
|
+
*
|
|
11
|
+
* Maps EXIF tag number → ExifInterface tag name.
|
|
12
|
+
*/
|
|
13
|
+
private val FALLBACK_TAGS =
|
|
14
|
+
mapOf(
|
|
15
|
+
0x010E to "ImageDescription",
|
|
16
|
+
0x010F to "Make",
|
|
17
|
+
0x0110 to "Model",
|
|
18
|
+
0x0112 to "Orientation",
|
|
19
|
+
0x011A to "XResolution",
|
|
20
|
+
0x011B to "YResolution",
|
|
21
|
+
0x0128 to "ResolutionUnit",
|
|
22
|
+
0x0131 to "Software",
|
|
23
|
+
0x013B to "Artist",
|
|
24
|
+
0x8298 to "Copyright",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
private const val IFD_FORMAT_SHORT = 3
|
|
28
|
+
private const val IFD_FORMAT_LONG = 4
|
|
29
|
+
private const val IFD_FORMAT_RATIONAL = 5
|
|
30
|
+
private const val IFD_FORMAT_STRING = 2
|
|
31
|
+
private const val IFD_FORMAT_UNDEFINED = 7
|
|
32
|
+
private const val EXIF_IFD_POINTER_TAG = 0x8769
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Scans raw JPEG bytes for IFD0 tags that may have been placed in
|
|
36
|
+
* the ExifIFD. Returns a map of tag name → value for any tags found.
|
|
37
|
+
*/
|
|
38
|
+
fun readFallbackTags(
|
|
39
|
+
inputStream: InputStream,
|
|
40
|
+
missingTags: Set<String>,
|
|
41
|
+
): Map<String, Any> {
|
|
42
|
+
if (missingTags.isEmpty()) return emptyMap()
|
|
43
|
+
|
|
44
|
+
val neededTagNumbers = FALLBACK_TAGS.filterValues { it in missingTags }.keys
|
|
45
|
+
if (neededTagNumbers.isEmpty()) return emptyMap()
|
|
46
|
+
|
|
47
|
+
val bytes = readExifSegment(inputStream) ?: return emptyMap()
|
|
48
|
+
val result = mutableMapOf<String, Any>()
|
|
49
|
+
|
|
50
|
+
val app1 = findApp1Exif(bytes) ?: return emptyMap()
|
|
51
|
+
val tiffOffset = app1.tiffOffset
|
|
52
|
+
val buf = ByteBuffer.wrap(bytes)
|
|
53
|
+
buf.order(app1.byteOrder)
|
|
54
|
+
|
|
55
|
+
// Read IFD0 to find ExifIFD offset (offsets are relative to TIFF start)
|
|
56
|
+
val ifd0Offset = buf.getInt(tiffOffset + 4)
|
|
57
|
+
val exifIfdOffset = findExifIfdOffset(buf, tiffOffset, ifd0Offset) ?: return emptyMap()
|
|
58
|
+
|
|
59
|
+
// Scan ExifIFD entries for our missing tags
|
|
60
|
+
scanIfd(buf, tiffOffset, exifIfdOffset, neededTagNumbers, result)
|
|
61
|
+
|
|
62
|
+
return result
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Reads only the JPEG header segments up to and including the EXIF APP1,
|
|
67
|
+
* avoiding reading the full image file into memory.
|
|
68
|
+
*/
|
|
69
|
+
private fun readExifSegment(inputStream: InputStream): ByteArray? {
|
|
70
|
+
val dis = java.io.DataInputStream(inputStream)
|
|
71
|
+
val header = ByteArray(2)
|
|
72
|
+
dis.readFully(header)
|
|
73
|
+
if (header[0] != 0xFF.toByte() || header[1] != 0xD8.toByte()) return null
|
|
74
|
+
|
|
75
|
+
val out = java.io.ByteArrayOutputStream(65536)
|
|
76
|
+
out.write(header)
|
|
77
|
+
|
|
78
|
+
val segHeader = ByteArray(4)
|
|
79
|
+
while (true) {
|
|
80
|
+
try {
|
|
81
|
+
dis.readFully(segHeader)
|
|
82
|
+
} catch (_: java.io.EOFException) {
|
|
83
|
+
break
|
|
84
|
+
}
|
|
85
|
+
val marker = segHeader[1].toInt() and 0xFF
|
|
86
|
+
val segLen = ((segHeader[2].toInt() and 0xFF) shl 8) or (segHeader[3].toInt() and 0xFF)
|
|
87
|
+
|
|
88
|
+
if (segHeader[0] != 0xFF.toByte() || segLen < 2) break
|
|
89
|
+
|
|
90
|
+
out.write(segHeader)
|
|
91
|
+
val segData = ByteArray(segLen - 2)
|
|
92
|
+
dis.readFully(segData)
|
|
93
|
+
out.write(segData)
|
|
94
|
+
|
|
95
|
+
// Found EXIF APP1 — we have enough
|
|
96
|
+
if (marker == 0xE1 && segData.size >= 6 &&
|
|
97
|
+
segData[0] == 0x45.toByte() &&
|
|
98
|
+
segData[1] == 0x78.toByte() &&
|
|
99
|
+
segData[2] == 0x69.toByte() &&
|
|
100
|
+
segData[3] == 0x66.toByte()
|
|
101
|
+
) {
|
|
102
|
+
break
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Stop if we hit SOS or image data
|
|
106
|
+
if (marker == 0xDA) break
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return out.toByteArray()
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
private data class App1Info(
|
|
113
|
+
val tiffOffset: Int,
|
|
114
|
+
val byteOrder: ByteOrder,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
private fun findApp1Exif(bytes: ByteArray): App1Info? {
|
|
118
|
+
if (bytes.size < 4 || bytes[0] != 0xFF.toByte() || bytes[1] != 0xD8.toByte()) return null
|
|
119
|
+
|
|
120
|
+
var pos = 2
|
|
121
|
+
while (pos + 4 < bytes.size) {
|
|
122
|
+
if (bytes[pos] != 0xFF.toByte()) return null
|
|
123
|
+
val marker = bytes[pos + 1].toInt() and 0xFF
|
|
124
|
+
|
|
125
|
+
// Read segment length (big-endian)
|
|
126
|
+
val segLen = ((bytes[pos + 2].toInt() and 0xFF) shl 8) or (bytes[pos + 3].toInt() and 0xFF)
|
|
127
|
+
|
|
128
|
+
if (marker == 0xE1 && segLen >= 8) {
|
|
129
|
+
// Check for "Exif\0\0"
|
|
130
|
+
if (pos + 10 < bytes.size &&
|
|
131
|
+
bytes[pos + 4] == 0x45.toByte() && // E
|
|
132
|
+
bytes[pos + 5] == 0x78.toByte() && // x
|
|
133
|
+
bytes[pos + 6] == 0x69.toByte() && // i
|
|
134
|
+
bytes[pos + 7] == 0x66.toByte() && // f
|
|
135
|
+
bytes[pos + 8] == 0x00.toByte() &&
|
|
136
|
+
bytes[pos + 9] == 0x00.toByte()
|
|
137
|
+
) {
|
|
138
|
+
val tiffOffset = pos + 10
|
|
139
|
+
val order =
|
|
140
|
+
if (bytes[tiffOffset] == 0x49.toByte() && bytes[tiffOffset + 1] == 0x49.toByte()) {
|
|
141
|
+
ByteOrder.LITTLE_ENDIAN
|
|
142
|
+
} else {
|
|
143
|
+
ByteOrder.BIG_ENDIAN
|
|
144
|
+
}
|
|
145
|
+
return App1Info(tiffOffset, order)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
pos += 2 + segLen
|
|
150
|
+
}
|
|
151
|
+
return null
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private fun findExifIfdOffset(
|
|
155
|
+
buf: ByteBuffer,
|
|
156
|
+
tiffOffset: Int,
|
|
157
|
+
ifdOffset: Int,
|
|
158
|
+
): Int? {
|
|
159
|
+
if (ifdOffset < 0 || tiffOffset + ifdOffset + 2 > buf.limit()) return null
|
|
160
|
+
|
|
161
|
+
val count = buf.getShort(tiffOffset + ifdOffset).toInt() and 0xFFFF
|
|
162
|
+
for (i in 0 until count) {
|
|
163
|
+
val entryOffset = tiffOffset + ifdOffset + 2 + i * 12
|
|
164
|
+
if (entryOffset + 12 > buf.limit()) return null
|
|
165
|
+
|
|
166
|
+
val tagNumber = buf.getShort(entryOffset).toInt() and 0xFFFF
|
|
167
|
+
if (tagNumber == EXIF_IFD_POINTER_TAG) {
|
|
168
|
+
return buf.getInt(entryOffset + 8)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return null
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private fun scanIfd(
|
|
175
|
+
buf: ByteBuffer,
|
|
176
|
+
tiffOffset: Int,
|
|
177
|
+
ifdOffset: Int,
|
|
178
|
+
neededTagNumbers: Set<Int>,
|
|
179
|
+
result: MutableMap<String, Any>,
|
|
180
|
+
) {
|
|
181
|
+
val absOffset = tiffOffset + ifdOffset
|
|
182
|
+
if (absOffset + 2 > buf.limit()) return
|
|
183
|
+
|
|
184
|
+
val count = buf.getShort(absOffset).toInt() and 0xFFFF
|
|
185
|
+
for (i in 0 until count) {
|
|
186
|
+
val entryOffset = absOffset + 2 + i * 12
|
|
187
|
+
if (entryOffset + 12 > buf.limit()) return
|
|
188
|
+
|
|
189
|
+
val tagNumber = buf.getShort(entryOffset).toInt() and 0xFFFF
|
|
190
|
+
if (tagNumber !in neededTagNumbers) continue
|
|
191
|
+
|
|
192
|
+
val format = buf.getShort(entryOffset + 2).toInt() and 0xFFFF
|
|
193
|
+
val componentCount = buf.getInt(entryOffset + 4)
|
|
194
|
+
if (componentCount <= 0) continue
|
|
195
|
+
|
|
196
|
+
val tagName = FALLBACK_TAGS[tagNumber] ?: continue
|
|
197
|
+
|
|
198
|
+
when (format) {
|
|
199
|
+
IFD_FORMAT_SHORT -> {
|
|
200
|
+
if (componentCount != 1) continue
|
|
201
|
+
val value = buf.getShort(entryOffset + 8).toInt() and 0xFFFF
|
|
202
|
+
if (value == 0) continue
|
|
203
|
+
result[tagName] = value
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
IFD_FORMAT_LONG -> {
|
|
207
|
+
if (componentCount != 1) continue
|
|
208
|
+
val value = buf.getInt(entryOffset + 8).toLong() and 0xFFFFFFFFL
|
|
209
|
+
if (value == 0L) continue
|
|
210
|
+
result[tagName] = value.toInt()
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
IFD_FORMAT_RATIONAL -> {
|
|
214
|
+
if (componentCount != 1) continue
|
|
215
|
+
val dataOffset = tiffOffset + buf.getInt(entryOffset + 8)
|
|
216
|
+
if (dataOffset < 0 || dataOffset + 8 > buf.limit()) continue
|
|
217
|
+
val numerator = buf.getInt(dataOffset).toLong() and 0xFFFFFFFFL
|
|
218
|
+
val denominator = buf.getInt(dataOffset + 4).toLong() and 0xFFFFFFFFL
|
|
219
|
+
if (denominator == 0L) continue
|
|
220
|
+
result[tagName] = numerator.toDouble() / denominator.toDouble()
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
IFD_FORMAT_STRING, IFD_FORMAT_UNDEFINED -> {
|
|
224
|
+
if (componentCount > 1024) continue
|
|
225
|
+
val dataOffset =
|
|
226
|
+
if (componentCount <= 4) {
|
|
227
|
+
entryOffset + 8
|
|
228
|
+
} else {
|
|
229
|
+
tiffOffset + buf.getInt(entryOffset + 8)
|
|
230
|
+
}
|
|
231
|
+
if (dataOffset < 0 || dataOffset + componentCount > buf.limit()) continue
|
|
232
|
+
|
|
233
|
+
val strBytes = ByteArray(componentCount)
|
|
234
|
+
buf.position(dataOffset)
|
|
235
|
+
buf.get(strBytes)
|
|
236
|
+
|
|
237
|
+
var len = strBytes.size
|
|
238
|
+
while (len > 0 && strBytes[len - 1] == 0.toByte()) len--
|
|
239
|
+
if (len == 0) continue
|
|
240
|
+
|
|
241
|
+
result[tagName] = String(strBytes, 0, len, Charsets.UTF_8).trim()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -16,6 +16,7 @@ import com.facebook.react.modules.core.PermissionAwareActivity
|
|
|
16
16
|
import com.facebook.react.modules.core.PermissionListener
|
|
17
17
|
import com.facebook.react.util.RNLog
|
|
18
18
|
import com.lodev09.exify.ExifyUtils.formatTags
|
|
19
|
+
import java.io.File
|
|
19
20
|
import java.io.IOException
|
|
20
21
|
|
|
21
22
|
private const val ERROR_TAG = "E_EXIFY_ERROR"
|
|
@@ -72,35 +73,77 @@ class ExifyModule(
|
|
|
72
73
|
promise: Promise,
|
|
73
74
|
) {
|
|
74
75
|
try {
|
|
75
|
-
val
|
|
76
|
-
if (scheme == "
|
|
77
|
-
|
|
78
|
-
} else if (scheme == "content" && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
79
|
-
try {
|
|
80
|
-
context.contentResolver.openInputStream(MediaStore.setRequireOriginal(photoUri))
|
|
81
|
-
} catch (e: SecurityException) {
|
|
82
|
-
context.contentResolver.openInputStream(photoUri)
|
|
83
|
-
}
|
|
76
|
+
val exif =
|
|
77
|
+
if (scheme == "file") {
|
|
78
|
+
ExifInterface(photoUri.path!!)
|
|
84
79
|
} else {
|
|
85
|
-
|
|
80
|
+
val inputStream =
|
|
81
|
+
if (scheme == "content" && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
82
|
+
try {
|
|
83
|
+
context.contentResolver.openInputStream(MediaStore.setRequireOriginal(photoUri))
|
|
84
|
+
} catch (_: SecurityException) {
|
|
85
|
+
context.contentResolver.openInputStream(photoUri)
|
|
86
|
+
}
|
|
87
|
+
} else if (scheme == "http" || scheme == "https") {
|
|
88
|
+
java.net.URL(uri).openStream()
|
|
89
|
+
} else {
|
|
90
|
+
context.contentResolver.openInputStream(photoUri)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (inputStream == null) {
|
|
94
|
+
RNLog.w(context, "Exify: Could not open URI: $uri")
|
|
95
|
+
promise.reject(ERROR_TAG, "Could not open URI: $uri")
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
inputStream.use { ExifInterface(it) }
|
|
86
100
|
}
|
|
87
101
|
|
|
88
|
-
|
|
89
|
-
RNLog.w(context, "Exify: Could not open URI: $uri")
|
|
90
|
-
promise.reject(ERROR_TAG, "Could not open URI: $uri")
|
|
91
|
-
return
|
|
92
|
-
}
|
|
102
|
+
val tags = formatTags(exif)
|
|
93
103
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
104
|
+
// ExifInterface ignores IFD0 tags placed in ExifIFD (non-standard but common
|
|
105
|
+
// with some image editors). Fall back to raw EXIF parsing for missing tags.
|
|
106
|
+
val missingTags =
|
|
107
|
+
IFD0_FALLBACK_TAGS
|
|
108
|
+
.filter {
|
|
109
|
+
if (!tags.hasKey(it)) return@filter true
|
|
110
|
+
val type = tags.getType(it)
|
|
111
|
+
(type == ReadableType.Number && tags.getDouble(it) == 0.0)
|
|
112
|
+
}.toSet()
|
|
113
|
+
if (missingTags.isNotEmpty()) {
|
|
114
|
+
val fallback =
|
|
115
|
+
try {
|
|
116
|
+
openInputStream(uri, photoUri, scheme)?.use { readFallbackTags(it, missingTags) }
|
|
117
|
+
} catch (_: Exception) {
|
|
118
|
+
null
|
|
119
|
+
}
|
|
120
|
+
fallback?.forEach { (tag, value) ->
|
|
121
|
+
when (value) {
|
|
122
|
+
is String -> tags.putString(tag, value)
|
|
123
|
+
is Int -> tags.putInt(tag, value)
|
|
124
|
+
is Double -> tags.putDouble(tag, value)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
97
127
|
}
|
|
128
|
+
|
|
129
|
+
promise.resolve(tags)
|
|
98
130
|
} catch (e: Exception) {
|
|
99
131
|
RNLog.w(context, "Exify: ${e.message}")
|
|
100
132
|
promise.reject(ERROR_TAG, e.message, e)
|
|
101
133
|
}
|
|
102
134
|
}
|
|
103
135
|
|
|
136
|
+
private fun openInputStream(
|
|
137
|
+
uri: String,
|
|
138
|
+
photoUri: Uri,
|
|
139
|
+
scheme: String,
|
|
140
|
+
): java.io.InputStream? =
|
|
141
|
+
when (scheme) {
|
|
142
|
+
"file" -> File(photoUri.path!!).inputStream()
|
|
143
|
+
"content" -> context.contentResolver.openInputStream(photoUri)
|
|
144
|
+
else -> java.net.URL(uri).openStream()
|
|
145
|
+
}
|
|
146
|
+
|
|
104
147
|
@Throws(IOException::class)
|
|
105
148
|
override fun write(
|
|
106
149
|
uri: String,
|
|
@@ -141,7 +184,10 @@ class ExifyModule(
|
|
|
141
184
|
exif.setAttribute(tag, value.toBigDecimal().toPlainString())
|
|
142
185
|
}
|
|
143
186
|
}
|
|
144
|
-
|
|
187
|
+
|
|
188
|
+
else -> {
|
|
189
|
+
exif.setAttribute(tag, tags.getDouble(tag).toInt().toString())
|
|
190
|
+
}
|
|
145
191
|
}
|
|
146
192
|
}
|
|
147
193
|
|
|
@@ -150,7 +196,9 @@ class ExifyModule(
|
|
|
150
196
|
}
|
|
151
197
|
|
|
152
198
|
ReadableType.Array -> {
|
|
153
|
-
|
|
199
|
+
val arr = tags.getArray(tag)!!
|
|
200
|
+
val values = (0 until arr.size()).joinToString(", ") { arr.getInt(it).toString() }
|
|
201
|
+
exif.setAttribute(tag, values)
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
else -> {
|
|
@@ -2,6 +2,24 @@ package com.lodev09.exify
|
|
|
2
2
|
|
|
3
3
|
import androidx.exifinterface.media.ExifInterface
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* IFD0 tags that ExifInterface may fail to read when they are
|
|
7
|
+
* incorrectly placed inside the ExifIFD by some image editors.
|
|
8
|
+
*/
|
|
9
|
+
val IFD0_FALLBACK_TAGS =
|
|
10
|
+
setOf(
|
|
11
|
+
ExifInterface.TAG_MAKE,
|
|
12
|
+
ExifInterface.TAG_MODEL,
|
|
13
|
+
ExifInterface.TAG_ARTIST,
|
|
14
|
+
ExifInterface.TAG_COPYRIGHT,
|
|
15
|
+
ExifInterface.TAG_IMAGE_DESCRIPTION,
|
|
16
|
+
ExifInterface.TAG_SOFTWARE,
|
|
17
|
+
ExifInterface.TAG_ORIENTATION,
|
|
18
|
+
ExifInterface.TAG_X_RESOLUTION,
|
|
19
|
+
ExifInterface.TAG_Y_RESOLUTION,
|
|
20
|
+
ExifInterface.TAG_RESOLUTION_UNIT,
|
|
21
|
+
)
|
|
22
|
+
|
|
5
23
|
/**
|
|
6
24
|
* Supported Exif Tags
|
|
7
25
|
* Note: Latitude, Longitude and Altitude tags are updated separately
|
|
@@ -64,13 +82,14 @@ val EXIFY_TAGS =
|
|
|
64
82
|
arrayOf("double", ExifInterface.TAG_FOCAL_LENGTH),
|
|
65
83
|
arrayOf("string", ExifInterface.TAG_LENS_MAKE),
|
|
66
84
|
arrayOf("string", ExifInterface.TAG_LENS_MODEL),
|
|
85
|
+
arrayOf("string", ExifInterface.TAG_BODY_SERIAL_NUMBER),
|
|
67
86
|
arrayOf("array", ExifInterface.TAG_LENS_SPECIFICATION),
|
|
68
87
|
arrayOf("int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM),
|
|
69
88
|
arrayOf("int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT),
|
|
70
89
|
arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION),
|
|
71
90
|
arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION),
|
|
72
91
|
arrayOf("int", ExifInterface.TAG_GAIN_CONTROL),
|
|
73
|
-
arrayOf("
|
|
92
|
+
arrayOf("int_array", ExifInterface.TAG_ISO_SPEED_RATINGS),
|
|
74
93
|
arrayOf("string", ExifInterface.TAG_IMAGE_UNIQUE_ID),
|
|
75
94
|
arrayOf("int", ExifInterface.TAG_LIGHT_SOURCE),
|
|
76
95
|
arrayOf("string", ExifInterface.TAG_MAKER_NOTE),
|
|
@@ -2,11 +2,11 @@ package com.lodev09.exify
|
|
|
2
2
|
|
|
3
3
|
import androidx.exifinterface.media.ExifInterface
|
|
4
4
|
import com.facebook.react.bridge.Arguments
|
|
5
|
-
import com.facebook.react.bridge.
|
|
5
|
+
import com.facebook.react.bridge.WritableMap
|
|
6
6
|
|
|
7
7
|
object ExifyUtils {
|
|
8
8
|
@JvmStatic
|
|
9
|
-
fun formatTags(exif: ExifInterface):
|
|
9
|
+
fun formatTags(exif: ExifInterface): WritableMap {
|
|
10
10
|
val tags = Arguments.createMap()
|
|
11
11
|
|
|
12
12
|
for ((type, tag) in EXIFY_TAGS) {
|
|
@@ -18,13 +18,23 @@ object ExifyUtils {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
"int" -> {
|
|
21
|
-
|
|
21
|
+
val intVal = exif.getAttributeInt(tag, 0)
|
|
22
|
+
if (tag == ExifInterface.TAG_ORIENTATION && intVal == 0) continue
|
|
23
|
+
tags.putInt(tag, intVal)
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
"double" -> {
|
|
25
27
|
tags.putDouble(tag, exif.getAttributeDouble(tag, 0.0))
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
"int_array" -> {
|
|
31
|
+
val array = Arguments.createArray()
|
|
32
|
+
attribute.split(", ").forEach { part ->
|
|
33
|
+
part.trim().toIntOrNull()?.let { array.pushInt(it) }
|
|
34
|
+
}
|
|
35
|
+
if (array.size() > 0) tags.putArray(tag, array)
|
|
36
|
+
}
|
|
37
|
+
|
|
28
38
|
"array" -> {
|
|
29
39
|
val array = Arguments.createArray()
|
|
30
40
|
exif.getAttributeRange(tag)?.forEach { value ->
|
package/ios/Exify.mm
CHANGED
|
@@ -43,6 +43,17 @@ static NSDictionary *getExifTags(NSDictionary *metadata) {
|
|
|
43
43
|
if (![value isKindOfClass:[NSDictionary class]] &&
|
|
44
44
|
![key isEqualToString:compressionKey]) {
|
|
45
45
|
tags[key] = value;
|
|
46
|
+
|
|
47
|
+
// Also emit standard EXIF names for CGImageSource-specific keys
|
|
48
|
+
if ([key isEqualToString:@"PixelWidth"]) {
|
|
49
|
+
tags[@"PixelXDimension"] = value;
|
|
50
|
+
} else if ([key isEqualToString:@"PixelHeight"]) {
|
|
51
|
+
tags[@"PixelYDimension"] = value;
|
|
52
|
+
} else if ([key isEqualToString:@"DPIWidth"]) {
|
|
53
|
+
tags[@"XResolution"] = value;
|
|
54
|
+
} else if ([key isEqualToString:@"DPIHeight"]) {
|
|
55
|
+
tags[@"YResolution"] = value;
|
|
56
|
+
}
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
59
|
|
|
@@ -63,13 +63,14 @@ export interface ExifTags {
|
|
|
63
63
|
LightSource?: number;
|
|
64
64
|
UserComment?: string;
|
|
65
65
|
GainControl?: number;
|
|
66
|
-
ISOSpeedRatings?:
|
|
66
|
+
ISOSpeedRatings?: number[];
|
|
67
67
|
FocalPlaneResolutionUnit?: number;
|
|
68
68
|
FocalPlaneXResolution?: number;
|
|
69
69
|
YCbCrCoefficients?: number;
|
|
70
70
|
FocalLengthIn35mmFilm?: number;
|
|
71
71
|
LensMake?: string;
|
|
72
72
|
LensModel?: string;
|
|
73
|
+
BodySerialNumber?: string;
|
|
73
74
|
LensSpecification?: number[];
|
|
74
75
|
ISO?: number;
|
|
75
76
|
FlashpixVersion?: number[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,QAAQ;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/types.ts
CHANGED
|
@@ -63,13 +63,14 @@ export interface ExifTags {
|
|
|
63
63
|
LightSource?: number;
|
|
64
64
|
UserComment?: string;
|
|
65
65
|
GainControl?: number;
|
|
66
|
-
ISOSpeedRatings?:
|
|
66
|
+
ISOSpeedRatings?: number[];
|
|
67
67
|
FocalPlaneResolutionUnit?: number;
|
|
68
68
|
FocalPlaneXResolution?: number;
|
|
69
69
|
YCbCrCoefficients?: number;
|
|
70
70
|
FocalLengthIn35mmFilm?: number;
|
|
71
71
|
LensMake?: string;
|
|
72
72
|
LensModel?: string;
|
|
73
|
+
BodySerialNumber?: string;
|
|
73
74
|
LensSpecification?: number[];
|
|
74
75
|
ISO?: number;
|
|
75
76
|
FlashpixVersion?: number[];
|