Skip to content

Commit 7295517

Browse files
committed
fix(android): use file path for ExifInterface read on file:// URIs
Use file path constructor for file:// URIs instead of InputStream, giving ExifInterface seek capability. Bump exifinterface 1.3.7 → 1.4.2.
1 parent b39189c commit 7295517

2 files changed

Lines changed: 51 additions & 19 deletions

File tree

android/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,5 +64,5 @@ android {
6464

6565
dependencies {
6666
implementation "com.facebook.react:react-android"
67-
implementation "androidx.exifinterface:exifinterface:1.3.7"
67+
implementation "androidx.exifinterface:exifinterface:1.4.2"
6868
}

android/src/main/java/com/lodev09/exify/ExifyModule.kt

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.facebook.react.modules.core.PermissionAwareActivity
1616
import com.facebook.react.modules.core.PermissionListener
1717
import com.facebook.react.util.RNLog
1818
import com.lodev09.exify.ExifyUtils.formatTags
19+
import java.io.File
1920
import java.io.IOException
2021

2122
private const val ERROR_TAG = "E_EXIFY_ERROR"
@@ -72,35 +73,66 @@ class ExifyModule(
7273
promise: Promise,
7374
) {
7475
try {
75-
val inputStream =
76-
if (scheme == "http" || scheme == "https") {
77-
java.net.URL(uri).openStream()
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!!)
8479
} else {
85-
context.contentResolver.openInputStream(photoUri)
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) }
86100
}
87101

88-
if (inputStream == null) {
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)
93103

94-
inputStream.use {
95-
val tags = formatTags(ExifInterface(it))
96-
promise.resolve(tags)
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_STRING_TAGS.filter { !tags.hasKey(it) }.toSet()
108+
if (missingTags.isNotEmpty()) {
109+
val fallback =
110+
try {
111+
openInputStream(uri, photoUri, scheme)?.use { readFallbackTags(it, missingTags) }
112+
} catch (_: Exception) {
113+
null
114+
}
115+
fallback?.forEach { (tag, value) -> tags.putString(tag, value) }
97116
}
117+
118+
promise.resolve(tags)
98119
} catch (e: Exception) {
99120
RNLog.w(context, "Exify: ${e.message}")
100121
promise.reject(ERROR_TAG, e.message, e)
101122
}
102123
}
103124

125+
private fun openInputStream(
126+
uri: String,
127+
photoUri: Uri,
128+
scheme: String,
129+
): java.io.InputStream? =
130+
when (scheme) {
131+
"file" -> File(photoUri.path!!).inputStream()
132+
"content" -> context.contentResolver.openInputStream(photoUri)
133+
else -> java.net.URL(uri).openStream()
134+
}
135+
104136
@Throws(IOException::class)
105137
override fun write(
106138
uri: String,

0 commit comments

Comments
 (0)