|
25 | 25 | import java.util.Date; |
26 | 26 | import java.util.Iterator; |
27 | 27 | import java.util.Locale; |
| 28 | +import java.util.Map; |
28 | 29 | import java.util.TimeZone; |
29 | 30 | import java.util.regex.Matcher; |
30 | 31 | import java.util.regex.Pattern; |
31 | 32 |
|
| 33 | +import com.adobe.internal.xmp.XMPException; |
| 34 | +import com.adobe.internal.xmp.XMPMetaFactory; |
32 | 35 | import com.drew.imaging.heif.HeifMetadataReader; |
33 | 36 | import com.drew.imaging.jpeg.JpegMetadataReader; |
34 | 37 | import com.drew.imaging.jpeg.JpegProcessingException; |
|
52 | 55 | import com.drew.metadata.iptc.IptcDirectory; |
53 | 56 | import com.drew.metadata.jpeg.JpegCommentDirectory; |
54 | 57 | import com.drew.metadata.jpeg.JpegDirectory; |
| 58 | +import com.drew.metadata.xmp.XmpDirectory; |
55 | 59 | import org.apache.commons.io.IOUtils; |
56 | 60 | import org.apache.commons.io.input.UnsynchronizedByteArrayInputStream; |
57 | 61 | import org.apache.jempbox.xmp.XMPMetadata; |
@@ -93,7 +97,7 @@ public class ImageMetadataExtractor { |
93 | 97 | public ImageMetadataExtractor(Metadata metadata) { |
94 | 98 | this(metadata, new CopyUnknownFieldsHandler(), new TiffPageNumberHandler(), |
95 | 99 | new JpegCommentHandler(), new ExifHandler(), new DimensionsHandler(), |
96 | | - new GeotagHandler(), new IptcHandler()); |
| 100 | + new GeotagHandler(), new IptcHandler(), new XmpHandler()); |
97 | 101 | } |
98 | 102 |
|
99 | 103 | /** |
@@ -304,6 +308,56 @@ public void handle(Directory directory, Metadata metadata) throws MetadataExcept |
304 | 308 | } |
305 | 309 | } |
306 | 310 |
|
| 311 | + /** |
| 312 | + * Copies the XMP properties parsed by Metadata Extractor into the metadata, |
| 313 | + * keyed by their {@code prefix:name} path. The other handlers copy a |
| 314 | + * directory's tags, but XMP keeps its properties in a separate map |
| 315 | + * ({@link XmpDirectory#getXmpProperties()}), so without this they are lost. |
| 316 | + * A property is skipped when its key is already set or matches a known Tika |
| 317 | + * field, so normalized values from other handlers are not overwritten. |
| 318 | + */ |
| 319 | + static class XmpHandler implements DirectoryHandler { |
| 320 | + |
| 321 | + static { |
| 322 | + // XMPCore's namespace registry is process-global and keeps the first |
| 323 | + // prefix it sees for a URI. Pin canonical prefixes so keys stay stable |
| 324 | + // (files use both Camera and GCamera for the Google photo namespace). |
| 325 | + // https://developer.android.com/media/platform/motion-photo-format |
| 326 | + try { |
| 327 | + XMPMetaFactory.getSchemaRegistry() |
| 328 | + .registerNamespace("http://ns.google.com/photos/1.0/camera/", "Camera"); |
| 329 | + XMPMetaFactory.getSchemaRegistry() |
| 330 | + .registerNamespace("http://ns.google.com/photos/1.0/container/", "Container"); |
| 331 | + XMPMetaFactory.getSchemaRegistry() |
| 332 | + .registerNamespace("http://ns.google.com/photos/1.0/container/item/", "Item"); |
| 333 | + } catch (XMPException e) { |
| 334 | + // Constant, valid URIs, so this cannot throw. A broken registration |
| 335 | + // would make the keys non-deterministic (parse-order dependent), which |
| 336 | + // MotionPhotoXmpTest catches in CI; rethrowing from a static initializer |
| 337 | + // would break all image parsing, so it is swallowed. |
| 338 | + } |
| 339 | + } |
| 340 | + |
| 341 | + public boolean supports(Class<? extends Directory> directoryType) { |
| 342 | + return XmpDirectory.class.isAssignableFrom(directoryType); |
| 343 | + } |
| 344 | + |
| 345 | + public void handle(Directory directory, Metadata metadata) throws MetadataException { |
| 346 | + Map<String, String> properties = ((XmpDirectory) directory).getXmpProperties(); |
| 347 | + if (properties == null) { |
| 348 | + return; |
| 349 | + } |
| 350 | + for (Map.Entry<String, String> property : properties.entrySet()) { |
| 351 | + String name = property.getKey(); |
| 352 | + String value = property.getValue(); |
| 353 | + if (value != null && metadata.get(name) == null |
| 354 | + && !MetadataFields.isMetadataField(name)) { |
| 355 | + metadata.set(name, value); |
| 356 | + } |
| 357 | + } |
| 358 | + } |
| 359 | + } |
| 360 | + |
307 | 361 | static class TiffPageNumberHandler implements DirectoryHandler { |
308 | 362 | public boolean supports(Class<? extends Directory> directoryType) { |
309 | 363 | return true; |
|
0 commit comments