@@ -26,6 +26,7 @@ import com.ashampoo.kim.format.tiff.constant.TiffTag
26
26
import com.ashampoo.kim.format.xmp.XmpReader
27
27
import com.ashampoo.kim.input.ByteArrayByteReader
28
28
import com.ashampoo.kim.model.GpsCoordinates
29
+ import com.ashampoo.kim.model.LocationShown
29
30
import com.ashampoo.kim.model.PhotoMetadata
30
31
import com.ashampoo.kim.model.TiffOrientation
31
32
import kotlinx.datetime.LocalDateTime
@@ -48,19 +49,20 @@ public object PhotoMetadataConverter {
48
49
ignoreOrientation : Boolean = false
49
50
): PhotoMetadata {
50
51
52
+ val xmpMetadata: PhotoMetadata ? = imageMetadata.xmp?.let {
53
+ XmpReader .readMetadata(it)
54
+ }
55
+
51
56
val orientation = if (ignoreOrientation)
52
57
TiffOrientation .STANDARD
53
58
else
54
59
TiffOrientation .of(imageMetadata.findShortValue(TiffTag .TIFF_TAG_ORIENTATION )?.toInt())
55
60
56
- val takenDateMillis = extractTakenDateMillis(imageMetadata)
57
-
58
- val gpsDirectory = imageMetadata.findTiffDirectory(TiffConstants .TIFF_DIRECTORY_GPS )
61
+ val takenDateMillis: Long? = xmpMetadata?.takenDate
62
+ ? : extractTakenDateMillisFromExif(imageMetadata)
59
63
60
- val gps = gpsDirectory?.let (GPSInfo ::createFrom)
61
-
62
- val latitude = gps?.getLatitudeAsDegreesNorth()
63
- val longitude = gps?.getLongitudeAsDegreesEast()
64
+ val gpsCoordinates: GpsCoordinates ? = xmpMetadata?.gpsCoordinates
65
+ ? : extractGpsCoordinatesFromExif(imageMetadata)
64
66
65
67
val cameraMake = imageMetadata.findStringValue(TiffTag .TIFF_TAG_MAKE )
66
68
val cameraModel = imageMetadata.findStringValue(TiffTag .TIFF_TAG_MODEL )
@@ -76,28 +78,22 @@ public object PhotoMetadataConverter {
76
78
val fNumber = imageMetadata.findDoubleValue(ExifTag .EXIF_TAG_FNUMBER )
77
79
val focalLength = imageMetadata.findDoubleValue(ExifTag .EXIF_TAG_FOCAL_LENGTH )
78
80
79
- val keywords = mutableSetOf<String >()
81
+ val keywords = xmpMetadata?.keywords?.ifEmpty {
82
+ extractKeywordsFromIptc(imageMetadata)
83
+ } ? : extractKeywordsFromIptc(imageMetadata)
80
84
81
85
val iptcRecords = imageMetadata.iptc?.records
82
86
83
- iptcRecords?.forEach {
87
+ val title = xmpMetadata?.title ? : iptcRecords
88
+ ?.find { it.iptcType == IptcTypes .OBJECT_NAME }
89
+ ?.value
84
90
85
- if (it.iptcType == IptcTypes . KEYWORDS )
86
- keywords.add(it.value)
87
- }
91
+ val description = xmpMetadata?.description ? : iptcRecords
92
+ ?.find { it.iptcType == IptcTypes . CAPTION_ABSTRACT }
93
+ ?.value
88
94
89
- val gpsCoordinates =
90
- if (latitude != null && longitude != null )
91
- GpsCoordinates (
92
- latitude = latitude,
93
- longitude = longitude
94
- )
95
- else
96
- null
97
-
98
- val xmpMetadata: PhotoMetadata ? = imageMetadata.xmp?.let {
99
- XmpReader .readMetadata(it)
100
- }
95
+ val location = xmpMetadata?.locationShown
96
+ ? : extractLocationFromIptc(imageMetadata)
101
97
102
98
val thumbnailBytes = imageMetadata.getExifThumbnailBytes()
103
99
@@ -120,9 +116,9 @@ public object PhotoMetadataConverter {
120
116
widthPx = imageMetadata.imageSize?.width,
121
117
heightPx = imageMetadata.imageSize?.height,
122
118
orientation = orientation,
123
- takenDate = xmpMetadata?.takenDate ? : takenDateMillis,
124
- gpsCoordinates = xmpMetadata?.gpsCoordinates ? : gpsCoordinates,
125
- location = xmpMetadata?. location,
119
+ takenDate = takenDateMillis,
120
+ gpsCoordinates = gpsCoordinates,
121
+ locationShown = location,
126
122
cameraMake = cameraMake,
127
123
cameraModel = cameraModel,
128
124
lensMake = lensMake,
@@ -131,9 +127,11 @@ public object PhotoMetadataConverter {
131
127
exposureTime = exposureTime,
132
128
fNumber = fNumber,
133
129
focalLength = focalLength,
130
+ title = title,
131
+ description = description,
134
132
flagged = xmpMetadata?.flagged ? : false ,
135
133
rating = xmpMetadata?.rating,
136
- keywords = keywords.ifEmpty { xmpMetadata?.keywords ? : emptySet() } ,
134
+ keywords = keywords,
137
135
faces = xmpMetadata?.faces ? : emptyMap(),
138
136
personsInImage = xmpMetadata?.personsInImage ? : emptySet(),
139
137
albums = xmpMetadata?.albums ? : emptySet(),
@@ -143,7 +141,7 @@ public object PhotoMetadataConverter {
143
141
}
144
142
145
143
@JvmStatic
146
- public fun extractTakenDateAsIsoString (metadata : ImageMetadata ): String? {
144
+ private fun extractTakenDateAsIsoString (metadata : ImageMetadata ): String? {
147
145
148
146
val takenDateField = metadata.findTiffField(ExifTag .EXIF_TAG_DATE_TIME_ORIGINAL )
149
147
? : return null
@@ -163,7 +161,9 @@ public object PhotoMetadataConverter {
163
161
}
164
162
165
163
@JvmStatic
166
- public fun extractTakenDateMillis (metadata : ImageMetadata ): Long? {
164
+ private fun extractTakenDateMillisFromExif (
165
+ metadata : ImageMetadata
166
+ ): Long? {
167
167
168
168
try {
169
169
@@ -203,6 +203,71 @@ public object PhotoMetadataConverter {
203
203
}
204
204
}
205
205
206
+ @JvmStatic
207
+ private fun extractGpsCoordinatesFromExif (
208
+ metadata : ImageMetadata
209
+ ): GpsCoordinates ? {
210
+
211
+ val gpsDirectory = metadata.findTiffDirectory(TiffConstants .TIFF_DIRECTORY_GPS )
212
+
213
+ val gps = gpsDirectory?.let (GPSInfo ::createFrom)
214
+
215
+ val latitude = gps?.getLatitudeAsDegreesNorth()
216
+ val longitude = gps?.getLongitudeAsDegreesEast()
217
+
218
+ if (latitude == null || longitude == null )
219
+ return null
220
+
221
+ return GpsCoordinates (
222
+ latitude = latitude,
223
+ longitude = longitude
224
+ )
225
+ }
226
+
227
+ @JvmStatic
228
+ private fun extractKeywordsFromIptc (
229
+ metadata : ImageMetadata
230
+ ): Set <String > {
231
+
232
+ return metadata.iptc?.records
233
+ ?.filter { it.iptcType == IptcTypes .KEYWORDS }
234
+ ?.map { it.value }
235
+ ?.toSet()
236
+ ? : emptySet()
237
+ }
238
+
239
+ @JvmStatic
240
+ private fun extractLocationFromIptc (
241
+ metadata : ImageMetadata
242
+ ): LocationShown ? {
243
+
244
+ val iptcRecords = metadata.iptc?.records
245
+ ? : return null
246
+
247
+ val iptcCity = iptcRecords
248
+ .find { it.iptcType == IptcTypes .CITY }
249
+ ?.value
250
+
251
+ val iptcState = iptcRecords
252
+ .find { it.iptcType == IptcTypes .PROVINCE_STATE }
253
+ ?.value
254
+
255
+ val iptcCountry = iptcRecords
256
+ .find { it.iptcType == IptcTypes .COUNTRY_PRIMARY_LOCATION_NAME }
257
+ ?.value
258
+
259
+ /* Don't create an object if everything is NULL */
260
+ if (iptcCity.isNullOrBlank() && iptcState.isNullOrBlank() && iptcCountry.isNullOrBlank())
261
+ return null
262
+
263
+ return LocationShown (
264
+ name = null ,
265
+ location = null ,
266
+ city = iptcCity,
267
+ state = iptcState,
268
+ country = iptcCountry
269
+ )
270
+ }
206
271
}
207
272
208
273
public fun ImageMetadata.convertToPhotoMetadata (
0 commit comments