@@ -26,6 +26,7 @@ import com.ashampoo.kim.format.tiff.constant.TiffTag
2626import com.ashampoo.kim.format.xmp.XmpReader
2727import com.ashampoo.kim.input.ByteArrayByteReader
2828import com.ashampoo.kim.model.GpsCoordinates
29+ import com.ashampoo.kim.model.LocationShown
2930import com.ashampoo.kim.model.PhotoMetadata
3031import com.ashampoo.kim.model.TiffOrientation
3132import kotlinx.datetime.LocalDateTime
@@ -48,19 +49,20 @@ public object PhotoMetadataConverter {
4849 ignoreOrientation : Boolean = false
4950 ): PhotoMetadata {
5051
52+ val xmpMetadata: PhotoMetadata ? = imageMetadata.xmp?.let {
53+ XmpReader .readMetadata(it)
54+ }
55+
5156 val orientation = if (ignoreOrientation)
5257 TiffOrientation .STANDARD
5358 else
5459 TiffOrientation .of(imageMetadata.findShortValue(TiffTag .TIFF_TAG_ORIENTATION )?.toInt())
5560
56- val takenDateMillis = extractTakenDateMillis(imageMetadata)
57-
58- val gpsDirectory = imageMetadata.findTiffDirectory(TiffConstants .TIFF_DIRECTORY_GPS )
61+ val takenDateMillis: Long? = xmpMetadata?.takenDate
62+ ? : extractTakenDateMillisFromExif(imageMetadata)
5963
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)
6466
6567 val cameraMake = imageMetadata.findStringValue(TiffTag .TIFF_TAG_MAKE )
6668 val cameraModel = imageMetadata.findStringValue(TiffTag .TIFF_TAG_MODEL )
@@ -76,28 +78,22 @@ public object PhotoMetadataConverter {
7678 val fNumber = imageMetadata.findDoubleValue(ExifTag .EXIF_TAG_FNUMBER )
7779 val focalLength = imageMetadata.findDoubleValue(ExifTag .EXIF_TAG_FOCAL_LENGTH )
7880
79- val keywords = mutableSetOf<String >()
81+ val keywords = xmpMetadata?.keywords?.ifEmpty {
82+ extractKeywordsFromIptc(imageMetadata)
83+ } ? : extractKeywordsFromIptc(imageMetadata)
8084
8185 val iptcRecords = imageMetadata.iptc?.records
8286
83- iptcRecords?.forEach {
87+ val title = xmpMetadata?.title ? : iptcRecords
88+ ?.find { it.iptcType == IptcTypes .OBJECT_NAME }
89+ ?.value
8490
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
8894
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)
10197
10298 val thumbnailBytes = imageMetadata.getExifThumbnailBytes()
10399
@@ -120,9 +116,9 @@ public object PhotoMetadataConverter {
120116 widthPx = imageMetadata.imageSize?.width,
121117 heightPx = imageMetadata.imageSize?.height,
122118 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,
126122 cameraMake = cameraMake,
127123 cameraModel = cameraModel,
128124 lensMake = lensMake,
@@ -131,9 +127,11 @@ public object PhotoMetadataConverter {
131127 exposureTime = exposureTime,
132128 fNumber = fNumber,
133129 focalLength = focalLength,
130+ title = title,
131+ description = description,
134132 flagged = xmpMetadata?.flagged ? : false ,
135133 rating = xmpMetadata?.rating,
136- keywords = keywords.ifEmpty { xmpMetadata?.keywords ? : emptySet() } ,
134+ keywords = keywords,
137135 faces = xmpMetadata?.faces ? : emptyMap(),
138136 personsInImage = xmpMetadata?.personsInImage ? : emptySet(),
139137 albums = xmpMetadata?.albums ? : emptySet(),
@@ -143,7 +141,7 @@ public object PhotoMetadataConverter {
143141 }
144142
145143 @JvmStatic
146- public fun extractTakenDateAsIsoString (metadata : ImageMetadata ): String? {
144+ private fun extractTakenDateAsIsoString (metadata : ImageMetadata ): String? {
147145
148146 val takenDateField = metadata.findTiffField(ExifTag .EXIF_TAG_DATE_TIME_ORIGINAL )
149147 ? : return null
@@ -163,7 +161,9 @@ public object PhotoMetadataConverter {
163161 }
164162
165163 @JvmStatic
166- public fun extractTakenDateMillis (metadata : ImageMetadata ): Long? {
164+ private fun extractTakenDateMillisFromExif (
165+ metadata : ImageMetadata
166+ ): Long? {
167167
168168 try {
169169
@@ -203,6 +203,71 @@ public object PhotoMetadataConverter {
203203 }
204204 }
205205
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+ }
206271}
207272
208273public fun ImageMetadata.convertToPhotoMetadata (
0 commit comments