mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
Work in progress for PSD metadata support:
- Implemented XMP Reader, Directory and Entry - More EXIF and IPTC changes - Cleaning up
This commit is contained in:
parent
d24c2c1b08
commit
64b21b83bb
@ -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<Entry> mEntries = new ArrayList<Entry>();
|
||||
|
||||
protected AbstractDirectory(final Collection<? extends Entry> 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();
|
||||
|
||||
|
@ -23,7 +23,7 @@ public abstract class AbstractEntry implements Entry {
|
||||
mValue = pValue;
|
||||
}
|
||||
|
||||
public Object getIdentifier() {
|
||||
public final Object getIdentifier() {
|
||||
return mIdentifier;
|
||||
}
|
||||
|
||||
|
@ -12,7 +12,7 @@ public interface Directory extends Iterable<Entry> {
|
||||
// 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<Entry> getBestEntries(Object pIdentifier, Object pQualifier, String pLanguage);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -0,0 +1,53 @@
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
/**
|
||||
* TIFF
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -0,0 +1,36 @@
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* XMP
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: XMP.java,v 1.0 Nov 12, 2009 12:19:32 AM haraldk Exp$
|
||||
*
|
||||
* @see <a href="http://www.adobe.com/products/xmp/">Extensible Metadata Platform (XMP)</a>
|
||||
*/
|
||||
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<String, String> DEFAULT_NS_MAPPING = Collections.unmodifiableMap(new XMPNamespaceMapping());
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<Entry> pEntries) {
|
||||
super(pEntries);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
|
||||
/**
|
||||
* XMPEntry
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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());
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.twelvemonkeys.imageio.metadata.xmp;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
/**
|
||||
* XMPNamespaceMapping
|
||||
*
|
||||
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<String, String> {
|
||||
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");
|
||||
}
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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<String, List<Entry>> subdirs = new LinkedHashMap<String, List<Entry>>();
|
||||
|
||||
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<Entry> dir = subdirs.get(node.getNamespaceURI());
|
||||
if (dir == null) {
|
||||
dir = new ArrayList<Entry>();
|
||||
subdirs.put(node.getNamespaceURI(), dir);
|
||||
}
|
||||
|
||||
Object value;
|
||||
|
||||
Node parseType = node.getAttributes().getNamedItemNS(XMP.NS_RDF, "parseType");
|
||||
if (parseType != null && "Resource".equals(parseType.getNodeValue())) {
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
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<Entry> entries = new ArrayList<Entry>();
|
||||
|
||||
for (Map.Entry<String, List<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<? extends Node> asIterable(final NamedNodeMap pNodeList) {
|
||||
return new Iterable<Node>() {
|
||||
public Iterator<Node> iterator() {
|
||||
return new Iterator<Node>() {
|
||||
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<? extends Node> asIterable(final NodeList pNodeList) {
|
||||
return new Iterable<Node>() {
|
||||
public Iterator<Node> iterator() {
|
||||
return new Iterator<Node>() {
|
||||
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");
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
@ -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 <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
|
||||
* @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=}
|
||||
* <p/>
|
||||
* <ul>
|
||||
* <li>
|
||||
* 8-bit (UTF-8):
|
||||
* 0x3C 0x3F 0x78 0x70 0x61 0x63 0x6B 0x65 0x74 0x20
|
||||
* 0x62 0x65 0x67 0x69 0x6E 0x3D
|
||||
* </li>
|
||||
* <li>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]
|
||||
* </li>
|
||||
* <li>32-bit encoding (UCS-4):
|
||||
* As 16 bit UCS2, with three 0x00 instead of one.</li>
|
||||
* </ul>
|
||||
*/
|
||||
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.
|
||||
* <p/>
|
||||
* <em>NOTE: The XMP Specification says this method of reading an XMP packet
|
||||
* should be considered a last resort.</em><br/>
|
||||
* 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 <?xpacket end= up-front or filter stream
|
||||
stream.mark();
|
||||
long end = scanForSequence(stream, XMP_PACKET_END);
|
||||
stream.reset();
|
||||
|
||||
long length = end - stream.getStreamPosition();
|
||||
Reader reader = new InputStreamReader(IIOUtil.createStreamAdapter(stream, length), cs);
|
||||
|
||||
// Skip 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");
|
||||
// }
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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");
|
||||
|
||||
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()));
|
||||
|
||||
node = new IIOMetadataNode("DirectoryResource");
|
||||
node.setAttribute("type", "XMP");
|
||||
appendEntries(node, "XMP", xmp.mDirectory);
|
||||
|
||||
// Set the entire XMP document as user data
|
||||
node.setUserObject(document);
|
||||
// node.appendChild(document.getFirstChild());
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
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<PSDImageResource> textResources = getResources(PSDEXIF1Data.class, PSDIPTCData.class, PSDXMPData.class);
|
||||
Iterator<PSDImageResource> 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<Entry>() {
|
||||
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<Entry>() {
|
||||
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<Entry> pFilter) {
|
||||
FilterIterator<Entry> pEntries = new FilterIterator<Entry>(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<PSDImageResource> getResources(final Class<? extends PSDImageResource>... pResourceTypes) {
|
||||
Iterator<PSDImageResource> getResources(final int... pResourceTypes) {
|
||||
Iterator<PSDImageResource> iterator = mImageResources.iterator();
|
||||
|
||||
return new FilterIterator<PSDImageResource>(iterator, new FilterIterator.Filter<PSDImageResource>() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user