mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-03 11:35:29 -04:00
Work in progress for PSD metadata support:
- Cleaned up EXIF, IPTC and XMP metadata classes.
This commit is contained in:
parent
aad80d043f
commit
d24c2c1b08
@ -1,15 +1,10 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EXIF metadata.
|
* EXIF metadata.
|
||||||
@ -23,9 +18,7 @@ import java.util.List;
|
|||||||
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
* @see <a href="http://partners.adobe.com/public/developer/tiff/index.html">Adobe TIFF developer resources</a>
|
||||||
*/
|
*/
|
||||||
final class PSDEXIF1Data extends PSDImageResource {
|
final class PSDEXIF1Data extends PSDImageResource {
|
||||||
// protected byte[] mData;
|
protected Directory mDirectory;
|
||||||
// protected Directory mDirectory;
|
|
||||||
protected com.twelvemonkeys.imageio.metadata.Directory mDirectory;
|
|
||||||
|
|
||||||
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
PSDEXIF1Data(final short pId, final ImageInputStream pInput) throws IOException {
|
||||||
super(pId, pInput);
|
super(pId, pInput);
|
||||||
@ -34,320 +27,16 @@ final class PSDEXIF1Data extends PSDImageResource {
|
|||||||
@Override
|
@Override
|
||||||
protected void readData(final ImageInputStream pInput) throws IOException {
|
protected void readData(final ImageInputStream pInput) throws IOException {
|
||||||
// This is in essence an embedded TIFF file.
|
// This is in essence an embedded TIFF file.
|
||||||
// TODO: Extract TIFF parsing to more general purpose package
|
|
||||||
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
// TODO: Instead, read the byte data, store for later parsing (or better yet, store offset, and read on request)
|
||||||
mDirectory = new EXIFReader().read(pInput);
|
mDirectory = new EXIFReader().read(pInput);
|
||||||
// byte[] bom = new byte[2];
|
|
||||||
// stream.readFully(bom);
|
|
||||||
// if (bom[0] == 'I' && bom[1] == 'I') {
|
|
||||||
// stream.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")));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (stream.readUnsignedShort() != 42) {
|
|
||||||
// throw new IIOException("Wrong TIFF magic in EXIF data.");
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// long directoryOffset = stream.readUnsignedInt();
|
|
||||||
//
|
|
||||||
// // Read TIFF directory
|
|
||||||
// mDirectory = Directory.read(stream, directoryOffset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder builder = toStringBuilder();
|
StringBuilder builder = toStringBuilder();
|
||||||
|
|
||||||
builder.append(", ").append(mDirectory);
|
builder.append(", ").append(mDirectory);
|
||||||
|
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TIFF Image file directory (IFD)
|
|
||||||
static class Directory implements Iterable<Entry> {
|
|
||||||
private List<Entry> mEntries = new ArrayList<Entry>();
|
|
||||||
|
|
||||||
private Directory() {}
|
|
||||||
|
|
||||||
public static Directory read(final ImageInputStream pInput, final long pOffset) throws IOException {
|
|
||||||
Directory directory = new Directory();
|
|
||||||
|
|
||||||
pInput.seek(pOffset);
|
|
||||||
int entryCount = pInput.readUnsignedShort();
|
|
||||||
|
|
||||||
for (int i = 0; i < entryCount; i++) {
|
|
||||||
directory.mEntries.add(Entry.read(pInput));
|
|
||||||
}
|
|
||||||
|
|
||||||
long nextOffset = pInput.readUnsignedInt();
|
|
||||||
|
|
||||||
if (nextOffset != 0) {
|
|
||||||
Directory next = Directory.read(pInput, nextOffset);
|
|
||||||
directory.mEntries.addAll(next.mEntries);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry get(int pTagId) {
|
|
||||||
for (Entry entry : mEntries) {
|
|
||||||
if (entry.mTagId == pTagId) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<Entry> iterator() {
|
|
||||||
return mEntries.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("Directory%s", mEntries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TIFF IFD Entry
|
|
||||||
static class Entry {
|
|
||||||
private static final int EXIF_IFD = 0x8769;
|
|
||||||
|
|
||||||
private final static String[] TYPE_NAMES = {
|
|
||||||
"BYTE", "ASCII", "SHORT", "LONG", "RATIONAL",
|
|
||||||
|
|
||||||
"SBYTE", "UNDEFINED", "SSHORT", "SLONG", "SRATIONAL", "FLOAT", "DOUBLE",
|
|
||||||
};
|
|
||||||
|
|
||||||
private final static int[] TYPE_LENGTHS = {
|
|
||||||
1, 1, 2, 4, 8,
|
|
||||||
|
|
||||||
1, 1, 2, 4, 8, 4, 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
final int mTagId;
|
|
||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
private short mType;
|
|
||||||
private int mCount;
|
|
||||||
private long mValueOffset;
|
|
||||||
private Object mValue;
|
|
||||||
|
|
||||||
private Entry(int pTagId) {
|
|
||||||
mTagId = pTagId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Entry read(final ImageInputStream pInput) throws IOException {
|
|
||||||
Entry entry = new Entry(pInput.readUnsignedShort());
|
|
||||||
|
|
||||||
entry.mType = pInput.readShort();
|
|
||||||
entry.mCount = pInput.readInt(); // Number of values
|
|
||||||
|
|
||||||
// TODO: Handle other sub-IFDs
|
|
||||||
// GPS IFD: 0x8825, Interoperability IFD: 0xA005
|
|
||||||
if (entry.mTagId == EXIF_IFD) {
|
|
||||||
long offset = pInput.readUnsignedInt();
|
|
||||||
pInput.mark();
|
|
||||||
|
|
||||||
try {
|
|
||||||
entry.mValue = Directory.read(pInput, offset);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
pInput.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
int valueLength = entry.getValueLength();
|
|
||||||
|
|
||||||
if (valueLength > 0 && valueLength <= 4) {
|
|
||||||
entry.readValueInLine(pInput);
|
|
||||||
pInput.skipBytes(4 - valueLength);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
entry.mValueOffset = pInput.readUnsignedInt(); // This is the *value* iff the value size is <= 4 bytes
|
|
||||||
entry.readValue(pInput);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readValue(final ImageInputStream pInput) throws IOException {
|
|
||||||
long pos = pInput.getStreamPosition();
|
|
||||||
try {
|
|
||||||
pInput.seek(mValueOffset);
|
|
||||||
readValueInLine(pInput);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
pInput.seek(pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readValueInLine(ImageInputStream pInput) throws IOException {
|
|
||||||
mValue = readValueDirect(pInput, mType, mCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Object readValueDirect(final ImageInputStream pInput, final short pType, final int pCount) throws IOException {
|
|
||||||
switch (pType) {
|
|
||||||
case 2:
|
|
||||||
// TODO: This might be UTF-8 or ISO-8859-1, even though against the spec
|
|
||||||
byte[] ascii = new byte[pCount];
|
|
||||||
pInput.readFully(ascii);
|
|
||||||
return StringUtil.decode(ascii, 0, ascii.length, "ASCII");
|
|
||||||
case 1:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedByte();
|
|
||||||
}
|
|
||||||
case 6:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readByte();
|
|
||||||
}
|
|
||||||
case 7:
|
|
||||||
byte[] bytes = new byte[pCount];
|
|
||||||
pInput.readFully(bytes);
|
|
||||||
return bytes;
|
|
||||||
case 3:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedShort();
|
|
||||||
}
|
|
||||||
case 8:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
short[] shorts = new short[pCount];
|
|
||||||
pInput.readFully(shorts, 0, shorts.length);
|
|
||||||
return shorts;
|
|
||||||
case 4:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
case 9:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
int[] ints = new int[pCount];
|
|
||||||
pInput.readFully(ints, 0, ints.length);
|
|
||||||
return ints;
|
|
||||||
case 11:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readFloat();
|
|
||||||
}
|
|
||||||
|
|
||||||
float[] floats = new float[pCount];
|
|
||||||
pInput.readFully(floats, 0, floats.length);
|
|
||||||
return floats;
|
|
||||||
case 12:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] doubles = new double[pCount];
|
|
||||||
pInput.readFully(doubles, 0, doubles.length);
|
|
||||||
return doubles;
|
|
||||||
|
|
||||||
// TODO: Consider using a Rational class
|
|
||||||
case 5:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] rationals = new double[pCount];
|
|
||||||
for (int i = 0; i < rationals.length; i++) {
|
|
||||||
rationals[i] = pInput.readUnsignedInt() / (double) pInput.readUnsignedInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return rationals;
|
|
||||||
case 10:
|
|
||||||
if (pCount == 1) {
|
|
||||||
return pInput.readInt() / (double) pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
double[] srationals = new double[pCount];
|
|
||||||
for (int i = 0; i < srationals.length; i++) {
|
|
||||||
srationals[i] = pInput.readInt() / (double) pInput.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
return srationals;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new IIOException(String.format("Unknown EXIF type '%s'", pType));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int getValueLength() {
|
|
||||||
if (mType > 0 && mType <= TYPE_LENGTHS.length) {
|
|
||||||
return TYPE_LENGTHS[mType - 1] * mCount;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String getTypeName() {
|
|
||||||
if (mType > 0 && mType <= TYPE_NAMES.length) {
|
|
||||||
return TYPE_NAMES[mType - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown type";
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Object getValue() {
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String getValueAsString() {
|
|
||||||
if (mValue instanceof String) {
|
|
||||||
return String.format("%s", mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mValue != null && mValue.getClass().isArray()) {
|
|
||||||
Class<?> type = mValue.getClass().getComponentType();
|
|
||||||
if (byte.class == type) {
|
|
||||||
return Arrays.toString((byte[]) mValue);
|
|
||||||
}
|
|
||||||
if (short.class == type) {
|
|
||||||
return Arrays.toString((short[]) mValue);
|
|
||||||
}
|
|
||||||
if (int.class == type) {
|
|
||||||
return Arrays.toString((int[]) mValue);
|
|
||||||
}
|
|
||||||
if (float.class == type) {
|
|
||||||
return Arrays.toString((float[]) mValue);
|
|
||||||
}
|
|
||||||
if (double.class == type) {
|
|
||||||
return Arrays.toString((double[]) mValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.valueOf(mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Tag names!
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("0x%04x: %s (%s, %d)", mTagId, mType == 2 ? String.format("\"%s\"", mValue) : getValueAsString(), getTypeName(), mCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,10 @@
|
|||||||
package com.twelvemonkeys.imageio.plugins.psd;
|
package com.twelvemonkeys.imageio.plugins.psd;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
import com.twelvemonkeys.imageio.metadata.iptc.IPTCReader;
|
||||||
import com.twelvemonkeys.lang.StringUtil;
|
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.nio.CharBuffer;
|
|
||||||
import java.nio.charset.CharacterCodingException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.nio.charset.CharsetDecoder;
|
|
||||||
import java.nio.charset.CodingErrorAction;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PSDIPTCData
|
* PSDIPTCData
|
||||||
@ -24,9 +14,7 @@ import java.util.List;
|
|||||||
* @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$
|
* @version $Id: PSDIPTCData.java,v 1.0 Nov 7, 2009 9:52:14 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
final class PSDIPTCData extends PSDImageResource {
|
final class PSDIPTCData extends PSDImageResource {
|
||||||
// TODO: Refactor to be more like PSDEXIF1Data...
|
Directory mDirectory;
|
||||||
// TODO: Extract IPTC/EXIF/XMP metadata extraction/parsing to separate module(s)
|
|
||||||
com.twelvemonkeys.imageio.metadata.Directory mDirectory;
|
|
||||||
|
|
||||||
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
PSDIPTCData(final short pId, final ImageInputStream pInput) throws IOException {
|
||||||
super(pId, pInput);
|
super(pId, pInput);
|
||||||
@ -43,379 +31,7 @@ final class PSDIPTCData extends PSDImageResource {
|
|||||||
StringBuilder builder = toStringBuilder();
|
StringBuilder builder = toStringBuilder();
|
||||||
builder.append(", ").append(mDirectory);
|
builder.append(", ").append(mDirectory);
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
|
|
||||||
return builder.toString();
|
return builder.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Entry {
|
|
||||||
final int mTagId;
|
|
||||||
private Object mValue;
|
|
||||||
|
|
||||||
public Entry(final int pTagId, final Object pValue) {
|
|
||||||
mTagId = pTagId;
|
|
||||||
mValue = pValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return String.format("%d:%d: %s", mTagId >> 8, mTagId & 0xff, mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String getTypeName() {
|
|
||||||
// TODO: Should this really look like EXIF?
|
|
||||||
if (mTagId == IPTC.TAG_RECORD_VERSION) {
|
|
||||||
return "SHORT";
|
|
||||||
}
|
|
||||||
else if (mValue instanceof String) {
|
|
||||||
return "ASCII";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "Unknown type";
|
|
||||||
}
|
|
||||||
|
|
||||||
public final String getValueAsString() {
|
|
||||||
return String.valueOf(mValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Object getValue() {
|
|
||||||
return mValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class Directory implements Iterable<Entry> {
|
|
||||||
private static final int ENCODING_UNKNOWN = -1;
|
|
||||||
private static final int ENCODING_UNSPECIFIED = 0;
|
|
||||||
private static final int ENCODING_UTF_8 = 0x1b2547;
|
|
||||||
|
|
||||||
private int mEncoding = ENCODING_UNSPECIFIED;
|
|
||||||
|
|
||||||
final List<Entry> mEntries = new ArrayList<Entry>();
|
|
||||||
|
|
||||||
private Directory() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Directory" + mEntries.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Entry get(int pTagId) {
|
|
||||||
for (Entry entry : mEntries) {
|
|
||||||
if (entry.mTagId == pTagId) {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Iterator<Entry> iterator() {
|
|
||||||
return mEntries.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Directory read(final ImageInputStream pInput, final long pSize) throws IOException {
|
|
||||||
Directory directory = new Directory();
|
|
||||||
|
|
||||||
final long streamEnd = pInput.getStreamPosition() + pSize;
|
|
||||||
|
|
||||||
// For each tag
|
|
||||||
while (pInput.getStreamPosition() < streamEnd) {
|
|
||||||
// Identifies start of a tag
|
|
||||||
byte marker = pInput.readByte();
|
|
||||||
|
|
||||||
if (marker != 0x1c) {
|
|
||||||
throw new IIOException(String.format("Corrupt IPTC stream segment, found 0x%02x (expected 0x1c)", marker));
|
|
||||||
}
|
|
||||||
|
|
||||||
int tagId = pInput.readShort();
|
|
||||||
int tagByteCount = pInput.readUnsignedShort();
|
|
||||||
|
|
||||||
directory.readEntry(pInput, tagId, tagByteCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
return directory;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void readEntry(final ImageInputStream pInput, final int pTagId, final int pLength) throws IOException {
|
|
||||||
Object value = null;
|
|
||||||
|
|
||||||
switch (pTagId) {
|
|
||||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
|
||||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
|
||||||
// TODO: Move somewhere else?
|
|
||||||
mEncoding = parseEncoding(pInput, pLength);
|
|
||||||
return;
|
|
||||||
case IPTC.TAG_RECORD_VERSION:
|
|
||||||
// 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) {
|
|
||||||
pInput.skipBytes(pLength);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// fall through
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we don't have a value, treat it as a string
|
|
||||||
if (value == null) {
|
|
||||||
if (pLength < 1) {
|
|
||||||
value = "(No value)";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value = parseString(pInput, pLength);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mEntries.add(new Entry(pTagId, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// private Date getDateForTime(final Directory directory, final int tagIdentifier) {
|
|
||||||
// int dateTag;
|
|
||||||
//
|
|
||||||
// switch (tagIdentifier) {
|
|
||||||
// case IPTC.TAG_RELEASE_TIME:
|
|
||||||
// dateTag = IPTC.TAG_RELEASE_DATE;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_EXPIRATION_TIME:
|
|
||||||
// dateTag = IPTC.TAG_EXPIRATION_DATE;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_TIME_CREATED:
|
|
||||||
// dateTag = IPTC.TAG_DATE_CREATED;
|
|
||||||
// break;
|
|
||||||
// case IPTC.TAG_DIGITAL_CREATION_TIME:
|
|
||||||
// dateTag = IPTC.TAG_DIGITAL_CREATION_DATE;
|
|
||||||
// break;
|
|
||||||
// default:
|
|
||||||
// return new Date(0l);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return directory.containsTag(dateTag) ? directory.getDate(dateTag) : new Date(0l);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
|
|
||||||
return tagByteCount == 3
|
|
||||||
&& (pInput.readUnsignedByte() << 16 | pInput.readUnsignedByte() << 8 | pInput.readUnsignedByte()) == ENCODING_UTF_8
|
|
||||||
? ENCODING_UTF_8 : ENCODING_UNKNOWN;
|
|
||||||
}
|
|
||||||
|
|
||||||
// private Date parseISO8601TimePart(final ImageInputStream pInputStream, int tagByteCount, final Date date) throws IOException {
|
|
||||||
// // ISO 8601: HHMMSS±HHMM
|
|
||||||
// if (tagByteCount >= 11) {
|
|
||||||
// String timeStr = parseString(pInputStream, tagByteCount);
|
|
||||||
// try {
|
|
||||||
// int hour = Integer.parseInt(timeStr.substring(0, 2));
|
|
||||||
// int minute = Integer.parseInt(timeStr.substring(2, 4));
|
|
||||||
// int second = Integer.parseInt(timeStr.substring(4, 6));
|
|
||||||
// String tzOffset = timeStr.substring(6, 11);
|
|
||||||
//
|
|
||||||
// TimeZone zone = new SimpleTimeZone(Integer.parseInt(tzOffset.charAt(0) == '+' ? tzOffset.substring(1) : tzOffset), tzOffset);
|
|
||||||
//
|
|
||||||
// GregorianCalendar calendar = new GregorianCalendar(zone);
|
|
||||||
// calendar.setTime(date);
|
|
||||||
//
|
|
||||||
// calendar.add(Calendar.HOUR_OF_DAY, hour);
|
|
||||||
// calendar.add(Calendar.MINUTE, minute);
|
|
||||||
// calendar.add(Calendar.SECOND, second);
|
|
||||||
//
|
|
||||||
// return calendar.getTime();
|
|
||||||
// }
|
|
||||||
// catch (NumberFormatException e) {
|
|
||||||
// // fall through and we'll store whatever was there as a String
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private Date parseISO8601DatePart(final ImageInputStream pInputStream, int tagByteCount) throws IOException {
|
|
||||||
// // ISO 8601: CCYYMMDD
|
|
||||||
// if (tagByteCount >= 8) {
|
|
||||||
// String dateStr = parseString(pInputStream, tagByteCount);
|
|
||||||
// try {
|
|
||||||
// int year = Integer.parseInt(dateStr.substring(0, 4));
|
|
||||||
// int month = Integer.parseInt(dateStr.substring(4, 6)) - 1;
|
|
||||||
// int day = Integer.parseInt(dateStr.substring(6, 8));
|
|
||||||
// GregorianCalendar calendar = new GregorianCalendar(year, month, day);
|
|
||||||
// return calendar.getTime();
|
|
||||||
// }
|
|
||||||
// catch (NumberFormatException e) {
|
|
||||||
// // fall through and we'll store whatever was there as a String
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: Pass encoding as parameter? Use if specified
|
|
||||||
private String parseString(final ImageInputStream pInput, final int pLength) throws IOException {
|
|
||||||
byte[] data = new byte[pLength];
|
|
||||||
pInput.readFully(data);
|
|
||||||
|
|
||||||
// NOTE: The IPTC specification says character data should use ISO 646 or ISO 2022 encoding.
|
|
||||||
// UTF-8 contains all 646 characters, but not 2022.
|
|
||||||
// This is however close to what libiptcdata does, see: http://libiptcdata.sourceforge.net/docs/iptc-i18n.html
|
|
||||||
Charset charset = Charset.forName("UTF-8");
|
|
||||||
CharsetDecoder decoder = charset.newDecoder();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// First try to decode using UTF-8 (which seems to be the de-facto standard)
|
|
||||||
// Will fail fast on illegal UTF-8-sequences
|
|
||||||
CharBuffer chars = decoder.onMalformedInput(CodingErrorAction.REPORT)
|
|
||||||
.onUnmappableCharacter(CodingErrorAction.REPORT)
|
|
||||||
.decode(ByteBuffer.wrap(data));
|
|
||||||
return chars.toString();
|
|
||||||
}
|
|
||||||
catch (CharacterCodingException notUTF8) {
|
|
||||||
if (mEncoding == ENCODING_UTF_8) {
|
|
||||||
throw new IIOException("Wrong encoding of IPTC data, explicitly set to UTF-8 in DataSet 1:90", notUTF8);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to use ISO-8859-1
|
|
||||||
// This will not fail, but may may create wrong fallback-characters
|
|
||||||
return StringUtil.decode(data, 0, data.length, "ISO8859_1");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static interface IPTC {
|
|
||||||
static final int ENVELOPE_RECORD = 1 << 8;
|
|
||||||
static final int APPLICATION_RECORD = 2 << 8;
|
|
||||||
|
|
||||||
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
|
|
||||||
|
|
||||||
/** 2:00 Record Version (mandatory) */
|
|
||||||
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
|
||||||
// /** 2:03 Object Type Reference */
|
|
||||||
// public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
|
|
||||||
// /** 2:04 Object Attribute Reference (repeatable) */
|
|
||||||
// public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
|
||||||
// /** 2:05 Object Name */
|
|
||||||
// public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
|
|
||||||
// /** 2:07 Edit Status */
|
|
||||||
// public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
|
||||||
// /** 2:08 Editorial Update */
|
|
||||||
// public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
|
||||||
// /** 2:10 Urgency */
|
|
||||||
// public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
|
||||||
// /** 2:12 Subect Reference (repeatable) */
|
|
||||||
// public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
|
|
||||||
// /** 2:15 Category */
|
|
||||||
// public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
|
||||||
// /** 2:20 Supplemental Category (repeatable) */
|
|
||||||
// public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
|
|
||||||
// /** 2:22 Fixture Identifier */
|
|
||||||
// public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
|
||||||
// /** 2:25 Keywords (repeatable) */
|
|
||||||
// public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25;
|
|
||||||
// /** 2:26 Content Locataion Code (repeatable) */
|
|
||||||
// public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
|
|
||||||
// /** 2:27 Content Locataion Name (repeatable) */
|
|
||||||
// public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
|
||||||
// /** 2:30 Release Date */
|
|
||||||
// public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
|
|
||||||
// /** 2:35 Release Time */
|
|
||||||
// public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
|
||||||
// /** 2:37 Expiration Date */
|
|
||||||
// public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
|
|
||||||
// /** 2:38 Expiration Time */
|
|
||||||
// public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
|
||||||
// /** 2:40 Special Instructions */
|
|
||||||
// public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
|
|
||||||
// /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
|
|
||||||
// public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
|
|
||||||
// /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
|
|
||||||
// public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
|
|
||||||
// /** 2:47 Reference Date (mandatory if 2:45 present) */
|
|
||||||
// public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
|
|
||||||
// /** 2:50 Reference Number (mandatory if 2:45 present) */
|
|
||||||
// public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
|
||||||
// /** 2:55 Date Created */
|
|
||||||
// public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
|
|
||||||
// /** 2:60 Time Created */
|
|
||||||
// public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
|
||||||
// /** 2:62 Digital Creation Date */
|
|
||||||
// public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
|
|
||||||
// /** 2:63 Digital Creation Date */
|
|
||||||
// public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
|
||||||
// /** 2:65 Originating Program */
|
|
||||||
// public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
|
|
||||||
// /** 2:70 Program Version (only valid if 2:65 present) */
|
|
||||||
// public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
|
|
||||||
// /** 2:75 Object Cycle (a: morning, p: evening, b: both) */
|
|
||||||
// public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
|
||||||
// /** 2:80 By-line (repeatable) */
|
|
||||||
// public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
|
|
||||||
// /** 2:85 By-line Title (repeatable) */
|
|
||||||
// public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
|
||||||
// /** 2:90 City */
|
|
||||||
// public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
|
||||||
// /** 2:92 Sub-location */
|
|
||||||
// public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
|
|
||||||
// /** 2:95 Province/State */
|
|
||||||
// public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
|
|
||||||
// /** 2:100 Country/Primary Location Code */
|
|
||||||
// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
|
|
||||||
// /** 2:101 Country/Primary Location Name */
|
|
||||||
// public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
|
|
||||||
// /** 2:103 Original Transmission Reference */
|
|
||||||
// public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
|
||||||
// /** 2:105 Headline */
|
|
||||||
// public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
|
||||||
// /** 2:110 Credit */
|
|
||||||
// public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
|
||||||
// /** 2:115 Source */
|
|
||||||
// public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
|
||||||
// /** 2:116 Copyright Notice */
|
|
||||||
// public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
|
|
||||||
// /** 2:118 Contact */
|
|
||||||
// public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
|
||||||
// /** 2:120 Catption/Abstract */
|
|
||||||
// public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
|
|
||||||
// /** 2:122 Writer/Editor (repeatable) */
|
|
||||||
// public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
|
||||||
// /** 2:125 Rasterized Caption (binary data) */
|
|
||||||
// public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
|
|
||||||
// /** 2:130 Image Type */
|
|
||||||
// public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
|
||||||
// /** 2:131 Image Orientation */
|
|
||||||
// public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
|
|
||||||
// /** 2:135 Language Identifier */
|
|
||||||
// public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
|
||||||
//
|
|
||||||
// // TODO: Should we expose this field?
|
|
||||||
// /**
|
|
||||||
// * 2:199 JobMinder Assignment Data (Custom IPTC field).
|
|
||||||
// * A common custom IPTC field used by a now discontinued application called JobMinder.
|
|
||||||
// *
|
|
||||||
// * @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
|
|
||||||
// */
|
|
||||||
// static final int CUSTOM_TAG_JOBMINDER_ASSIGMENT_DATA = APPLICATION_RECORD | 199;
|
|
||||||
//
|
|
||||||
// // TODO: 2:150-2:154 Audio and 2:200-2:202 Object Preview Data
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user