expo-media-library 18.3.0-canary-20250930-9dc59d3 → 18.3.0-canary-20251003-7b9d7ff

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.
Files changed (68) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/main/java/expo/modules/medialibrary/next/MediaLibraryNextModule.kt +10 -0
  4. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/Asset.kt +8 -0
  5. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/ExifTags.kt +135 -0
  6. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetDelegate.kt +4 -0
  7. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetLegacyDelegate.kt +35 -0
  8. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/delegates/AssetModernDelegate.kt +36 -0
  9. package/android/src/main/java/expo/modules/medialibrary/next/objects/asset/deleters/AssetModernDeleter.kt +3 -0
  10. package/android/src/main/java/expo/modules/medialibrary/next/records/Location.kt +9 -0
  11. package/build/next/types/Asset.d.ts +17 -0
  12. package/build/next/types/Asset.d.ts.map +1 -1
  13. package/build/next/types/Asset.js.map +1 -1
  14. package/build/next/types/Location.d.ts +5 -0
  15. package/build/next/types/Location.d.ts.map +1 -0
  16. package/build/next/types/Location.js +2 -0
  17. package/build/next/types/Location.js.map +1 -0
  18. package/expo-module.config.json +1 -1
  19. package/ios/next/MediaLibraryNextModule.swift +8 -0
  20. package/ios/next/exceptions/Exceptions.swift +6 -0
  21. package/ios/next/objects/asset/Asset.swift +24 -21
  22. package/ios/next/objects/asset/Location.swift +13 -0
  23. package/ios/next/objects/asset/UriExtractor.swift +86 -0
  24. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar → 18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar} +0 -0
  25. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar.md5 +1 -0
  26. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar.sha1 +1 -0
  27. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar.sha256 +1 -0
  28. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar.sha512 +1 -0
  29. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar +0 -0
  30. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar.md5 +1 -0
  31. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar.sha1 +1 -0
  32. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar.sha256 +1 -0
  33. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar.sha512 +1 -0
  34. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module → 18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.module} +22 -22
  35. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.module.md5 +1 -0
  36. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.module.sha1 +1 -0
  37. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.module.sha256 +1 -0
  38. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.module.sha512 +1 -0
  39. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/{18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom → 18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.pom} +1 -1
  40. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.pom.md5 +1 -0
  41. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.pom.sha1 +1 -0
  42. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.pom.sha256 +1 -0
  43. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20251003-7b9d7ff/expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.pom.sha512 +1 -0
  44. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml +4 -4
  45. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.md5 +1 -1
  46. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha1 +1 -1
  47. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha256 +1 -1
  48. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/maven-metadata.xml.sha512 +1 -1
  49. package/package.json +3 -3
  50. package/src/next/types/Asset.ts +17 -0
  51. package/src/next/types/Location.ts +4 -0
  52. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.md5 +0 -1
  53. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha1 +0 -1
  54. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha256 +0 -1
  55. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar.sha512 +0 -1
  56. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar +0 -0
  57. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.md5 +0 -1
  58. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha1 +0 -1
  59. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha256 +0 -1
  60. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar.sha512 +0 -1
  61. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.md5 +0 -1
  62. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha1 +0 -1
  63. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha256 +0 -1
  64. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.module.sha512 +0 -1
  65. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.md5 +0 -1
  66. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha1 +0 -1
  67. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha256 +0 -1
  68. package/local-maven-repo/host/exp/exponent/expo.modules.medialibrary/18.3.0-canary-20250930-9dc59d3/expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.pom.sha512 +0 -1
package/CHANGELOG.md CHANGED
@@ -6,6 +6,7 @@
6
6
 
7
7
  ### 🎉 New features
8
8
 
9
+ - [next] Add exif support ([#39992](https://github.com/expo/expo/pull/39992) by [@Wenszel](https://github.com/Wenszel))
9
10
  - [next] Add `Album.get(title)` ([#39717](https://github.com/expo/expo/pull/39717) by [@Wenszel](https://github.com/Wenszel))
10
11
 
11
12
  ### 🐛 Bug fixes
@@ -4,13 +4,13 @@ plugins {
4
4
  }
5
5
 
6
6
  group = 'host.exp.exponent'
7
- version = '18.3.0-canary-20250930-9dc59d3'
7
+ version = '18.3.0-canary-20251003-7b9d7ff'
8
8
 
9
9
  android {
10
10
  namespace "expo.modules.medialibrary"
11
11
  defaultConfig {
12
12
  versionCode 37
13
- versionName "18.3.0-canary-20250930-9dc59d3"
13
+ versionName "18.3.0-canary-20251003-7b9d7ff"
14
14
  }
15
15
  }
16
16
 
@@ -92,6 +92,16 @@ class MediaLibraryNextModule : Module() {
92
92
  self.getDuration()
93
93
  }
94
94
 
95
+ AsyncFunction("getExif") Coroutine { self: Asset ->
96
+ systemPermissionsDelegate.requireSystemPermissions(false)
97
+ self.getExif()
98
+ }
99
+
100
+ AsyncFunction("getLocation") Coroutine { self: Asset ->
101
+ systemPermissionsDelegate.requireSystemPermissions(false)
102
+ self.getLocation()
103
+ }
104
+
95
105
  AsyncFunction("getFilename") Coroutine { self: Asset ->
96
106
  systemPermissionsDelegate.requireSystemPermissions(false)
97
107
  self.getFilename()
@@ -1,11 +1,13 @@
1
1
  package expo.modules.medialibrary.next.objects.asset
2
2
 
3
3
  import android.net.Uri
4
+ import android.os.Bundle
4
5
  import expo.modules.kotlin.sharedobjects.SharedObject
5
6
  import expo.modules.medialibrary.next.objects.asset.delegates.AssetDelegate
6
7
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
7
8
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
8
9
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
10
+ import expo.modules.medialibrary.next.records.Location
9
11
  import kotlinx.coroutines.Dispatchers
10
12
  import kotlinx.coroutines.withContext
11
13
 
@@ -39,6 +41,12 @@ class Asset(val assetDelegate: AssetDelegate) : SharedObject() {
39
41
  suspend fun getMimeType(): MimeType =
40
42
  assetDelegate.getMimeType()
41
43
 
44
+ suspend fun getLocation(): Location? =
45
+ assetDelegate.getLocation()
46
+
47
+ suspend fun getExif(): Bundle =
48
+ assetDelegate.getExif()
49
+
42
50
  suspend fun move(relativePath: RelativePath) = withContext(Dispatchers.IO) {
43
51
  assetDelegate.move(relativePath)
44
52
  }
@@ -0,0 +1,135 @@
1
+ package expo.modules.medialibrary.next.objects.asset
2
+
3
+ import androidx.exifinterface.media.ExifInterface
4
+
5
+ val EXIF_TAGS = arrayOf(
6
+ arrayOf("string", ExifInterface.TAG_ARTIST),
7
+ arrayOf("int", ExifInterface.TAG_BITS_PER_SAMPLE),
8
+ arrayOf("int", ExifInterface.TAG_COMPRESSION),
9
+ arrayOf("string", ExifInterface.TAG_COPYRIGHT),
10
+ arrayOf("string", ExifInterface.TAG_DATETIME),
11
+ arrayOf("string", ExifInterface.TAG_IMAGE_DESCRIPTION),
12
+ arrayOf("int", ExifInterface.TAG_IMAGE_LENGTH),
13
+ arrayOf("int", ExifInterface.TAG_IMAGE_WIDTH),
14
+ arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT),
15
+ arrayOf("int", ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH),
16
+ arrayOf("string", ExifInterface.TAG_MAKE),
17
+ arrayOf("string", ExifInterface.TAG_MODEL),
18
+ arrayOf("int", ExifInterface.TAG_ORIENTATION),
19
+ arrayOf("int", ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION),
20
+ arrayOf("int", ExifInterface.TAG_PLANAR_CONFIGURATION),
21
+ arrayOf("double", ExifInterface.TAG_PRIMARY_CHROMATICITIES),
22
+ arrayOf("double", ExifInterface.TAG_REFERENCE_BLACK_WHITE),
23
+ arrayOf("int", ExifInterface.TAG_RESOLUTION_UNIT),
24
+ arrayOf("int", ExifInterface.TAG_ROWS_PER_STRIP),
25
+ arrayOf("int", ExifInterface.TAG_SAMPLES_PER_PIXEL),
26
+ arrayOf("string", ExifInterface.TAG_SOFTWARE),
27
+ arrayOf("int", ExifInterface.TAG_STRIP_BYTE_COUNTS),
28
+ arrayOf("int", ExifInterface.TAG_STRIP_OFFSETS),
29
+ arrayOf("int", ExifInterface.TAG_TRANSFER_FUNCTION),
30
+ arrayOf("double", ExifInterface.TAG_WHITE_POINT),
31
+ arrayOf("double", ExifInterface.TAG_X_RESOLUTION),
32
+ arrayOf("double", ExifInterface.TAG_Y_CB_CR_COEFFICIENTS),
33
+ arrayOf("int", ExifInterface.TAG_Y_CB_CR_POSITIONING),
34
+ arrayOf("int", ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING),
35
+ arrayOf("double", ExifInterface.TAG_Y_RESOLUTION),
36
+ arrayOf("double", ExifInterface.TAG_APERTURE_VALUE),
37
+ arrayOf("double", ExifInterface.TAG_BRIGHTNESS_VALUE),
38
+ arrayOf("string", ExifInterface.TAG_CFA_PATTERN),
39
+ arrayOf("int", ExifInterface.TAG_COLOR_SPACE),
40
+ arrayOf("string", ExifInterface.TAG_COMPONENTS_CONFIGURATION),
41
+ arrayOf("double", ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL),
42
+ arrayOf("int", ExifInterface.TAG_CONTRAST),
43
+ arrayOf("int", ExifInterface.TAG_CUSTOM_RENDERED),
44
+ arrayOf("string", ExifInterface.TAG_DATETIME_DIGITIZED),
45
+ arrayOf("string", ExifInterface.TAG_DATETIME_ORIGINAL),
46
+ arrayOf("string", ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION),
47
+ arrayOf("double", ExifInterface.TAG_DIGITAL_ZOOM_RATIO),
48
+ arrayOf("string", ExifInterface.TAG_EXIF_VERSION),
49
+ arrayOf("double", ExifInterface.TAG_EXPOSURE_BIAS_VALUE),
50
+ arrayOf("double", ExifInterface.TAG_EXPOSURE_INDEX),
51
+ arrayOf("int", ExifInterface.TAG_EXPOSURE_MODE),
52
+ arrayOf("int", ExifInterface.TAG_EXPOSURE_PROGRAM),
53
+ arrayOf("double", ExifInterface.TAG_EXPOSURE_TIME),
54
+ arrayOf("double", ExifInterface.TAG_F_NUMBER),
55
+ arrayOf("string", ExifInterface.TAG_FILE_SOURCE),
56
+ arrayOf("int", ExifInterface.TAG_FLASH),
57
+ arrayOf("double", ExifInterface.TAG_FLASH_ENERGY),
58
+ arrayOf("string", ExifInterface.TAG_FLASHPIX_VERSION),
59
+ arrayOf("double", ExifInterface.TAG_FOCAL_LENGTH),
60
+ arrayOf("int", ExifInterface.TAG_FOCAL_LENGTH_IN_35MM_FILM),
61
+ arrayOf("int", ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT),
62
+ arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION),
63
+ arrayOf("double", ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION),
64
+ arrayOf("int", ExifInterface.TAG_GAIN_CONTROL),
65
+ arrayOf("int", ExifInterface.TAG_ISO_SPEED_RATINGS),
66
+ arrayOf("string", ExifInterface.TAG_IMAGE_UNIQUE_ID),
67
+ arrayOf("int", ExifInterface.TAG_LIGHT_SOURCE),
68
+ arrayOf("string", ExifInterface.TAG_MAKER_NOTE),
69
+ arrayOf("double", ExifInterface.TAG_MAX_APERTURE_VALUE),
70
+ arrayOf("int", ExifInterface.TAG_METERING_MODE),
71
+ arrayOf("int", ExifInterface.TAG_NEW_SUBFILE_TYPE),
72
+ arrayOf("string", ExifInterface.TAG_OECF),
73
+ arrayOf("int", ExifInterface.TAG_PIXEL_X_DIMENSION),
74
+ arrayOf("int", ExifInterface.TAG_PIXEL_Y_DIMENSION),
75
+ arrayOf("string", ExifInterface.TAG_RELATED_SOUND_FILE),
76
+ arrayOf("int", ExifInterface.TAG_SATURATION),
77
+ arrayOf("int", ExifInterface.TAG_SCENE_CAPTURE_TYPE),
78
+ arrayOf("string", ExifInterface.TAG_SCENE_TYPE),
79
+ arrayOf("int", ExifInterface.TAG_SENSING_METHOD),
80
+ arrayOf("int", ExifInterface.TAG_SHARPNESS),
81
+ arrayOf("double", ExifInterface.TAG_SHUTTER_SPEED_VALUE),
82
+ arrayOf("string", ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE),
83
+ arrayOf("string", ExifInterface.TAG_SPECTRAL_SENSITIVITY),
84
+ arrayOf("int", ExifInterface.TAG_SUBFILE_TYPE),
85
+ arrayOf("string", ExifInterface.TAG_SUBSEC_TIME),
86
+ arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_DIGITIZED),
87
+ arrayOf("string", ExifInterface.TAG_SUBSEC_TIME_ORIGINAL),
88
+ arrayOf("int", ExifInterface.TAG_SUBJECT_AREA),
89
+ arrayOf("double", ExifInterface.TAG_SUBJECT_DISTANCE),
90
+ arrayOf("int", ExifInterface.TAG_SUBJECT_DISTANCE_RANGE),
91
+ arrayOf("int", ExifInterface.TAG_SUBJECT_LOCATION),
92
+ arrayOf("string", ExifInterface.TAG_USER_COMMENT),
93
+ arrayOf("int", ExifInterface.TAG_WHITE_BALANCE),
94
+ arrayOf("int", ExifInterface.TAG_GPS_ALTITUDE_REF),
95
+ arrayOf("string", ExifInterface.TAG_GPS_AREA_INFORMATION),
96
+ arrayOf("double", ExifInterface.TAG_GPS_DOP),
97
+ arrayOf("string", ExifInterface.TAG_GPS_DATESTAMP),
98
+ arrayOf("double", ExifInterface.TAG_GPS_DEST_BEARING),
99
+ arrayOf("string", ExifInterface.TAG_GPS_DEST_BEARING_REF),
100
+ arrayOf("double", ExifInterface.TAG_GPS_DEST_DISTANCE),
101
+ arrayOf("string", ExifInterface.TAG_GPS_DEST_DISTANCE_REF),
102
+ arrayOf("double", ExifInterface.TAG_GPS_DEST_LATITUDE),
103
+ arrayOf("string", ExifInterface.TAG_GPS_DEST_LATITUDE_REF),
104
+ arrayOf("double", ExifInterface.TAG_GPS_DEST_LONGITUDE),
105
+ arrayOf("string", ExifInterface.TAG_GPS_DEST_LONGITUDE_REF),
106
+ arrayOf("int", ExifInterface.TAG_GPS_DIFFERENTIAL),
107
+ arrayOf("double", ExifInterface.TAG_GPS_IMG_DIRECTION),
108
+ arrayOf("string", ExifInterface.TAG_GPS_IMG_DIRECTION_REF),
109
+ arrayOf("string", ExifInterface.TAG_GPS_LATITUDE_REF),
110
+ arrayOf("string", ExifInterface.TAG_GPS_LONGITUDE_REF),
111
+ arrayOf("string", ExifInterface.TAG_GPS_MAP_DATUM),
112
+ arrayOf("string", ExifInterface.TAG_GPS_MEASURE_MODE),
113
+ arrayOf("string", ExifInterface.TAG_GPS_PROCESSING_METHOD),
114
+ arrayOf("string", ExifInterface.TAG_GPS_SATELLITES),
115
+ arrayOf("double", ExifInterface.TAG_GPS_SPEED),
116
+ arrayOf("string", ExifInterface.TAG_GPS_SPEED_REF),
117
+ arrayOf("string", ExifInterface.TAG_GPS_STATUS),
118
+ arrayOf("string", ExifInterface.TAG_GPS_TIMESTAMP),
119
+ arrayOf("double", ExifInterface.TAG_GPS_TRACK),
120
+ arrayOf("string", ExifInterface.TAG_GPS_TRACK_REF),
121
+ arrayOf("string", ExifInterface.TAG_GPS_VERSION_ID),
122
+ arrayOf("string", ExifInterface.TAG_INTEROPERABILITY_INDEX),
123
+ arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH),
124
+ arrayOf("int", ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH),
125
+ arrayOf("int", ExifInterface.TAG_DNG_VERSION),
126
+ arrayOf("int", ExifInterface.TAG_DEFAULT_CROP_SIZE),
127
+ arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_START),
128
+ arrayOf("int", ExifInterface.TAG_ORF_PREVIEW_IMAGE_LENGTH),
129
+ arrayOf("int", ExifInterface.TAG_ORF_ASPECT_FRAME),
130
+ arrayOf("int", ExifInterface.TAG_RW2_SENSOR_BOTTOM_BORDER),
131
+ arrayOf("int", ExifInterface.TAG_RW2_SENSOR_LEFT_BORDER),
132
+ arrayOf("int", ExifInterface.TAG_RW2_SENSOR_RIGHT_BORDER),
133
+ arrayOf("int", ExifInterface.TAG_RW2_SENSOR_TOP_BORDER),
134
+ arrayOf("int", ExifInterface.TAG_RW2_ISO)
135
+ )
@@ -1,10 +1,12 @@
1
1
  package expo.modules.medialibrary.next.objects.asset.delegates
2
2
 
3
3
  import android.net.Uri
4
+ import android.os.Bundle
4
5
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
5
6
  import expo.modules.medialibrary.next.objects.asset.Asset
6
7
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
7
8
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
9
+ import expo.modules.medialibrary.next.records.Location
8
10
 
9
11
  interface AssetDelegate {
10
12
  val contentUri: Uri
@@ -17,6 +19,8 @@ interface AssetDelegate {
17
19
  suspend fun getModificationTime(): Long?
18
20
  suspend fun getUri(): Uri
19
21
  suspend fun getMimeType(): MimeType
22
+ suspend fun getLocation(): Location?
23
+ suspend fun getExif(): Bundle
20
24
  suspend fun delete()
21
25
  suspend fun move(relativePath: RelativePath)
22
26
  suspend fun copy(relativePath: RelativePath): Asset
@@ -4,8 +4,10 @@ import android.content.Context
4
4
  import android.graphics.BitmapFactory
5
5
  import android.net.Uri
6
6
  import android.os.Build
7
+ import android.os.Bundle
7
8
  import androidx.annotation.DeprecatedSinceApi
8
9
  import androidx.core.net.toUri
10
+ import androidx.exifinterface.media.ExifInterface
9
11
  import expo.modules.medialibrary.MediaLibraryUtils
10
12
  import expo.modules.medialibrary.next.exceptions.AssetCouldNotBeCreated
11
13
  import expo.modules.medialibrary.next.exceptions.AssetPropertyNotFoundException
@@ -23,13 +25,18 @@ import expo.modules.medialibrary.next.extensions.safeCopy
23
25
  import expo.modules.medialibrary.next.extensions.safeMove
24
26
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
25
27
  import expo.modules.medialibrary.next.objects.asset.Asset
28
+ import expo.modules.medialibrary.next.objects.asset.EXIF_TAGS
26
29
  import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
27
30
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
28
31
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
32
+ import expo.modules.medialibrary.next.records.Location
29
33
  import kotlinx.coroutines.Dispatchers
34
+ import kotlinx.coroutines.ensureActive
30
35
  import kotlinx.coroutines.withContext
31
36
  import java.io.File
32
37
  import java.lang.ref.WeakReference
38
+ import kotlin.collections.component1
39
+ import kotlin.collections.component2
33
40
  import kotlin.time.DurationUnit
34
41
  import kotlin.time.toDuration
35
42
 
@@ -114,6 +121,34 @@ class AssetLegacyDelegate(
114
121
  ?: MimeType.from(getUri())
115
122
  }
116
123
 
124
+ override suspend fun getLocation(): Location? =
125
+ contentResolver.openInputStream(contentUri)?.use { stream ->
126
+ ExifInterface(stream)
127
+ .latLong
128
+ ?.let { (lat, long) -> Location(lat, long) }
129
+ }
130
+
131
+ override suspend fun getExif(): Bundle = withContext(Dispatchers.IO) {
132
+ if (getMediaType() != MediaType.IMAGE) {
133
+ return@withContext Bundle()
134
+ }
135
+ val exifBundle = Bundle()
136
+ contentResolver.openInputStream(contentUri)?.use { stream ->
137
+ ensureActive()
138
+ val exifInterface = ExifInterface(stream)
139
+ for ((type, name) in EXIF_TAGS) {
140
+ if (exifInterface.getAttribute(name) != null) {
141
+ when (type) {
142
+ "string" -> exifBundle.putString(name, exifInterface.getAttribute(name))
143
+ "int" -> exifBundle.putInt(name, exifInterface.getAttributeInt(name, 0))
144
+ "double" -> exifBundle.putDouble(name, exifInterface.getAttributeDouble(name, 0.0))
145
+ }
146
+ }
147
+ }
148
+ }
149
+ return@withContext exifBundle
150
+ }
151
+
117
152
  override suspend fun delete(): Unit = withContext(Dispatchers.IO) {
118
153
  assetDeleter.delete(contentUri)
119
154
  }
@@ -2,8 +2,10 @@ package expo.modules.medialibrary.next.objects.asset.delegates
2
2
 
3
3
  import android.content.Context
4
4
  import android.graphics.BitmapFactory
5
+ import androidx.exifinterface.media.ExifInterface
5
6
  import android.net.Uri
6
7
  import android.os.Build
8
+ import android.os.Bundle
7
9
  import androidx.annotation.RequiresApi
8
10
  import androidx.core.net.toUri
9
11
  import expo.modules.medialibrary.next.exceptions.AssetPropertyNotFoundException
@@ -22,13 +24,19 @@ import expo.modules.medialibrary.next.extensions.resolver.queryAssetCreationTime
22
24
  import expo.modules.medialibrary.next.extensions.resolver.updateRelativePath
23
25
  import expo.modules.medialibrary.next.objects.wrappers.RelativePath
24
26
  import expo.modules.medialibrary.next.objects.asset.Asset
27
+ import expo.modules.medialibrary.next.objects.asset.EXIF_TAGS
25
28
  import expo.modules.medialibrary.next.objects.asset.deleters.AssetDeleter
26
29
  import expo.modules.medialibrary.next.objects.wrappers.MediaType
27
30
  import expo.modules.medialibrary.next.objects.wrappers.MimeType
31
+ import expo.modules.medialibrary.next.records.Location
28
32
  import kotlinx.coroutines.Dispatchers
33
+ import kotlinx.coroutines.ensureActive
29
34
  import kotlinx.coroutines.withContext
30
35
  import java.io.File
31
36
  import java.lang.ref.WeakReference
37
+ import kotlin.collections.component1
38
+ import kotlin.collections.component2
39
+ import kotlin.let
32
40
  import kotlin.time.DurationUnit
33
41
  import kotlin.time.toDuration
34
42
 
@@ -114,6 +122,34 @@ class AssetModernDelegate(
114
122
  ?: MimeType.from(getUri())
115
123
  }
116
124
 
125
+ override suspend fun getLocation(): Location? =
126
+ contentResolver.openInputStream(contentUri)?.use { stream ->
127
+ ExifInterface(stream)
128
+ .latLong
129
+ ?.let { (lat, long) -> Location(lat, long) }
130
+ }
131
+
132
+ override suspend fun getExif(): Bundle = withContext(Dispatchers.IO) {
133
+ if (getMediaType() != MediaType.IMAGE) {
134
+ return@withContext Bundle()
135
+ }
136
+ val exifBundle = Bundle()
137
+ contentResolver.openInputStream(contentUri)?.use { stream ->
138
+ ensureActive()
139
+ val exifInterface = ExifInterface(stream)
140
+ for ((type, name) in EXIF_TAGS) {
141
+ if (exifInterface.getAttribute(name) != null) {
142
+ when (type) {
143
+ "string" -> exifBundle.putString(name, exifInterface.getAttribute(name))
144
+ "int" -> exifBundle.putInt(name, exifInterface.getAttributeInt(name, 0))
145
+ "double" -> exifBundle.putDouble(name, exifInterface.getAttributeDouble(name, 0.0))
146
+ }
147
+ }
148
+ }
149
+ }
150
+ return@withContext exifBundle
151
+ }
152
+
117
153
  override suspend fun delete() = withContext(Dispatchers.IO) {
118
154
  assetDeleter.delete(contentUri)
119
155
  }
@@ -16,6 +16,9 @@ class AssetModernDeleter(
16
16
  }
17
17
 
18
18
  override suspend fun delete(contentUris: List<Uri>) = withContext(Dispatchers.IO) {
19
+ if (contentUris.isEmpty()) {
20
+ return@withContext
21
+ }
19
22
  mediaStorePermissionsDelegate.launchMediaStoreDeleteRequest(contentUris)
20
23
  }
21
24
  }
@@ -0,0 +1,9 @@
1
+ package expo.modules.medialibrary.next.records
2
+
3
+ import expo.modules.kotlin.records.Field
4
+ import expo.modules.kotlin.records.Record
5
+
6
+ data class Location(
7
+ @Field val latitude: Double?,
8
+ @Field val longitude: Double?
9
+ ) : Record
@@ -1,4 +1,5 @@
1
1
  import { Album } from './Album';
2
+ import { Location } from './Location';
2
3
  import { MediaType } from './MediaType';
3
4
  /**
4
5
  * Represents a single media asset on the device (image, video, or audio).
@@ -73,6 +74,22 @@ export declare class Asset {
73
74
  * @throws An exception if the asset could not be found.
74
75
  */
75
76
  getWidth(): Promise<number>;
77
+ /**
78
+ * Gets the location of the asset.
79
+ * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.
80
+ * @returns A promise resolving to the {@link Location} object or `null` if the location data is unavailable.
81
+ * @throws An exception if the asset could not be found, or if the permission is not granted on Android.
82
+ */
83
+ getLocation(): Promise<Location | null>;
84
+ /**
85
+ * Gets the exif data of the {@link MediaType.image} asset.
86
+ * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.
87
+ * @returns A promise resolving to the exif data object or an empty object if the exif data is unavailable.
88
+ * @throws An exception if the asset could not be found.
89
+ */
90
+ getExif(): Promise<{
91
+ [key: string]: any;
92
+ }>;
76
93
  /**
77
94
  * Deletes the asset from the device’s media store.
78
95
  * @returns A promise that resolves once the deletion has completed.
@@ -1 +1 @@
1
- {"version":3,"file":"Asset.d.ts","sourceRoot":"","sources":["../../../src/next/types/Asset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,KAAK;IACxB;;;OAGG;gBACS,EAAE,EAAE,MAAM;IAEtB;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;OAIG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAEzC;;;;;;OAMG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAErC;;;;OAIG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAE5B;;;;OAIG;IACH,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAElC;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAE7C;;;;;OAKG;IACH,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAEzB;;;;;OAKG;IACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAE3B;;;;;;;;OAQG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;CAC/D"}
1
+ {"version":3,"file":"Asset.d.ts","sourceRoot":"","sources":["../../../src/next/types/Asset.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,CAAC,OAAO,OAAO,KAAK;IACxB;;;OAGG;gBACS,EAAE,EAAE,MAAM;IAEtB;;;;OAIG;IACH,EAAE,EAAE,MAAM,CAAC;IAEX;;;;OAIG;IACH,eAAe,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAEzC;;;;;;OAMG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAErC;;;;OAIG;IACH,WAAW,IAAI,OAAO,CAAC,MAAM,CAAC;IAE9B;;;;;OAKG;IACH,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC;IAE5B;;;;OAIG;IACH,YAAY,IAAI,OAAO,CAAC,SAAS,CAAC;IAElC;;;;OAIG;IACH,mBAAmB,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAE7C;;;;;OAKG;IACH,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAEzB;;;;;OAKG;IACH,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC;IAE3B;;;;;OAKG;IACH,WAAW,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;IAEvC;;;;;OAKG;IACH,OAAO,IAAI,OAAO,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;IAE1C;;;;;;;;OAQG;IACH,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBvB,MAAM,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC;CAC/D"}
@@ -1 +1 @@
1
- {"version":3,"file":"Asset.js","sourceRoot":"","sources":["../../../src/next/types/Asset.ts"],"names":[],"mappings":"","sourcesContent":["import { Album } from './Album';\nimport { MediaType } from './MediaType';\n\n/**\n * Represents a single media asset on the device (image, video, or audio).\n *\n * An {@link Asset} instance corresponds to an entry in the device's media store.\n * It exposes metadata (such as filename, dimensions, or creation time) and utility methods (like deleting).\n *\n * To create a new asset, use {@link Asset.create}, if you already have an asset ID, you can instantiate it directly using the constructor.\n */\nexport declare class Asset {\n /**\n * Reinitialize an instance of an asset with a given ID.\n * @param id - For Android, it is a `contentUri` (content://media/external/images/media/12345) and for iOS, it is `PHAsset` localIdentifier URI.\n */\n constructor(id: string);\n\n /**\n * ID of the asset.\n * Can be used to re-instantiate an {@link Asset} later.\n * For android it is a contentUri and PHAsset localIdentifier URI for iOS.\n */\n id: string;\n\n /**\n * Gets the creation time of the asset.\n * @returns A promise resolving to the UNIX timestamp in milliseconds, or `null` if unavailable.\n * @throws An exception if the asset could not be found.\n */\n getCreationTime(): Promise<number | null>;\n\n /**\n * Gets the duration of the asset.\n * Applies only to assets with media type {@link MediaType.audio} or {@link MediaType.video}.\n * For other media types, it returns `null`.\n * @returns A promise resolving to the duration in milliseconds, or `null` if not applicable.\n * @throws An exception if the asset could not be found.\n */\n getDuration(): Promise<number | null>;\n\n /**\n * Gets the filename of the asset, including its extension.\n * @returns A promise resolving to the filename string.\n * @throws An exception if the asset could not be found.\n */\n getFilename(): Promise<string>;\n\n /**\n * Gets the height of the asset in pixels.\n * Only applicable for image and video assets.\n * @returns A promise resolving to the height in pixels.\n * @throws An exception if the filename cannot be found.\n */\n getHeight(): Promise<number>;\n\n /**\n * Gets the media type of the asset (image, video, audio or unknown).\n * @returns A promise resolving to a {@link MediaType} enum value.\n * @throws An exception if the asset could not be found.\n */\n getMediaType(): Promise<MediaType>;\n\n /**\n * Gets the last modification time of the asset.\n * @returns A promise resolving to the UNIX timestamp in milliseconds, or `null` if unavailable.\n * @throws An exception if the asset could not be found.\n */\n getModificationTime(): Promise<number | null>;\n\n /**\n * Gets the URI pointing to the asset’s location in the system.\n * Example, for Android: `file:///storage/emulated/0/DCIM/Camera/IMG_20230915_123456.jpg`.\n * @returns A promise resolving to the string URI.\n * @throws An exception if the asset could not be found.\n */\n getUri(): Promise<string>;\n\n /**\n * Gets the width of the asset in pixels.\n * Only applicable for image and video assets.\n * @returns A promise resolving to the width in pixels.\n * @throws An exception if the asset could not be found.\n */\n getWidth(): Promise<number>;\n\n /**\n * Deletes the asset from the device’s media store.\n * @returns A promise that resolves once the deletion has completed.\n *\n * @example\n * ```ts\n * await asset.delete();\n * ```\n */\n delete(): Promise<void>;\n\n /*\n * A static function. Creates a new asset from a given file path.\n * Optionally associates the asset with an album. On Android, if not specified, the asset will be placed in the default \"Pictures\" directory.\n *\n * @param filePath - Local filesystem path (for example, `file:///...`) of the file to import.\n * @param album - Optional {@link Album} instance to place the asset in.\n * @returns A promise resolving to the created {@link Asset}.\n * @throws An exception if the asset could not be created, for example, if the file does not exist or permission is denied.\n *\n * @example\n * ```ts\n * const asset = await Asset.create(\"file:///storage/emulated/0/DCIM/Camera/IMG_20230915_123456.jpg\");\n * console.log(await asset.getFilename()); // \"IMG_20230915_123456.jpg\"\n * ```\n */\n static create(filePath: string, album?: Album): Promise<Asset>;\n}\n"]}
1
+ {"version":3,"file":"Asset.js","sourceRoot":"","sources":["../../../src/next/types/Asset.ts"],"names":[],"mappings":"","sourcesContent":["import { Album } from './Album';\nimport { Location } from './Location';\nimport { MediaType } from './MediaType';\n\n/**\n * Represents a single media asset on the device (image, video, or audio).\n *\n * An {@link Asset} instance corresponds to an entry in the device's media store.\n * It exposes metadata (such as filename, dimensions, or creation time) and utility methods (like deleting).\n *\n * To create a new asset, use {@link Asset.create}, if you already have an asset ID, you can instantiate it directly using the constructor.\n */\nexport declare class Asset {\n /**\n * Reinitialize an instance of an asset with a given ID.\n * @param id - For Android, it is a `contentUri` (content://media/external/images/media/12345) and for iOS, it is `PHAsset` localIdentifier URI.\n */\n constructor(id: string);\n\n /**\n * ID of the asset.\n * Can be used to re-instantiate an {@link Asset} later.\n * For android it is a contentUri and PHAsset localIdentifier URI for iOS.\n */\n id: string;\n\n /**\n * Gets the creation time of the asset.\n * @returns A promise resolving to the UNIX timestamp in milliseconds, or `null` if unavailable.\n * @throws An exception if the asset could not be found.\n */\n getCreationTime(): Promise<number | null>;\n\n /**\n * Gets the duration of the asset.\n * Applies only to assets with media type {@link MediaType.audio} or {@link MediaType.video}.\n * For other media types, it returns `null`.\n * @returns A promise resolving to the duration in milliseconds, or `null` if not applicable.\n * @throws An exception if the asset could not be found.\n */\n getDuration(): Promise<number | null>;\n\n /**\n * Gets the filename of the asset, including its extension.\n * @returns A promise resolving to the filename string.\n * @throws An exception if the asset could not be found.\n */\n getFilename(): Promise<string>;\n\n /**\n * Gets the height of the asset in pixels.\n * Only applicable for image and video assets.\n * @returns A promise resolving to the height in pixels.\n * @throws An exception if the filename cannot be found.\n */\n getHeight(): Promise<number>;\n\n /**\n * Gets the media type of the asset (image, video, audio or unknown).\n * @returns A promise resolving to a {@link MediaType} enum value.\n * @throws An exception if the asset could not be found.\n */\n getMediaType(): Promise<MediaType>;\n\n /**\n * Gets the last modification time of the asset.\n * @returns A promise resolving to the UNIX timestamp in milliseconds, or `null` if unavailable.\n * @throws An exception if the asset could not be found.\n */\n getModificationTime(): Promise<number | null>;\n\n /**\n * Gets the URI pointing to the asset’s location in the system.\n * Example, for Android: `file:///storage/emulated/0/DCIM/Camera/IMG_20230915_123456.jpg`.\n * @returns A promise resolving to the string URI.\n * @throws An exception if the asset could not be found.\n */\n getUri(): Promise<string>;\n\n /**\n * Gets the width of the asset in pixels.\n * Only applicable for image and video assets.\n * @returns A promise resolving to the width in pixels.\n * @throws An exception if the asset could not be found.\n */\n getWidth(): Promise<number>;\n\n /**\n * Gets the location of the asset.\n * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.\n * @returns A promise resolving to the {@link Location} object or `null` if the location data is unavailable.\n * @throws An exception if the asset could not be found, or if the permission is not granted on Android.\n */\n getLocation(): Promise<Location | null>;\n\n /**\n * Gets the exif data of the {@link MediaType.image} asset.\n * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.\n * @returns A promise resolving to the exif data object or an empty object if the exif data is unavailable.\n * @throws An exception if the asset could not be found.\n */\n getExif(): Promise<{ [key: string]: any }>;\n\n /**\n * Deletes the asset from the device’s media store.\n * @returns A promise that resolves once the deletion has completed.\n *\n * @example\n * ```ts\n * await asset.delete();\n * ```\n */\n delete(): Promise<void>;\n\n /*\n * A static function. Creates a new asset from a given file path.\n * Optionally associates the asset with an album. On Android, if not specified, the asset will be placed in the default \"Pictures\" directory.\n *\n * @param filePath - Local filesystem path (for example, `file:///...`) of the file to import.\n * @param album - Optional {@link Album} instance to place the asset in.\n * @returns A promise resolving to the created {@link Asset}.\n * @throws An exception if the asset could not be created, for example, if the file does not exist or permission is denied.\n *\n * @example\n * ```ts\n * const asset = await Asset.create(\"file:///storage/emulated/0/DCIM/Camera/IMG_20230915_123456.jpg\");\n * console.log(await asset.getFilename()); // \"IMG_20230915_123456.jpg\"\n * ```\n */\n static create(filePath: string, album?: Album): Promise<Asset>;\n}\n"]}
@@ -0,0 +1,5 @@
1
+ export type Location = {
2
+ latitude: number;
3
+ longitude: number;
4
+ };
5
+ //# sourceMappingURL=Location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Location.d.ts","sourceRoot":"","sources":["../../../src/next/types/Location.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=Location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Location.js","sourceRoot":"","sources":["../../../src/next/types/Location.ts"],"names":[],"mappings":"","sourcesContent":["export type Location = {\n latitude: number;\n longitude: number;\n};\n"]}
@@ -11,7 +11,7 @@
11
11
  "publication": {
12
12
  "groupId": "host.exp.exponent",
13
13
  "artifactId": "expo.modules.medialibrary",
14
- "version": "18.3.0-canary-20250930-9dc59d3",
14
+ "version": "18.3.0-canary-20251003-7b9d7ff",
15
15
  "repository": "local-maven-repo"
16
16
  }
17
17
  }
@@ -22,6 +22,10 @@ public final class MediaLibraryNextModule: Module {
22
22
  try await this.getDuration()
23
23
  }
24
24
 
25
+ AsyncFunction("getExif") { (this: Asset) in
26
+ try await this.getExif()
27
+ }
28
+
25
29
  AsyncFunction("getFilename") { (this: Asset) in
26
30
  try await this.getFilename()
27
31
  }
@@ -38,6 +42,10 @@ public final class MediaLibraryNextModule: Module {
38
42
  try await this.getModificationTime()
39
43
  }
40
44
 
45
+ AsyncFunction("getLocation") { (this: Asset) in
46
+ try await this.getLocation()
47
+ }
48
+
41
49
  AsyncFunction("getUri") { (this: Asset) in
42
50
  try await this.getUri()
43
51
  }
@@ -77,3 +77,9 @@ internal final class PredicateBuilderException: GenericException<String> {
77
77
  "Failed to build a predicate: \(param)"
78
78
  }
79
79
  }
80
+
81
+ internal final class FailedToExtractUri: GenericException<String> {
82
+ override var reason: String {
83
+ "Failed to extract an uri: \(param)"
84
+ }
85
+ }
@@ -49,6 +49,17 @@ class Asset: SharedObject {
49
49
  return date.millisecondsSince1970
50
50
  }
51
51
 
52
+ func getLocation() async throws -> Location? {
53
+ let phAsset = try await requirePHAsset()
54
+ guard let clLocation = phAsset.location else {
55
+ return nil
56
+ }
57
+ return Location(
58
+ latitude: clLocation.coordinate.latitude,
59
+ longitude: clLocation.coordinate.longitude
60
+ )
61
+ }
62
+
52
63
  func getModificationTime() async throws -> Int? {
53
64
  let phAsset = try await requirePHAsset()
54
65
  guard let date = phAsset.modificationDate else {
@@ -62,29 +73,21 @@ class Asset: SharedObject {
62
73
  return MediaTypeNext.from(phAsset.mediaType)
63
74
  }
64
75
 
65
- func getUri() async throws -> String {
76
+ func getExif() async throws -> [String: Any?] {
66
77
  let phAsset = try await requirePHAsset()
78
+ guard try await getMediaType() == MediaTypeNext.IMAGE else {
79
+ return [:]
80
+ }
81
+ let uri = try await UriExtractor.extract(from: phAsset)
82
+ guard let ciImage = CIImage(contentsOf: uri) else {
83
+ return [:]
84
+ }
85
+ return ciImage.properties
86
+ }
67
87
 
68
- switch phAsset.mediaType {
69
- case PHAssetMediaType.image:
70
- let contentEditingInput = try await phAsset.requestContentEditingInput()
71
- guard let url = contentEditingInput.fullSizeImageURL else {
72
- throw FailedToGetPropertyException("uri")
73
- }
74
- return url.absoluteString
75
-
76
- case PHAssetMediaType.video:
77
- let options = PHVideoRequestOptions()
78
- options.version = .original
79
- guard let avAsset = try await PHImageManager.default()
80
- .requestAVAsset(forVideo: phAsset, options: options) as? AVURLAsset else {
81
- throw FailedToGetPropertyException("uri")
82
- }
83
- return avAsset.url.absoluteString
84
-
85
- default:
86
- throw FailedToGetPropertyException("uri")
87
- }
88
+ func getUri() async throws -> String {
89
+ let phAsset = try await requirePHAsset()
90
+ return try await UriExtractor.extract(from: phAsset).absoluteString
88
91
  }
89
92
 
90
93
  func delete() async throws {
@@ -0,0 +1,13 @@
1
+ import ExpoModulesCore
2
+
3
+ struct Location: Record {
4
+ @Field var latitude: Double
5
+ @Field var longitude: Double
6
+
7
+ init() {}
8
+
9
+ init(latitude: Double, longitude: Double) {
10
+ self.latitude = latitude
11
+ self.longitude = longitude
12
+ }
13
+ }
@@ -0,0 +1,86 @@
1
+ import Photos
2
+ import AVFoundation
3
+
4
+ class UriExtractor {
5
+ static func extract(from phAsset: PHAsset) async throws -> URL {
6
+ switch phAsset.mediaType {
7
+ case .image:
8
+ return try await extract(fromImage: phAsset)
9
+ case .video:
10
+ return try await extract(fromVideo: phAsset)
11
+ default:
12
+ throw FailedToExtractUri("Unsupported media type")
13
+ }
14
+ }
15
+
16
+ private static func extract(fromImage phAsset: PHAsset) async throws -> URL {
17
+ let contentEditingInput = try await phAsset.requestContentEditingInput()
18
+ guard let url = contentEditingInput.fullSizeImageURL else {
19
+ throw FailedToExtractUri("Missing fullSizeImageURL for image")
20
+ }
21
+ return url
22
+ }
23
+
24
+ private static func extract(fromVideo phAsset: PHAsset) async throws -> URL {
25
+ let options = PHVideoRequestOptions()
26
+ options.version = .original
27
+ let avAsset = try await PHImageManager.default()
28
+ .requestAVAsset(forVideo: phAsset, options: options)
29
+
30
+ if let urlAsset = avAsset as? AVURLAsset {
31
+ return urlAsset.url
32
+ }
33
+ if let composition = avAsset as? AVComposition {
34
+ return try await handleSlowmotionVideo(composition: composition)
35
+ }
36
+ throw FailedToExtractUri("Unsupported AVAsset type")
37
+ }
38
+
39
+ private static func handleSlowmotionVideo(composition: AVComposition) async throws -> URL {
40
+ let outputURL = try makeOutputURL()
41
+ let exporter = try makeExporter(for: composition, outputURL: outputURL)
42
+ try await exportVideo(exporter: exporter)
43
+ return outputURL
44
+ }
45
+
46
+ private static func makeOutputURL() throws -> URL {
47
+ let directory = FileManager.default.temporaryDirectory
48
+ .appendingPathComponent("MediaLibraryUriExtractorDirectory", isDirectory: true)
49
+ try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
50
+
51
+ let filename = "slowMoVideo-\(UUID().uuidString).mov"
52
+ return directory.appendingPathComponent(filename)
53
+ }
54
+
55
+ private static func makeExporter(for composition: AVComposition, outputURL: URL) throws -> AVAssetExportSession {
56
+ guard let exporter = AVAssetExportSession(asset: composition, presetName: AVAssetExportPresetHighestQuality) else {
57
+ throw FailedToExtractUri("Failed to create AVAssetExportSession")
58
+ }
59
+
60
+ exporter.outputURL = outputURL
61
+ exporter.outputFileType = .mov
62
+ exporter.shouldOptimizeForNetworkUse = true
63
+ return exporter
64
+ }
65
+
66
+ private struct ExporterBox: @unchecked Sendable {
67
+ let exporter: AVAssetExportSession
68
+ }
69
+
70
+ private static func exportVideo(exporter: AVAssetExportSession) async throws {
71
+ let box = ExporterBox(exporter: exporter)
72
+
73
+ try await withCheckedThrowingContinuation { continuation in
74
+ box.exporter.exportAsynchronously {
75
+ switch box.exporter.status {
76
+ case .completed:
77
+ continuation.resume()
78
+ case .failed, .cancelled:
79
+ continuation.resume(throwing: box.exporter.error ?? FailedToExtractUri("Slowmotion Export failed"))
80
+ default:
81
+ continuation.resume(throwing: FailedToExtractUri("Unexpected export status: \(box.exporter.status.rawValue)"))
82
+ }
83
+ }
84
+ }
85
+ }
86
+ }
@@ -0,0 +1 @@
1
+ a96e8cdba38d07a56e757f9fd992faeda0134e7264c184bca2ba813f03af35138f55e33b0db974ee8b342a457f69c9f114c23c1bf3536904781c72b7e3719ca3
@@ -0,0 +1 @@
1
+ c2783c2c43d94019acd8b10733c8377a22e893b06ab4e5a4c7b7c01ffb4ab8a7211ba1873b7e7aeda7c21fcd321f3432697a92996dfee317d588aa464437d260
@@ -3,7 +3,7 @@
3
3
  "component": {
4
4
  "group": "host.exp.exponent",
5
5
  "module": "expo.modules.medialibrary",
6
- "version": "18.3.0-canary-20250930-9dc59d3",
6
+ "version": "18.3.0-canary-20251003-7b9d7ff",
7
7
  "attributes": {
8
8
  "org.gradle.status": "release"
9
9
  }
@@ -33,13 +33,13 @@
33
33
  ],
34
34
  "files": [
35
35
  {
36
- "name": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar",
37
- "url": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar",
38
- "size": 543329,
39
- "sha512": "8f89acc1129594801ecfa079599d82cadba212cf1a82f639ca6a5725d1632db0004a91090e1169132304bb8db817942a1fee1ed1d457e00b3a829bc51c8071ff",
40
- "sha256": "39d65da4d8af4706749c26b961765ba04aaad6332bad3a7efcc2ed5a41935023",
41
- "sha1": "8d2e17f294b33a519aa407e492170dbe4945c9f8",
42
- "md5": "d3710df965d39584743b2e04ad0d1810"
36
+ "name": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar",
37
+ "url": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar",
38
+ "size": 560052,
39
+ "sha512": "c2783c2c43d94019acd8b10733c8377a22e893b06ab4e5a4c7b7c01ffb4ab8a7211ba1873b7e7aeda7c21fcd321f3432697a92996dfee317d588aa464437d260",
40
+ "sha256": "dfc16c8348c09598afb661682a9af8626fe5157cf51e75b6bdc615a713be9ecd",
41
+ "sha1": "9d1e2c3fe0a44b21a707597b54855213a9efe8f5",
42
+ "md5": "fafd5b66cebedb47896cf16f908e30c6"
43
43
  }
44
44
  ]
45
45
  },
@@ -83,13 +83,13 @@
83
83
  ],
84
84
  "files": [
85
85
  {
86
- "name": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar",
87
- "url": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3.aar",
88
- "size": 543329,
89
- "sha512": "8f89acc1129594801ecfa079599d82cadba212cf1a82f639ca6a5725d1632db0004a91090e1169132304bb8db817942a1fee1ed1d457e00b3a829bc51c8071ff",
90
- "sha256": "39d65da4d8af4706749c26b961765ba04aaad6332bad3a7efcc2ed5a41935023",
91
- "sha1": "8d2e17f294b33a519aa407e492170dbe4945c9f8",
92
- "md5": "d3710df965d39584743b2e04ad0d1810"
86
+ "name": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar",
87
+ "url": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff.aar",
88
+ "size": 560052,
89
+ "sha512": "c2783c2c43d94019acd8b10733c8377a22e893b06ab4e5a4c7b7c01ffb4ab8a7211ba1873b7e7aeda7c21fcd321f3432697a92996dfee317d588aa464437d260",
90
+ "sha256": "dfc16c8348c09598afb661682a9af8626fe5157cf51e75b6bdc615a713be9ecd",
91
+ "sha1": "9d1e2c3fe0a44b21a707597b54855213a9efe8f5",
92
+ "md5": "fafd5b66cebedb47896cf16f908e30c6"
93
93
  }
94
94
  ]
95
95
  },
@@ -103,13 +103,13 @@
103
103
  },
104
104
  "files": [
105
105
  {
106
- "name": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar",
107
- "url": "expo.modules.medialibrary-18.3.0-canary-20250930-9dc59d3-sources.jar",
108
- "size": 73893,
109
- "sha512": "934a566095d435efae5715900e23e99408e18221068dca4a32a81381473d4c0239a5cd2256645b14f514cb507e05861971ae7c4f2a086e4a92c17359095026c5",
110
- "sha256": "745fd3718d10bdbf4207972eca04fb87abb808713ae84644ab08cffac403d0a2",
111
- "sha1": "bcccebe772325f21a9935321ae969920ab774547",
112
- "md5": "956cf3f11a22834fd45c099479659f34"
106
+ "name": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar",
107
+ "url": "expo.modules.medialibrary-18.3.0-canary-20251003-7b9d7ff-sources.jar",
108
+ "size": 76414,
109
+ "sha512": "a96e8cdba38d07a56e757f9fd992faeda0134e7264c184bca2ba813f03af35138f55e33b0db974ee8b342a457f69c9f114c23c1bf3536904781c72b7e3719ca3",
110
+ "sha256": "e74b32a8014b50e6c67da261cc7451ac37c463c83ba2b4f7faa1f194be110721",
111
+ "sha1": "a3cec26e7891bee7431c766ef8ae4fc8639590f5",
112
+ "md5": "9190c7184bc2cc0870be71699ef64471"
113
113
  }
114
114
  ]
115
115
  }
@@ -0,0 +1 @@
1
+ 9a1d3594d3c2883e27d7a328becd749e406c16ca735778a1b6c0b3c922dba990b139959a0de980ad0dd16f19cc3802bbf2d4f1d7ea7e2d01d2192a178d04ac5f
@@ -9,7 +9,7 @@
9
9
  <modelVersion>4.0.0</modelVersion>
10
10
  <groupId>host.exp.exponent</groupId>
11
11
  <artifactId>expo.modules.medialibrary</artifactId>
12
- <version>18.3.0-canary-20250930-9dc59d3</version>
12
+ <version>18.3.0-canary-20251003-7b9d7ff</version>
13
13
  <packaging>aar</packaging>
14
14
  <name>expo.modules.medialibrary</name>
15
15
  <url>https://github.com/expo/expo</url>
@@ -0,0 +1 @@
1
+ 5535b463919eb77258a0ce8797ccd0d3dca50b58cf35e89b6d4a99bd8ff5318b37e6df8000a51787a658216d19e7be18c503f3ece7ae56658af0b169773c6b57
@@ -3,11 +3,11 @@
3
3
  <groupId>host.exp.exponent</groupId>
4
4
  <artifactId>expo.modules.medialibrary</artifactId>
5
5
  <versioning>
6
- <latest>18.3.0-canary-20250930-9dc59d3</latest>
7
- <release>18.3.0-canary-20250930-9dc59d3</release>
6
+ <latest>18.3.0-canary-20251003-7b9d7ff</latest>
7
+ <release>18.3.0-canary-20251003-7b9d7ff</release>
8
8
  <versions>
9
- <version>18.3.0-canary-20250930-9dc59d3</version>
9
+ <version>18.3.0-canary-20251003-7b9d7ff</version>
10
10
  </versions>
11
- <lastUpdated>20250930162506</lastUpdated>
11
+ <lastUpdated>20251003163125</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- 5be50c89641ac7a17c22d66e90e0bbdb
1
+ edeb4e0a8650b3c962437aa26db06700
@@ -1 +1 @@
1
- d11d2433188d15a6d4c5cd66c0af6f549fb38bc0
1
+ 1969132e8bbd762ca4e1a23ce72465b6d2b74bdf
@@ -1 +1 @@
1
- 59bb0c55f5fb7c3d07129ef354cb5ab330a6599d21e4a685f09e8f329726541c
1
+ 714b65bf56dbf7069722765f42d4a9fc40602575a028380e20f259e6d8a75a46
@@ -1 +1 @@
1
- 3f6882355e871b3a5d29ae8d328bb45ea1d564899b32188902ce3219964a794c27707b2c359750b395dd88e24c36785ed5e47d73f1046d3446d33d4604164769
1
+ 55ba283da3c42c8c4988cb1918f6f2de79e2c82707b666946d1af1253941aad011e3ab5da80ed8e653b02d071822af2afdc78b8f1a6cf47857c2752929ba8398
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-media-library",
3
- "version": "18.3.0-canary-20250930-9dc59d3",
3
+ "version": "18.3.0-canary-20251003-7b9d7ff",
4
4
  "description": "Provides access to user's media library.",
5
5
  "main": "build/MediaLibrary.js",
6
6
  "types": "build/MediaLibrary.d.ts",
@@ -38,10 +38,10 @@
38
38
  "preset": "expo-module-scripts"
39
39
  },
40
40
  "devDependencies": {
41
- "expo-module-scripts": "5.0.8-canary-20250930-9dc59d3"
41
+ "expo-module-scripts": "5.0.8-canary-20251003-7b9d7ff"
42
42
  },
43
43
  "peerDependencies": {
44
- "expo": "55.0.0-canary-20250930-9dc59d3",
44
+ "expo": "55.0.0-canary-20251003-7b9d7ff",
45
45
  "react-native": "*"
46
46
  },
47
47
  "codegenConfig": {
@@ -1,4 +1,5 @@
1
1
  import { Album } from './Album';
2
+ import { Location } from './Location';
2
3
  import { MediaType } from './MediaType';
3
4
 
4
5
  /**
@@ -84,6 +85,22 @@ export declare class Asset {
84
85
  */
85
86
  getWidth(): Promise<number>;
86
87
 
88
+ /**
89
+ * Gets the location of the asset.
90
+ * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.
91
+ * @returns A promise resolving to the {@link Location} object or `null` if the location data is unavailable.
92
+ * @throws An exception if the asset could not be found, or if the permission is not granted on Android.
93
+ */
94
+ getLocation(): Promise<Location | null>;
95
+
96
+ /**
97
+ * Gets the exif data of the {@link MediaType.image} asset.
98
+ * On Android, this method requires the `ACCESS_MEDIA_LOCATION` permission to access location metadata.
99
+ * @returns A promise resolving to the exif data object or an empty object if the exif data is unavailable.
100
+ * @throws An exception if the asset could not be found.
101
+ */
102
+ getExif(): Promise<{ [key: string]: any }>;
103
+
87
104
  /**
88
105
  * Deletes the asset from the device’s media store.
89
106
  * @returns A promise that resolves once the deletion has completed.
@@ -0,0 +1,4 @@
1
+ export type Location = {
2
+ latitude: number;
3
+ longitude: number;
4
+ };
@@ -1 +0,0 @@
1
- 934a566095d435efae5715900e23e99408e18221068dca4a32a81381473d4c0239a5cd2256645b14f514cb507e05861971ae7c4f2a086e4a92c17359095026c5
@@ -1 +0,0 @@
1
- 8f89acc1129594801ecfa079599d82cadba212cf1a82f639ca6a5725d1632db0004a91090e1169132304bb8db817942a1fee1ed1d457e00b3a829bc51c8071ff
@@ -1 +0,0 @@
1
- 65f52213563a4c3e7f0798a43768ce74a13fe1310e0267e6bd22671432e1fde347e43e119d0484726bde0e97862b1d985d0e6f8fe65e63b75d457ec31a582a4c
@@ -1 +0,0 @@
1
- 909ddf923e911d61392be5129b8b8db267761ae3e2fc3abd2ed19441bab8753907a084525fed4e1ea1e1d9d76ff87d0f7567f29964ba32d0118e8281c6d499b8