diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java index dbad3f8c..4837a330 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractDirectory.java @@ -13,7 +13,6 @@ import java.util.List; * @version $Id: AbstractDirectory.java,v 1.0 Nov 11, 2009 5:31:04 PM haraldk Exp$ */ public abstract class AbstractDirectory implements Directory { - // A linked hashmap or a stable bag structure might also work.. private final List mEntries = new ArrayList(); protected AbstractDirectory(final Collection pEntries) { @@ -32,9 +31,9 @@ public abstract class AbstractDirectory implements Directory { return null; } - public Entry getEntryByName(final String pName) { + public Entry getEntryByFieldName(final String pFieldName) { for (Entry entry : this) { - if (entry.getFieldName().equals(pName)) { + if (entry.getFieldName() != null && entry.getFieldName().equals(pFieldName)) { return entry; } } @@ -66,6 +65,7 @@ public abstract class AbstractDirectory implements Directory { return mEntries.add(pEntry); } + @SuppressWarnings({"SuspiciousMethodCalls"}) public boolean remove(final Object pEntry) { assertMutable(); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java index e03a917d..2ef60c07 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/AbstractEntry.java @@ -23,7 +23,7 @@ public abstract class AbstractEntry implements Entry { mValue = pValue; } - public Object getIdentifier() { + public final Object getIdentifier() { return mIdentifier; } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java index 66fa5011..07c5fb0e 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/Directory.java @@ -12,7 +12,7 @@ public interface Directory extends Iterable { // For multiple entries with same id in directory, the first entry (using the order from the stream) will be returned Entry getEntryById(Object pIdentifier); - Entry getEntryByName(String pName); + Entry getEntryByFieldName(String pName); // Iterator containing the entries in //Iterator getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java index 4fc8c22f..5ff05628 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIF.java @@ -7,41 +7,8 @@ package com.twelvemonkeys.imageio.metadata.exif; * @author last modified by $Author: haraldk$ * @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$ */ -interface EXIF { - /* - 1 = BYTE 8-bit unsigned integer. - 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte - must be NUL (binary zero). - 3 = SHORT 16-bit (2-byte) unsigned integer. - 4 = LONG 32-bit (4-byte) unsigned integer. - 5 = RATIONAL Two LONGs: the first represents the numerator of a - fraction; the second, the denominator. - - TIFF 6.0 and above: - 6 = SBYTE An 8-bit signed (twos-complement) integer. - 7 = UNDEFINED An 8-bit byte that may contain anything, depending on - the definition of the field. - 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. - 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. - 10 = SRATIONAL Two SLONGs: the first represents the numerator of a - fraction, the second the denominator. - 11 = FLOAT Single precision (4-byte) IEEE format. - 12 = DOUBLE Double precision (8-byte) IEEE format. - */ - - static int EXIF_IFD = 0x8769; - - static String[] TYPE_NAMES = { - "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", - - "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", - }; - - static int[] TYPE_LENGTHS = { - 1, 1, 2, 4, 8, - - 1, 1, 2, 4, 8, 4, 8, - }; - - int TIFF_MAGIC = 42; +public interface EXIF { + int TAG_COLOR_SPACE = 40961; + int TAG_PIXEL_X_DIMENSION = 40962; + int TAG_PIXEL_Y_DIMENSION = 40963; } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java index 98d426de..77bb8e64 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFDirectory.java @@ -4,7 +4,6 @@ import com.twelvemonkeys.imageio.metadata.AbstractDirectory; import com.twelvemonkeys.imageio.metadata.Entry; import java.util.Collection; -import java.util.List; /** * EXIFDirectory diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java index 7ed7cbb5..da2e4c6e 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFEntry.java @@ -12,19 +12,42 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry; final class EXIFEntry extends AbstractEntry { final private short mType; - EXIFEntry(final Object pIdentifier, final Object pValue, final short pType) { + EXIFEntry(final int pIdentifier, final Object pValue, final short pType) { super(pIdentifier, pValue); + + if (pType < 1 || pType > TIFF.TYPE_NAMES.length) { + throw new IllegalArgumentException(String.format("Illegal EXIF type: %s", pType)); + } + mType = pType; } @Override public String getFieldName() { - // TODO: Need tons of constants... ;-) - return super.getFieldName(); + switch ((Integer) getIdentifier()) { + case TIFF.TAG_SOFTWARE: + return "Software"; + case TIFF.TAG_DATE_TIME: + return "DateTime"; + case TIFF.TAG_ARTIST: + return "Artist"; + case TIFF.TAG_COPYRIGHT: + return "Copyright"; + + case EXIF.TAG_COLOR_SPACE: + return "ColorSpace"; + case EXIF.TAG_PIXEL_X_DIMENSION: + return "PixelXDimension"; + case EXIF.TAG_PIXEL_Y_DIMENSION: + return "PixelYDimension"; + // TODO: More field names + } + + return null; } @Override public String getTypeName() { - return EXIF.TYPE_NAMES[mType]; + return TIFF.TYPE_NAMES[mType - 1]; } } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java index d5a406ca..f4dd56f1 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/EXIFReader.java @@ -29,12 +29,12 @@ public final class EXIFReader extends MetadataReader { pInput.setByteOrder(ByteOrder.LITTLE_ENDIAN); } else if (!(bom[0] == 'M' && bom[1] == 'M')) { - throw new IIOException(String.format("Invalid byte order marker '%s'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); + throw new IIOException(String.format("Invalid TIFF byte order mark '%s', expected: 'II' or 'MM'", StringUtil.decode(bom, 0, bom.length, "ASCII"))); } int magic = pInput.readUnsignedShort(); - if (magic != EXIF.TIFF_MAGIC) { - throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, EXIF.TIFF_MAGIC)); + if (magic != TIFF.TIFF_MAGIC) { + throw new IIOException(String.format("Wrong TIFF magic in EXIF data: %04x, expected: %04x", magic, TIFF.TIFF_MAGIC)); } long directoryOffset = pInput.readUnsignedInt(); @@ -73,9 +73,8 @@ public final class EXIFReader extends MetadataReader { Object value; - // TODO: Handle other sub-IFDs - // GPS IFD: 0x8825, Interoperability IFD: 0xA005 - if (tagId == EXIF.EXIF_IFD) { + if (tagId == TIFF.IFD_EXIF || tagId == TIFF.IFD_GPS || tagId == TIFF.IFD_INTEROP) { + // Parse sub IFDs long offset = pInput.readUnsignedInt(); pInput.mark(); @@ -207,8 +206,8 @@ public final class EXIFReader extends MetadataReader { } private int getValueLength(final int pType, final int pCount) { - if (pType > 0 && pType <= EXIF.TYPE_LENGTHS.length) { - return EXIF.TYPE_LENGTHS[pType - 1] * pCount; + if (pType > 0 && pType <= TIFF.TYPE_LENGTHS.length) { + return TIFF.TYPE_LENGTHS[pType - 1] * pCount; } return -1; diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java new file mode 100644 index 00000000..b47ae01b --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/exif/TIFF.java @@ -0,0 +1,53 @@ +package com.twelvemonkeys.imageio.metadata.exif; + +/** + * TIFF + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: TIFF.java,v 1.0 Nov 15, 2009 3:02:24 PM haraldk Exp$ + */ +public interface TIFF { + int TIFF_MAGIC = 42; + + /* + 1 = BYTE 8-bit unsigned integer. + 2 = ASCII 8-bit byte that contains a 7-bit ASCII code; the last byte + must be NUL (binary zero). + 3 = SHORT 16-bit (2-byte) unsigned integer. + 4 = LONG 32-bit (4-byte) unsigned integer. + 5 = RATIONAL Two LONGs: the first represents the numerator of a + fraction; the second, the denominator. + + TIFF 6.0 and above: + 6 = SBYTE An 8-bit signed (twos-complement) integer. + 7 = UNDEFINED An 8-bit byte that may contain anything, depending on + the definition of the field. + 8 = SSHORT A 16-bit (2-byte) signed (twos-complement) integer. + 9 = SLONG A 32-bit (4-byte) signed (twos-complement) integer. + 10 = SRATIONAL Two SLONGs: the first represents the numerator of a + fraction, the second the denominator. + 11 = FLOAT Single precision (4-byte) IEEE format. + 12 = DOUBLE Double precision (8-byte) IEEE format. + */ + String[] TYPE_NAMES = { + "BYTE", "ASCII", "SHORT", "LONG", "RATIONAL", + + "SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE", + }; + int[] TYPE_LENGTHS = { + 1, 1, 2, 4, 8, + + 1, 1, 2, 4, 8, 4, 8, + }; + + int IFD_EXIF = 0x8769; + int IFD_GPS = 0x8825; + int IFD_INTEROP = 0xA005; + + + int TAG_SOFTWARE = 305; + int TAG_DATE_TIME = 306; + int TAG_ARTIST = 315; + int TAG_COPYRIGHT = 33432; +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java index 8773fc48..9c046911 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCEntry.java @@ -10,7 +10,18 @@ import com.twelvemonkeys.imageio.metadata.AbstractEntry; * @version $Id: IPTCEntry.java,v 1.0 Nov 13, 2009 8:57:04 PM haraldk Exp$ */ class IPTCEntry extends AbstractEntry { - public IPTCEntry(int pTagId, Object pValue) { + public IPTCEntry(final int pTagId, final Object pValue) { super(pTagId, pValue); } + + @Override + public String getFieldName() { + switch ((Integer) getIdentifier()) { + case IPTC.TAG_SOURCE: + return "Source"; + // TODO: More tags... + } + + return null; + } } diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java index 863f039b..eea40917 100644 --- a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/iptc/IPTCReader.java @@ -24,7 +24,7 @@ import java.util.List; * @author last modified by $Author: haraldk$ * @version $Id: IPTCReader.java,v 1.0 Nov 13, 2009 8:37:23 PM haraldk Exp$ */ -public class IPTCReader extends MetadataReader { +public final class IPTCReader extends MetadataReader { private static final int ENCODING_UNKNOWN = -1; private static final int ENCODING_UNSPECIFIED = 0; private static final int ENCODING_UTF_8 = 0x1b2547; @@ -38,10 +38,10 @@ public class IPTCReader extends MetadataReader { // 0x1c identifies start of a tag while (pInput.read() == 0x1c) { - int tagId = pInput.readShort(); + short tagId = pInput.readShort(); int tagByteCount = pInput.readUnsignedShort(); - Entry entry = readEntry(pInput, tagId, tagByteCount); + if (entry != null) { entries.add(entry); } @@ -50,7 +50,7 @@ public class IPTCReader extends MetadataReader { return new IPTCDirectory(entries); } - private Entry readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException { + private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException { Object value = null; switch (pTagId) { @@ -63,30 +63,6 @@ public class IPTCReader extends MetadataReader { // A single unsigned short value value = pInput.readUnsignedShort(); break; -// case IPTC.TAG_RELEASE_DATE: -// case IPTC.TAG_EXPIRATION_DATE: -// case IPTC.TAG_REFERENCE_DATE: -// case IPTC.TAG_DATE_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_DATE: -// // Date object -// Date date = parseISO8601DatePart(pInput, tagByteCount); -// if (date != null) { -// directory.setDate(tagIdentifier, date); -// return; -// } -// case IPTC.TAG_RELEASE_TIME: -// case IPTC.TAG_EXPIRATION_TIME: -// case IPTC.TAG_TIME_CREATED: -// case IPTC.TAG_DIGITAL_CREATION_TIME: -// // NOTE: Spec says fields should be sent in order, so this is okay -// date = getDateForTime(directory, tagIdentifier); -// -// Date time = parseISO8601TimePart(pInput, tagByteCount, date); -// if (time != null) { -// directory.setDate(tagIdentifier, time); -// return; -// } -// default: // Skip non-Application fields, as they are typically not human readable if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { @@ -100,7 +76,7 @@ public class IPTCReader extends MetadataReader { // If we don't have a value, treat it as a string if (value == null) { if (pLength < 1) { - value = "(No value)"; + value = null; } else { value = parseString(pInput, pLength); diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java new file mode 100644 index 00000000..b67d24ab --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMP.java @@ -0,0 +1,36 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import java.util.Collections; +import java.util.Map; + +/** + * XMP + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMP.java,v 1.0 Nov 12, 2009 12:19:32 AM haraldk Exp$ + * + * @see Extensible Metadata Platform (XMP) + */ +public interface XMP { + /** W3C Resource Description Format namespace */ + String NS_RDF = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; + + /** Dublin Core Metadata Initiative namespace */ + String NS_DC = "http://purl.org/dc/elements/1.1/"; + + String NS_EXIF = "http://ns.adobe.com/exif/1.0/"; + + String NS_PHOTOSHOP = "http://ns.adobe.com/photoshop/1.0/"; + + String NS_ST_REF = "http://ns.adobe.com/xap/1.0/sType/ResourceRef#"; + + String NS_TIFF = "http://ns.adobe.com/tiff/1.0/"; + + String NS_XAP = "http://ns.adobe.com/xap/1.0/"; + + String NS_XAP_MM = "http://ns.adobe.com/xap/1.0/mm/"; + + /** Contains the mapping from URI to default namespace prefix. */ + Map DEFAULT_NS_MAPPING = Collections.unmodifiableMap(new XMPNamespaceMapping()); +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java new file mode 100644 index 00000000..b79ad8d1 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPDirectory.java @@ -0,0 +1,23 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.AbstractDirectory; +import com.twelvemonkeys.imageio.metadata.Entry; + +import java.util.List; + +/** +* XMPDirectory +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: XMPDirectory.java,v 1.0 Nov 17, 2009 9:38:58 PM haraldk Exp$ +*/ +final class XMPDirectory extends AbstractDirectory { + // TODO: Store size of root directory, to allow serializing + // TODO: XMPDirectory, maybe not even an AbstractDirectory + // - Keeping the Document would allow for easier serialization + // TODO: Or use direct SAX parsing + public XMPDirectory(List pEntries) { + super(pEntries); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java new file mode 100644 index 00000000..4ef515ff --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPEntry.java @@ -0,0 +1,29 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.AbstractEntry; + +/** +* XMPEntry +* +* @author Harald Kuhr +* @author last modified by $Author: haraldk$ +* @version $Id: XMPEntry.java,v 1.0 Nov 17, 2009 9:38:39 PM haraldk Exp$ +*/ +final class XMPEntry extends AbstractEntry { + private final String mFieldName; + + public XMPEntry(final String pIdentifier, final Object pValue) { + this(pIdentifier, null, pValue); + } + + public XMPEntry(final String pIdentifier, final String pFieldName, final Object pValue) { + super(pIdentifier, pValue); + mFieldName = pFieldName; + } + + @SuppressWarnings({"SuspiciousMethodCalls"}) + @Override + public String getFieldName() { + return mFieldName != null ? mFieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier()); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java new file mode 100644 index 00000000..bc0077be --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPNamespaceMapping.java @@ -0,0 +1,23 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import java.util.HashMap; + +/** + * XMPNamespaceMapping + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPNamespaceMapping.java,v 1.0 Nov 17, 2009 6:35:21 PM haraldk Exp$ + */ +final class XMPNamespaceMapping extends HashMap { + public XMPNamespaceMapping() { + put(XMP.NS_RDF, "rdf"); + put(XMP.NS_DC, "dc"); + put(XMP.NS_EXIF, "exif"); + put(XMP.NS_PHOTOSHOP, "photoshop"); + put(XMP.NS_ST_REF, "stRef"); + put(XMP.NS_TIFF, "tiff"); + put(XMP.NS_XAP, "xap"); + put(XMP.NS_XAP_MM, "xapMM"); + } +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java new file mode 100644 index 00000000..fff9de38 --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPReader.java @@ -0,0 +1,195 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.metadata.*; +import com.twelvemonkeys.imageio.util.IIOUtil; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +import javax.imageio.IIOException; +import javax.imageio.stream.ImageInputStream; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.util.*; + +/** + * XMPReader + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPReader.java,v 1.0 Nov 14, 2009 11:04:30 PM haraldk Exp$ + */ +public final class XMPReader extends MetadataReader { + @Override + public Directory read(final ImageInputStream pInput) throws IOException { + pInput.mark(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(IIOUtil.createStreamAdapter(pInput), Charset.forName("UTF-8"))); + String line; + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + + pInput.reset(); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + + try { + // TODO: Consider parsing using SAX? + // TODO: Determine encoding and parse using a Reader... + // TODO: Refactor scanner to return inputstream? + DocumentBuilder builder = factory.newDocumentBuilder(); + Document document = builder.parse(new InputSource(IIOUtil.createStreamAdapter(pInput))); + +// XMLSerializer serializer = new XMLSerializer(System.err, System.getProperty("file.encoding")); +// serializer.serialize(document); + + + // Each rdf:Description is a Directory (but we can't really rely on that structure.. it's only convention) + // - Each element inside the rdf:Desc is an Entry + + Node rdfRoot = document.getElementsByTagNameNS(XMP.NS_RDF, "RDF").item(0); + NodeList descriptions = document.getElementsByTagNameNS(XMP.NS_RDF, "Description"); + + return parseDirectories(rdfRoot, descriptions); + } + catch (SAXException e) { + throw new IIOException(e.getMessage(), e); + } + catch (ParserConfigurationException e) { + throw new RuntimeException(e); // TODO: Or IOException? + } + } + + // TODO: Consider using namespace-prefix in tags/identifiers and qName as field only!? + + private XMPDirectory parseDirectories(final Node pParentNode, NodeList pNodes) { + Map> subdirs = new LinkedHashMap>(); + + for (Node desc : asIterable(pNodes)) { + if (desc.getParentNode() != pParentNode) { + continue; + } + + for (Node node : asIterable(desc.getChildNodes())) { + if (node.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + // Lookup + List dir = subdirs.get(node.getNamespaceURI()); + if (dir == null) { + dir = new ArrayList(); + subdirs.put(node.getNamespaceURI(), dir); + } + + Object value; + + Node parseType = node.getAttributes().getNamedItemNS(XMP.NS_RDF, "parseType"); + if (parseType != null && "Resource".equals(parseType.getNodeValue())) { + List entries = new ArrayList(); + + for (Node child : asIterable(node.getChildNodes())) { + if (child.getNodeType() != Node.ELEMENT_NODE) { + continue; + } + + // TODO: Preserve the stRef namespace here.. + entries.add(new XMPEntry(child.getNamespaceURI() + child.getLocalName(), child.getLocalName(), getChildTextValue(child))); + } + value = new XMPDirectory(entries); + } + else { + // TODO: Support alternative RDF syntax (short-form), using attributes on desc +// NamedNodeMap attributes = node.getAttributes(); +// +// for (Node attr : asIterable(attributes)) { +// System.out.println("attr.getNodeName(): " + attr.getNodeName()); +// System.out.println("attr.getNodeValue(): " + attr.getNodeValue()); +// } + + value = getChildTextValue(node); + } + + // TODO: Preserve namespace (without URI?) here.. + XMPEntry entry = new XMPEntry(node.getNamespaceURI() + node.getLocalName(), node.getLocalName(), value); + dir.add(entry); + } + } + + List entries = new ArrayList(); + + for (Map.Entry> entry : subdirs.entrySet()) { + entries.add(new XMPEntry(entry.getKey(), new XMPDirectory(entry.getValue()))); + } + + return new XMPDirectory(entries); + } + + private Object getChildTextValue(Node node) { + Object value; + Node child = node.getFirstChild(); + + String strVal = null; + if (child != null) { + strVal = child.getNodeValue(); + } + + value = strVal != null ? strVal.trim() : ""; + return value; + } + + private Iterable asIterable(final NamedNodeMap pNodeList) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private int mIndex; + + public boolean hasNext() { + return pNodeList != null && pNodeList.getLength() > mIndex; + } + + public Node next() { + return pNodeList.item(mIndex++); + } + + public void remove() { + throw new UnsupportedOperationException("Method remove not supported"); + } + }; + } + }; + } + + private Iterable asIterable(final NodeList pNodeList) { + return new Iterable() { + public Iterator iterator() { + return new Iterator() { + private int mIndex; + + public boolean hasNext() { + return pNodeList != null && pNodeList.getLength() > mIndex; + } + + public Node next() { + return pNodeList.item(mIndex++); + } + + public void remove() { + throw new UnsupportedOperationException("Method remove not supported"); + } + }; + } + }; + } + +} diff --git a/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java new file mode 100644 index 00000000..8e9f6a7c --- /dev/null +++ b/twelvemonkeys-imageio/metadata/src/main/java/com/twelvemonkeys/imageio/metadata/xmp/XMPScanner.java @@ -0,0 +1,215 @@ +package com.twelvemonkeys.imageio.metadata.xmp; + +import com.twelvemonkeys.imageio.stream.BufferedImageInputStream; +import com.twelvemonkeys.imageio.util.IIOUtil; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import java.io.*; +import java.nio.charset.Charset; + +/** + * XMPScanner + * + * @author Harald Kuhr + * @author last modified by $Author: haraldk$ + * @version $Id: XMPScanner.java,v 1.0 Nov 11, 2009 4:49:00 PM haraldk Exp$ + */ +public final class XMPScanner { + /** + * {@code <?xpacket begin=} + *

+ *

    + *
  • + * 8-bit (UTF-8): + * 0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20 + * 0x62 0x65 0x67 0x69 0x6E 0x3D + *
  • + *
  • 16-bit encoding (UCS-2, UTF-16): (either big- or little-endian order) + * 0x3C 0x00 0x3F 0x00 0x78 0x00 0x70 0x00 0x61 0x00 + * 0x63 0x00 0x6B 0x00 0x65 0x00 0x74 0x00 0x20 0x00 0x62 0x00 + * 0x65 0x00 0x67 0x00 0x69 0x00 0x6E 0x00 0x3D [0x00] + *
  • + *
  • 32-bit encoding (UCS-4): + * As 16 bit UCS2, with three 0x00 instead of one.
  • + *
+ */ + private static final byte[] XMP_PACKET_BEGIN = { + 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, + 0x62, 0x65, 0x67, 0x69, 0x6E, 0x3D + }; + + /** + * {@code <?xpacket end=} + */ + private static final byte[] XMP_PACKET_END = { + 0x3C, 0x3F, 0x78, 0x70, 0x61, 0x63, 0x6B, 0x65, 0x74, 0x20, + 0x65, 0x6E, 0x64, 0x3D + }; + + /** + * Scans the given input for an XML metadata packet. + * The scanning process involves reading every byte in the file, while searching for an XMP packet. + * This process is very inefficient, compared to reading a known file format. + *

+ * NOTE: The XMP Specification says this method of reading an XMP packet + * should be considered a last resort.
+ * This is because files may contain multiple XMP packets, some which may be related to embedded resources, + * some which may be obsolete (or even incomplete). + * + * @param pInput the input to scan. The input may be an {@link javax.imageio.stream.ImageInputStream} or + * any object that can be passed to {@link ImageIO#createImageInputStream(Object)}. + * Typically this may be a {@link File}, {@link InputStream} or {@link java.io.RandomAccessFile}. + * + * @return a character Reader + * + * @throws java.nio.charset.UnsupportedCharsetException if the encoding specified within the BOM is not supported + * by the JRE. + * @throws IOException if an I/O exception occurs reading from {@code pInput}. + * @see ImageIO#createImageInputStream(Object) + */ + static public Reader scanForXMPPacket(final Object pInput) throws IOException { + ImageInputStream stream = pInput instanceof ImageInputStream ? (ImageInputStream) pInput : ImageIO.createImageInputStream(pInput); + + // TODO: Consider if BufferedIIS is a good idea + if (!(stream instanceof BufferedImageInputStream)) { + stream = new BufferedImageInputStream(stream); + } + + // TODO: Might be more than one XMP block per file (it's possible to re-start for now).. + long pos; + pos = scanForSequence(stream, XMP_PACKET_BEGIN); + + if (pos >= 0) { + // Skip ' OR " (plus possible nulls for 16/32 bit) + byte quote = stream.readByte(); + + if (quote == '\'' || quote == '"') { + Charset cs = null; + + // Read BOM + byte[] bom = new byte[4]; + stream.readFully(bom); + + // NOTE: Empty string should be treated as UTF-8 for backwards compatibility + if (bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF && bom[3] == quote || + bom[0] == quote) { + // UTF-8 + cs = Charset.forName("UTF-8"); + } + else if (bom[0] == (byte) 0xFE && bom[1] == (byte) 0xFF && bom[2] == 0x00 && bom[3] == quote) { + // UTF-16 BIG endian + cs = Charset.forName("UTF-16BE"); + } + else if (bom[0] == 0x00 && bom[1] == (byte) 0xFF && bom[2] == (byte) 0xFE && bom[3] == quote) { + stream.skipBytes(1); // Alignment + + // UTF-16 little endian + cs = Charset.forName("UTF-16LE"); + } + else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == (byte) 0xFE && bom[3] == (byte) 0xFF) { + // NOTE: 32-bit character set not supported by default + // UTF 32 BIG endian + cs = Charset.forName("UTF-32BE"); + } + else if (bom[0] == (byte) 0xFF && bom[1] == (byte) 0xFE && bom[2] == 0x00 && bom[3] == 0x00) { + // TODO: FixMe.. + // NOTE: 32-bit character set not supported by default + // UTF 32 little endian + cs = Charset.forName("UTF-32LE"); + } + + if (cs != null) { + // Read all bytes until + while (reader.read() != '>') { + } + + // Return reader? + // How to decide between w or r?! + return reader; + } + } + } + + return null; + } + + /** + * Scans for a given ASCII sequence. + * + * @param pStream the stream to scan + * @param pSequence the byte sequence to search for + * + * @return the start position of the given sequence. + * + * @throws IOException if an I/O exception occurs during scanning + */ + private static long scanForSequence(final ImageInputStream pStream, final byte[] pSequence) throws IOException { + long start = -1l; + + int index = 0; + int nullBytes = 0; + + for (int read; (read = pStream.read()) >= 0;) { + if (pSequence[index] == (byte) read) { + // If this is the first byte in the sequence, store position + if (start == -1) { + start = pStream.getStreamPosition() - 1; + } + + // Inside the sequence, there might be 1 or 3 null bytes, depending on 16/32 byte encoding + if (nullBytes == 1 || nullBytes == 3) { + pStream.skipBytes(nullBytes); + } + + index++; + + // If we found the entire sequence, we're done, return start position + if (index == pSequence.length) { + return start; + } + } + else if (index == 1 && read == 0 && nullBytes < 3) { + // Skip 1 or 3 null bytes for 16/32 bit encoding + nullBytes++; + } + else if (index != 0) { + // Start over + index = 0; + start = -1; + nullBytes = 0; + } + } + + return -1l; + } + + //static public XMPDirectory parse(input); + + public static void main(final String[] pArgs) throws IOException { + ImageInputStream stream = ImageIO.createImageInputStream(new File(pArgs[0])); + + Reader xmp; + while ((xmp = scanForXMPPacket(stream)) != null) { + BufferedReader reader = new BufferedReader(xmp); + String line; + + while ((line = reader.readLine()) != null) { + System.out.println(line); + } + } + + stream.close(); +// else { +// System.err.println("XMP not found"); +// } + } +} diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java index 5ba2d84a..4859fa96 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/AbstractMetadata.java @@ -15,6 +15,7 @@ import java.util.Arrays; * @version $Id: AbstractMetadata.java,v 1.0 Nov 13, 2009 1:02:12 AM haraldk Exp$ */ abstract class AbstractMetadata extends IIOMetadata implements Cloneable { + // TODO: Move to core... protected AbstractMetadata(final boolean pStandardFormatSupported, final String pNativeFormatName, final String pNativeFormatClassName, diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java index 276d7e07..d18c80ea 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDMetadata.java @@ -2,15 +2,13 @@ package com.twelvemonkeys.imageio.plugins.psd; import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Entry; +import com.twelvemonkeys.imageio.metadata.exif.TIFF; +import com.twelvemonkeys.imageio.metadata.iptc.IPTC; import com.twelvemonkeys.lang.StringUtil; import com.twelvemonkeys.util.FilterIterator; -import org.w3c.dom.Document; import org.w3c.dom.Node; -import org.xml.sax.InputSource; import javax.imageio.metadata.IIOMetadataNode; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; import java.awt.image.IndexColorModel; import java.util.Arrays; import java.util.Iterator; @@ -251,72 +249,36 @@ public final class PSDMetadata extends AbstractMetadata { } else if (imageResource instanceof PSDIPTCData) { // TODO: Revise/rethink this... - // Transcode to XMP? ;-) PSDIPTCData iptc = (PSDIPTCData) imageResource; - node = new IIOMetadataNode("Directory"); + node = new IIOMetadataNode("DirectoryResource"); node.setAttribute("type", "IPTC"); node.setUserObject(iptc.mDirectory); - for (Entry entry : iptc.mDirectory) { - IIOMetadataNode tag = new IIOMetadataNode("Entry"); - tag.setAttribute("tag", String.format("%d:%02d", (Integer) entry.getIdentifier() >> 8, (Integer) entry.getIdentifier() & 0xff)); - - String field = entry.getFieldName(); - if (field != null) { - tag.setAttribute("field", String.format("%s", field)); - } - tag.setAttribute("value", entry.getValueAsString()); - - String type = entry.getTypeName(); - if (type != null) { - tag.setAttribute("type", type); - } - node.appendChild(tag); - } + appendEntries(node, "IPTC", iptc.mDirectory); } else if (imageResource instanceof PSDEXIF1Data) { // TODO: Revise/rethink this... - // Transcode to XMP? ;-) PSDEXIF1Data exif = (PSDEXIF1Data) imageResource; - node = new IIOMetadataNode("Directory"); + node = new IIOMetadataNode("DirectoryResource"); node.setAttribute("type", "EXIF"); // TODO: Set byte[] data instead node.setUserObject(exif.mDirectory); - appendEntries(node, exif.mDirectory); + appendEntries(node, "EXIF", exif.mDirectory); } else if (imageResource instanceof PSDXMPData) { // TODO: Revise/rethink this... Would it be possible to parse XMP as IIOMetadataNodes? Or is that just stupid... // Or maybe use the Directory approach used by IPTC and EXIF.. PSDXMPData xmp = (PSDXMPData) imageResource; - node = new IIOMetadataNode("XMP"); + node = new IIOMetadataNode("DirectoryResource"); + node.setAttribute("type", "XMP"); + appendEntries(node, "XMP", xmp.mDirectory); - try { -// BufferedReader reader = new BufferedReader(xmp.getData()); -// String line; -// while ((line = reader.readLine()) != null) { -// System.out.println(line); -// } -// - DocumentBuilder builder; - Document document; - - DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); - factory.setNamespaceAware(true); - builder = factory.newDocumentBuilder(); - document = builder.parse(new InputSource(xmp.getData())); - - - // Set the entire XMP document as user data - node.setUserObject(document); -// node.appendChild(document.getFirstChild()); - } - catch (Exception e) { - e.printStackTrace(); - } + // Set the entire XMP document as user data + node.setUserObject(xmp.mData); } else { // Generic resource.. @@ -342,18 +304,25 @@ public final class PSDMetadata extends AbstractMetadata { return resource; } - private void appendEntries(IIOMetadataNode pNode, final Directory pDirectory) { + private void appendEntries(final IIOMetadataNode pNode, final String pType, final Directory pDirectory) { for (Entry entry : pDirectory) { + Object tagId = entry.getIdentifier(); + IIOMetadataNode tag = new IIOMetadataNode("Entry"); - tag.setAttribute("tag", String.format("%s", entry.getIdentifier())); + tag.setAttribute("tag", String.format("%s", tagId)); String field = entry.getFieldName(); if (field != null) { tag.setAttribute("field", String.format("%s", field)); } + else { + if ("IPTC".equals(pType)) { + tag.setAttribute("field", String.format("%s:%s", (Integer) tagId >> 8, (Integer) tagId & 0xff)); + } + } if (entry.getValue() instanceof Directory) { - appendEntries(tag, (Directory) entry.getValue()); + appendEntries(tag, pType, (Directory) entry.getValue()); tag.setAttribute("type", "Directory"); } else { @@ -614,7 +583,7 @@ public final class PSDMetadata extends AbstractMetadata { PSDEXIF1Data data = exif.next(); // Get the EXIF DateTime (aka ModifyDate) tag if present - Entry dateTime = data.mDirectory.getEntryById(0x0132); // TODO: Constant + Entry dateTime = data.mDirectory.getEntryById(TIFF.TAG_DATE_TIME); if (dateTime != null) { node = new IIOMetadataNode("ImageCreationTime"); // As TIFF, but could just as well be ImageModificationTime // Format: "YYYY:MM:DD hh:mm:ss" @@ -642,7 +611,7 @@ public final class PSDMetadata extends AbstractMetadata { // Example: TIFF Software field => /Text/TextEntry@keyword = "Software", // /Text/TextEntry@value = Name and version number of the software package(s) used to create the image. - Iterator textResources = getResources(PSDEXIF1Data.class, PSDIPTCData.class, PSDXMPData.class); + Iterator textResources = getResources(PSD.RES_IPTC_NAA, PSD.RES_EXIF_DATA_1, PSD.RES_XMP_DATA); if (!textResources.hasNext()) { return null; @@ -660,24 +629,40 @@ public final class PSDMetadata extends AbstractMetadata { if (textResource instanceof PSDIPTCData) { PSDIPTCData iptc = (PSDIPTCData) textResource; - for (Entry entry : iptc.mDirectory) { - node = new IIOMetadataNode("TextEntry"); + appendTextEntriesFlat(text, iptc.mDirectory, new FilterIterator.Filter() { + public boolean accept(final Entry pEntry) { + Integer tagId = (Integer) pEntry.getIdentifier(); - if (entry.getValue() instanceof String) { - node.setAttribute("keyword", String.format("%s", entry.getFieldName())); - node.setAttribute("value", entry.getValueAsString()); - text.appendChild(node); + switch (tagId) { + case IPTC.TAG_SOURCE: + return true; + default: + return false; + } } - } + }); } else if (textResource instanceof PSDEXIF1Data) { PSDEXIF1Data exif = (PSDEXIF1Data) textResource; - // TODO: Use name? - appendTextEntriesFlat(text, exif.mDirectory); + appendTextEntriesFlat(text, exif.mDirectory, new FilterIterator.Filter() { + public boolean accept(final Entry pEntry) { + Integer tagId = (Integer) pEntry.getIdentifier(); + + switch (tagId) { + case TIFF.TAG_SOFTWARE: + case TIFF.TAG_ARTIST: + case TIFF.TAG_COPYRIGHT: + return true; + default: + return false; + } + } + }); } else if (textResource instanceof PSDXMPData) { // TODO: Parse XMP (heavy) ONLY if we don't have required fields from IPTC/EXIF? + // TODO: Use XMP IPTC/EXIF/TIFFF NativeDigest field to validate if the values are in sync... PSDXMPData xmp = (PSDXMPData) textResource; } } @@ -685,15 +670,26 @@ public final class PSDMetadata extends AbstractMetadata { return text; } - private void appendTextEntriesFlat(IIOMetadataNode pNode, Directory pDirectory) { - for (Entry entry : pDirectory) { + private void appendTextEntriesFlat(final IIOMetadataNode pNode, final Directory pDirectory, final FilterIterator.Filter pFilter) { + FilterIterator pEntries = new FilterIterator(pDirectory.iterator(), pFilter); + while (pEntries.hasNext()) { + Entry entry = pEntries.next(); + if (entry.getValue() instanceof Directory) { - appendTextEntriesFlat(pNode, (Directory) entry.getValue()); + appendTextEntriesFlat(pNode, (Directory) entry.getValue(), pFilter); } else if (entry.getValue() instanceof String) { IIOMetadataNode tag = new IIOMetadataNode("TextEntry"); - // TODO: Use name! - tag.setAttribute("keyword", String.format("%s", entry.getFieldName())); + String fieldName = entry.getFieldName(); + + if (fieldName != null) { + tag.setAttribute("keyword", String.format("%s", fieldName)); + } + else { + // TODO: This should never happen, as we filter out only specific nodes + tag.setAttribute("keyword", String.format("%s", entry.getIdentifier())); + } + tag.setAttribute("value", entry.getValueAsString()); pNode.appendChild(tag); } @@ -734,13 +730,13 @@ public final class PSDMetadata extends AbstractMetadata { }); } - Iterator getResources(final Class... pResourceTypes) { + Iterator getResources(final int... pResourceTypes) { Iterator iterator = mImageResources.iterator(); return new FilterIterator(iterator, new FilterIterator.Filter() { - public boolean accept(final PSDImageResource pElement) { - for (Class type : pResourceTypes) { - if (type.isInstance(pElement)) { + public boolean accept(final PSDImageResource pResource) { + for (int type : pResourceTypes) { + if (type == pResource.mId) { return true; } } diff --git a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java index 7ba689e1..605e78ef 100644 --- a/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java +++ b/twelvemonkeys-imageio/psd/src/main/java/com/twelvemonkeys/imageio/plugins/psd/PSDXMPData.java @@ -1,5 +1,7 @@ package com.twelvemonkeys.imageio.plugins.psd; +import com.twelvemonkeys.imageio.metadata.Directory; +import com.twelvemonkeys.imageio.metadata.xmp.XMPReader; import com.twelvemonkeys.lang.StringUtil; import javax.imageio.stream.ImageInputStream; @@ -21,6 +23,7 @@ import java.nio.charset.Charset; */ final class PSDXMPData extends PSDImageResource { protected byte[] mData; + Directory mDirectory; PSDXMPData(final short pId, final ImageInputStream pInput) throws IOException { super(pId, pInput); @@ -29,7 +32,9 @@ final class PSDXMPData extends PSDImageResource { @Override protected void readData(final ImageInputStream pInput) throws IOException { mData = new byte[(int) mSize]; // TODO: Fix potential overflow, or document why that can't happen (read spec) - pInput.readFully(mData); + //pInput.readFully(mData); + + mDirectory = new XMPReader().read(pInput); } @Override