TMI-136: Added abstract MetadataWriter, along with preliminary IPTCWriter + test cases. Retrofit EXIFWriter. Loads of small changes and clean-up.

This commit is contained in:
Harald Kuhr 2015-06-05 10:49:31 +02:00
parent bbaa3e1186
commit f6d5a60600
15 changed files with 415 additions and 113 deletions

View File

@ -0,0 +1,15 @@
package com.twelvemonkeys.imageio.metadata;
import javax.imageio.stream.ImageOutputStream;
import java.io.IOException;
/**
* MetadataWriter.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: MetadataWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
*/
public abstract class MetadataWriter {
abstract public boolean write(Directory directory, ImageOutputStream stream) throws IOException;
}

View File

@ -71,6 +71,8 @@ final class EXIFEntry extends AbstractEntry {
return "IPTC"; return "IPTC";
case TIFF.TAG_PHOTOSHOP: case TIFF.TAG_PHOTOSHOP:
return "Adobe"; return "Adobe";
case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
return "ImageSourceData";
case TIFF.TAG_ICC_PROFILE: case TIFF.TAG_ICC_PROFILE:
return "ICCProfile"; return "ICCProfile";
@ -189,7 +191,7 @@ final class EXIFEntry extends AbstractEntry {
case EXIF.TAG_WHITE_BALANCE: case EXIF.TAG_WHITE_BALANCE:
return "WhiteBalance"; return "WhiteBalance";
case EXIF.TAG_DIGITAL_ZOOM_RATIO: case EXIF.TAG_DIGITAL_ZOOM_RATIO:
return "DigitalZoomRation"; return "DigitalZoomRatio";
case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM: case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM:
return "FocalLengthIn35mmFilm"; return "FocalLengthIn35mmFilm";
case EXIF.TAG_SCENE_CAPTURE_TYPE: case EXIF.TAG_SCENE_CAPTURE_TYPE:

View File

@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
import com.twelvemonkeys.imageio.metadata.CompoundDirectory; import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
import com.twelvemonkeys.imageio.metadata.Directory; import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry; import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import com.twelvemonkeys.lang.Validate; import com.twelvemonkeys.lang.Validate;
import javax.imageio.IIOException; import javax.imageio.IIOException;
@ -48,7 +49,7 @@ import java.util.*;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$ * @version $Id: EXIFWriter.java,v 1.0 17.07.13 10:20 haraldk Exp$
*/ */
public class EXIFWriter { public final class EXIFWriter extends MetadataWriter {
static final int WORD_LENGTH = 2; static final int WORD_LENGTH = 2;
static final int LONGWORD_LENGTH = 4; static final int LONGWORD_LENGTH = 4;
@ -58,6 +59,7 @@ public class EXIFWriter {
return write(new IFD(entries), stream); return write(new IFD(entries), stream);
} }
@Override
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException { public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
Validate.notNull(directory); Validate.notNull(directory);
Validate.notNull(stream); Validate.notNull(stream);

View File

@ -184,6 +184,18 @@ public interface TIFF {
*/ */
int TAG_PHOTOSHOP = 34377; int TAG_PHOTOSHOP = 34377;
/**
* Photoshop layer and mask information (byte order follows TIFF container).
* Layer and mask information found in a typical layered Photoshop file.
* Starts with a character string of "Adobe Photoshop Document Data Block"
* (or "Adobe Photoshop Document Data V0002" for PSB)
* including the null termination character.
* @see com.twelvemonkeys.imageio.metadata.psd.PSD
*/
int TAG_PHOTOSHOP_IMAGE_SOURCE_DATA = 37724;
int TAG_PHOTOSHOP_ANNOTATIONS = 50255;
/** /**
* ICC Color Profile. * ICC Color Profile.
* @see java.awt.color.ICC_Profile * @see java.awt.color.ICC_Profile

View File

@ -36,110 +36,115 @@ package com.twelvemonkeys.imageio.metadata.iptc;
* @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$ * @version $Id: IPTC.java,v 1.0 Nov 11, 2009 6:20:21 PM haraldk Exp$
*/ */
public interface IPTC { public interface IPTC {
static final int ENVELOPE_RECORD = 1 << 8; int ENVELOPE_RECORD = 1 << 8;
static final int APPLICATION_RECORD = 2 << 8; int APPLICATION_RECORD = 2 << 8;
static final int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90; /** 1:05: Destination */
int TAG_DESTINATION = ENVELOPE_RECORD | 5;
/** 1:50: Product ID */
int TAG_PRODUCT_ID = ENVELOPE_RECORD | 50;
/** 1:90: Coded Character Set */
int TAG_CODED_CHARACTER_SET = ENVELOPE_RECORD | 90;
/** 2:00 Record Version (mandatory) */ /** 2:00 Record Version (mandatory) */
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200 int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
/** 2:03 Object Type Reference */ /** 2:03 Object Type Reference */
public static final int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3; int TAG_OBJECT_TYPE_REFERENCE = APPLICATION_RECORD | 3;
/** 2:04 Object Attribute Reference (repeatable) */ /** 2:04 Object Attribute Reference (repeatable) */
public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4; int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
/** 2:05 Object Name */ /** 2:05 Object Name */
public static final int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205 int TAG_OBJECT_NAME = APPLICATION_RECORD | 5; // 0x0205
/** 2:07 Edit Status */ /** 2:07 Edit Status */
public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7; int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
/** 2:08 Editorial Update */ /** 2:08 Editorial Update */
public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8; int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
/** 2:10 Urgency */ /** 2:10 Urgency */
public static final int TAG_URGENCY = APPLICATION_RECORD | 10; int TAG_URGENCY = APPLICATION_RECORD | 10;
/** 2:12 Subect Reference (repeatable) */ /** 2:12 Subect Reference (repeatable) */
public static final int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12; int TAG_SUBJECT_REFERENCE = APPLICATION_RECORD | 12;
/** 2:15 Category */ /** 2:15 Category */
public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
/** 2:20 Supplemental Category (repeatable) */ /** 2:20 Supplemental Category (repeatable) */
public static final int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20; int TAG_SUPPLEMENTAL_CATEGORIES = APPLICATION_RECORD | 20;
/** 2:22 Fixture Identifier */ /** 2:22 Fixture Identifier */
public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22; int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
/** 2:25 Keywords (repeatable) */ /** 2:25 Keywords (repeatable) */
public static final int TAG_KEYWORDS = APPLICATION_RECORD | 25; int TAG_KEYWORDS = APPLICATION_RECORD | 25;
/** 2:26 Content Locataion Code (repeatable) */ /** 2:26 Content Locataion Code (repeatable) */
public static final int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26; int TAG_CONTENT_LOCATION_CODE = APPLICATION_RECORD | 26;
/** 2:27 Content Locataion Name (repeatable) */ /** 2:27 Content Locataion Name (repeatable) */
public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27; int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
/** 2:30 Release Date */ /** 2:30 Release Date */
public static final int TAG_RELEASE_DATE = APPLICATION_RECORD | 30; int TAG_RELEASE_DATE = APPLICATION_RECORD | 30;
/** 2:35 Release Time */ /** 2:35 Release Time */
public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35; int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
/** 2:37 Expiration Date */ /** 2:37 Expiration Date */
public static final int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37; int TAG_EXPIRATION_DATE = APPLICATION_RECORD | 37;
/** 2:38 Expiration Time */ /** 2:38 Expiration Time */
public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38; int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
/** 2:40 Special Instructions */ /** 2:40 Special Instructions */
public static final int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228 int TAG_SPECIAL_INSTRUCTIONS = APPLICATION_RECORD | 40; // 0x0228
/** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */ /** 2:42 Action Advised (1: Kill, 2: Replace, 3: Append, 4: Reference) */
public static final int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42; int TAG_ACTION_ADVICED = APPLICATION_RECORD | 42;
/** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */ /** 2:45 Reference Service (repeatable in triplets with 2:47 and 2:50) */
public static final int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45; int TAG_REFERENCE_SERVICE = APPLICATION_RECORD | 45;
/** 2:47 Reference Date (mandatory if 2:45 present) */ /** 2:47 Reference Date (mandatory if 2:45 present) */
public static final int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47; int TAG_REFERENCE_DATE = APPLICATION_RECORD | 47;
/** 2:50 Reference Number (mandatory if 2:45 present) */ /** 2:50 Reference Number (mandatory if 2:45 present) */
public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50; int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
/** 2:55 Date Created */ /** 2:55 Date Created */
public static final int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237 int TAG_DATE_CREATED = APPLICATION_RECORD | 55; // 0x0237
/** 2:60 Time Created */ /** 2:60 Time Created */
public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60; int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
/** 2:62 Digital Creation Date */ /** 2:62 Digital Creation Date */
public static final int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62; int TAG_DIGITAL_CREATION_DATE = APPLICATION_RECORD | 62;
/** 2:63 Digital Creation Date */ /** 2:63 Digital Creation Date */
public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63; int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
/** 2:65 Originating Program */ /** 2:65 Originating Program */
public static final int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65; int TAG_ORIGINATING_PROGRAM = APPLICATION_RECORD | 65;
/** 2:70 Program Version (only valid if 2:65 present) */ /** 2:70 Program Version (only valid if 2:65 present) */
public static final int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70; int TAG_PROGRAM_VERSION = APPLICATION_RECORD | 70;
/** 2:75 Object Cycle (a: morning, p: evening, b: both) */ /** 2:75 Object Cycle (a: morning, p: evening, b: both) */
public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75; int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
/** 2:80 By-line (repeatable) */ /** 2:80 By-line (repeatable) */
public static final int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250 int TAG_BY_LINE = APPLICATION_RECORD | 80; // 0x0250
/** 2:85 By-line Title (repeatable) */ /** 2:85 By-line Title (repeatable) */
public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255 int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
/** 2:90 City */ /** 2:90 City */
public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
/** 2:92 Sub-location */ /** 2:92 Sub-location */
public static final int TAG_SUB_LOCATION = APPLICATION_RECORD | 92; int TAG_SUB_LOCATION = APPLICATION_RECORD | 92;
/** 2:95 Province/State */ /** 2:95 Province/State */
public static final int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f int TAG_PROVINCE_OR_STATE = APPLICATION_RECORD | 95; // 0x025f
/** 2:100 Country/Primary Location Code */ /** 2:100 Country/Primary Location Code */
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100; int TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE = APPLICATION_RECORD | 100;
/** 2:101 Country/Primary Location Name */ /** 2:101 Country/Primary Location Name */
public static final int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265 int TAG_COUNTRY_OR_PRIMARY_LOCATION = APPLICATION_RECORD | 101; // 0x0265
/** 2:103 Original Transmission Reference */ /** 2:103 Original Transmission Reference */
public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267 int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
/** 2:105 Headline */ /** 2:105 Headline */
public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269 int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
/** 2:110 Credit */ /** 2:110 Credit */
public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
/** 2:115 Source */ /** 2:115 Source */
public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273 int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
/** 2:116 Copyright Notice */ /** 2:116 Copyright Notice */
public static final int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274 int TAG_COPYRIGHT_NOTICE = APPLICATION_RECORD | 116; // 0x0274
/** 2:118 Contact */ /** 2:118 Contact */
public static final int TAG_CONTACT = APPLICATION_RECORD | 118; int TAG_CONTACT = APPLICATION_RECORD | 118;
/** 2:120 Catption/Abstract */ /** 2:120 Catption/Abstract */
public static final int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278 int TAG_CAPTION = APPLICATION_RECORD | 120; // 0x0278
/** 2:122 Writer/Editor (repeatable) */ /** 2:122 Writer/Editor (repeatable) */
public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
/** 2:125 Rasterized Caption (binary data) */ /** 2:125 Rasterized Caption (binary data) */
public static final int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125; int TAG_RASTERIZED_CATPTION = APPLICATION_RECORD | 125;
/** 2:130 Image Type */ /** 2:130 Image Type */
public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130; int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
/** 2:131 Image Orientation */ /** 2:131 Image Orientation */
public static final int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131; int TAG_IMAGE_ORIENTATION = APPLICATION_RECORD | 131;
/** 2:135 Language Identifier */ /** 2:135 Language Identifier */
public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135; int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
// TODO: 2:150-2:154 Audio // TODO: 2:150-2:154 Audio
@ -150,9 +155,28 @@ public interface IPTC {
* *
* @see <a href="http://www.jobminder.net/">JobMinder Homepage</a> * @see <a href="http://www.jobminder.net/">JobMinder Homepage</a>
*/ */
static final int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199; int CUSTOM_TAG_JOBMINDER_ASSIGNMENT_DATA = APPLICATION_RECORD | 199;
// TODO: Other custom fields in 155-200 range? // TODO: Other custom fields in 155-200 range?
// TODO: 2:200-2:202 Object Preview Data // TODO: 2:200-2:202 Object Preview Data
final class Tags {
static boolean isArray(final short tagId) {
switch (tagId) {
case IPTC.TAG_DESTINATION:
case IPTC.TAG_PRODUCT_ID:
case IPTC.TAG_SUBJECT_REFERENCE:
case IPTC.TAG_SUPPLEMENTAL_CATEGORIES:
case IPTC.TAG_KEYWORDS:
case IPTC.TAG_CONTENT_LOCATION_CODE:
case IPTC.TAG_CONTENT_LOCATION_NAME:
case IPTC.TAG_BY_LINE:
return true;
default:
return false;
}
}
}
} }

View File

@ -42,6 +42,7 @@ import java.util.Collection;
*/ */
final class IPTCDirectory extends AbstractDirectory { final class IPTCDirectory extends AbstractDirectory {
IPTCDirectory(final Collection<? extends Entry> entries) { IPTCDirectory(final Collection<? extends Entry> entries) {
// TODO: Normalize multiple entries with same key to single entry w/array
super(entries); super(entries);
} }
} }

View File

@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry {
switch ((Integer) getIdentifier()) { switch ((Integer) getIdentifier()) {
case IPTC.TAG_RECORD_VERSION: case IPTC.TAG_RECORD_VERSION:
return "RecordVersion"; return "RecordVersion";
case IPTC.TAG_KEYWORDS:
return "Keywords";
case IPTC.TAG_SPECIAL_INSTRUCTIONS:
return "Instructions";
case IPTC.TAG_DIGITAL_CREATION_DATE:
return "DigitalCreationDate";
case IPTC.TAG_DIGITAL_CREATION_TIME:
return "DigitalCreationTime";
case IPTC.TAG_DATE_CREATED:
return "DateCreated";
case IPTC.TAG_TIME_CREATED:
return "TimeCreated";
case IPTC.TAG_BY_LINE_TITLE:
return "ByLineTitle";
case IPTC.TAG_CITY:
return "City";
case IPTC.TAG_SUB_LOCATION:
return "SubLocation";
case IPTC.TAG_PROVINCE_OR_STATE:
return "StateProvince";
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION_CODE:
return "CountryCode";
case IPTC.TAG_COUNTRY_OR_PRIMARY_LOCATION:
return "Country";
case IPTC.TAG_SOURCE: case IPTC.TAG_SOURCE:
return "Source"; return "Source";
case IPTC.TAG_CAPTION: case IPTC.TAG_CAPTION:

View File

@ -43,8 +43,9 @@ import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction; import java.nio.charset.CodingErrorAction;
import java.util.ArrayList; import java.util.Arrays;
import java.util.List; import java.util.LinkedHashMap;
import java.util.Map;
/** /**
* IPTCReader * IPTCReader
@ -60,52 +61,72 @@ public final class IPTCReader extends MetadataReader {
private int encoding = ENCODING_UNSPECIFIED; private int encoding = ENCODING_UNSPECIFIED;
@Override @Override
public Directory read(final ImageInputStream input) throws IOException { public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input"); Validate.notNull(input, "input");
List<Entry> entries = new ArrayList<Entry>(); Map<Short, Entry> entries = new LinkedHashMap<>();
// 0x1c identifies start of a tag // 0x1c identifies start of a tag
while (input.read() == 0x1c) { while (input.read() == 0x1c) {
short tagId = input.readShort(); short tagId = input.readShort();
int tagByteCount = input.readUnsignedShort(); int tagByteCount = input.readUnsignedShort();
Entry entry = readEntry(input, tagId, tagByteCount);
boolean array = IPTC.Tags.isArray(tagId);
Entry entry = readEntry(input, tagId, tagByteCount, array, array ? entries.get(tagId) : null);
if (entry != null) { if (entry != null) {
entries.add(entry); entries.put(tagId, entry);
} }
} }
return new IPTCDirectory(entries); return new IPTCDirectory(entries.values());
} }
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength) throws IOException { private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) {
Object value = null; Object[] oldValue = oldEntry != null ? (Object[]) oldEntry.getValue() : null;
Object[] value;
if (newValue instanceof String) {
if (oldValue == null) {
value = new String[] {(String) newValue};
}
else {
String[] array = (String[]) oldValue;
value = Arrays.copyOf(array, array.length + 1);
value[value.length - 1] = newValue;
}
}
else {
if (oldValue == null) {
value = new Object[] {newValue};
}
else {
value = Arrays.copyOf(oldValue, oldValue.length + 1);
value [value .length - 1] = newValue;
}
}
return new IPTCEntry(tagId, value);
}
private IPTCEntry readEntry(final ImageInputStream pInput, final short pTagId, final int pLength, final boolean array, final Entry oldEntry) throws IOException {
Object value;
switch (pTagId) { switch (pTagId) {
case IPTC.TAG_CODED_CHARACTER_SET: case IPTC.TAG_CODED_CHARACTER_SET:
// TODO: Mapping from ISO 646 to Java supported character sets? // TODO: Mapping from ISO 646 to Java supported character sets?
// TODO: Move somewhere else?
encoding = parseEncoding(pInput, pLength); encoding = parseEncoding(pInput, pLength);
return null; return null;
case IPTC.TAG_RECORD_VERSION: case IPTC.TAG_RECORD_VERSION:
// TODO: Assert length == 2?
// A single unsigned short value // A single unsigned short value
value = pInput.readUnsignedShort(); value = pInput.readUnsignedShort();
break; break;
default: default:
// Skip non-Application fields, as they are typically not human readable // TODO: Create Tags.getType(tag), to allow for more flexible types
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) { if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) {
pInput.skipBytes(pLength); // Treat Application records as Strings
return null;
}
// fall through
}
// If we don't have a value, treat it as a string
if (value == null) {
if (pLength < 1) { if (pLength < 1) {
value = null; value = null;
} }
@ -113,8 +134,15 @@ public final class IPTCReader extends MetadataReader {
value = parseString(pInput, pLength); value = parseString(pInput, pLength);
} }
} }
else {
// Non-Application fields, typically not human readable
byte[] data = new byte[pLength];
pInput.readFully(data);
value = data;
}
}
return new IPTCEntry(pTagId, value); return array ? mergeEntries(pTagId, value, oldEntry) : new IPTCEntry(pTagId, value);
} }
private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException { private int parseEncoding(final ImageInputStream pInput, int tagByteCount) throws IOException {
@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader {
} }
// Fall back to use ISO-8859-1 // Fall back to use ISO-8859-1
// This will not fail, but may may create wrong fallback-characters // This will not fail, but may create wrong fallback-characters
return StringUtil.decode(data, 0, data.length, "ISO8859_1"); return StringUtil.decode(data, 0, data.length, "ISO8859_1");
} }
} }

View File

@ -0,0 +1,61 @@
package com.twelvemonkeys.imageio.metadata.iptc;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import javax.imageio.stream.ImageOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static com.twelvemonkeys.lang.Validate.notNull;
/**
* IPTCWriter.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: IPTCWriter.java,v 1.0 28/05/15 harald.kuhr Exp$
*/
public final class IPTCWriter extends MetadataWriter {
@Override
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
notNull(directory, "directory");
notNull(stream, "stream");
for (Entry entry : directory) {
int tag = (Integer) entry.getIdentifier();
Object value = entry.getValue();
if (IPTC.Tags.isArray((short) tag)) {
Object[] values = (Object[]) value;
for (Object v : values) {
stream.write(0x1c);
stream.writeShort(tag);
writeValue(stream, v);
}
}
else {
stream.write(0x1c);
stream.writeShort(tag);
writeValue(stream, value);
}
}
return false;
}
private void writeValue(final ImageOutputStream stream, final Object value) throws IOException {
if (value instanceof String) {
byte[] data = ((String) value).getBytes(StandardCharsets.UTF_8);
stream.writeShort(data.length);
stream.write(data);
}
else if (value instanceof Integer) {
// TODO: Need to know types from tag
stream.writeShort(2);
stream.writeShort((Integer) value);
}
}
}

View File

@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry {
field.setAccessible(true); field.setAccessible(true);
if (field.get(null).equals(getIdentifier())) { if (field.get(null).equals(getIdentifier())) {
return StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true); String fieldName = StringUtil.lispToCamel(field.getName().substring(4).replace("_", "-").toLowerCase(), true);
return name != null ? fieldName + ": " + name : fieldName;
} }
} }
} }

View File

@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader {
public Directory read(final ImageInputStream input) throws IOException { public Directory read(final ImageInputStream input) throws IOException {
Validate.notNull(input, "input"); Validate.notNull(input, "input");
List<PSDEntry> entries = new ArrayList<PSDEntry>(); List<PSDEntry> entries = new ArrayList<>();
while (true) { while (true) {
try { try {

View File

@ -79,7 +79,7 @@ public abstract class MetadataReaderAbstractTest {
assertNotNull(directory); assertNotNull(directory);
} }
protected final Matcher<Entry> hasValue(final Object value) { protected static Matcher<Entry> hasValue(final Object value) {
return new EntryHasValue(value); return new EntryHasValue(value);
} }

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2012, Harald Kuhr
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name "TwelveMonkeys" nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.twelvemonkeys.imageio.metadata;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import org.junit.Test;
import javax.imageio.ImageIO;
import javax.imageio.spi.IIORegistry;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
/**
* ReaderAbstractTest
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: haraldk$
* @version $Id: ReaderAbstractTest.java,v 1.0 04.01.12 09:40 haraldk Exp$
*/
public abstract class MetadataWriterAbstractTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
protected final URL getResource(final String name) throws IOException {
return getClass().getResource(name);
}
protected final ImageInputStream getDataAsIIS() throws IOException {
return ImageIO.createImageInputStream(getData());
}
protected abstract InputStream getData() throws IOException;
protected abstract MetadataWriter createWriter();
@Test(expected = IllegalArgumentException.class)
public void testWriteNullDirectory() throws IOException {
createWriter().write(null, new MemoryCacheImageOutputStream(new ByteArrayOutputStream()));
}
@Test(expected = IllegalArgumentException.class)
public void testWriteNullStream() throws IOException {
createWriter().write(new AbstractDirectory(new ArrayList<Entry>()) {
}, null);
}
}

View File

@ -28,24 +28,17 @@
package com.twelvemonkeys.imageio.metadata.exif; package com.twelvemonkeys.imageio.metadata.exif;
import com.twelvemonkeys.imageio.metadata.AbstractDirectory; import com.twelvemonkeys.imageio.metadata.*;
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.Entry;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream; import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
import com.twelvemonkeys.io.FastByteArrayOutputStream; import com.twelvemonkeys.io.FastByteArrayOutputStream;
import org.junit.Test; import org.junit.Test;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.spi.IIORegistry;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream; import javax.imageio.stream.ImageOutputStream;
import javax.imageio.stream.ImageOutputStreamImpl; import javax.imageio.stream.ImageOutputStreamImpl;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
@ -61,37 +54,25 @@ import static org.junit.Assert.assertNotNull;
* @author last modified by $Author: haraldk$ * @author last modified by $Author: haraldk$
* @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$ * @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
*/ */
public class EXIFWriterTest { public class EXIFWriterTest extends MetadataWriterAbstractTest {
static {
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
ImageIO.setUseCache(false);
}
protected final URL getResource(final String name) throws IOException { @Override
return getClass().getResource(name);
}
protected final ImageInputStream getDataAsIIS() throws IOException {
return ImageIO.createImageInputStream(getData());
}
// @Override
protected InputStream getData() throws IOException { protected InputStream getData() throws IOException {
return getResource("/exif/exif-jpeg-segment.bin").openStream(); return getResource("/exif/exif-jpeg-segment.bin").openStream();
} }
// @Override
protected EXIFReader createReader() { protected EXIFReader createReader() {
return new EXIFReader(); return new EXIFReader();
} }
@Override
protected EXIFWriter createWriter() { protected EXIFWriter createWriter() {
return new EXIFWriter(); return new EXIFWriter();
} }
@Test @Test
public void testWriteReadSimple() throws IOException { public void testWriteReadSimple() throws IOException {
ArrayList<Entry> entries = new ArrayList<Entry>(); ArrayList<Entry> entries = new ArrayList<>();
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
@ -139,7 +120,7 @@ public class EXIFWriterTest {
@Test @Test
public void testWriteMotorola() throws IOException { public void testWriteMotorola() throws IOException {
ArrayList<Entry> entries = new ArrayList<Entry>(); ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
Directory directory = new AbstractDirectory(entries) {}; Directory directory = new AbstractDirectory(entries) {};
@ -174,7 +155,7 @@ public class EXIFWriterTest {
@Test @Test
public void testWriteIntel() throws IOException { public void testWriteIntel() throws IOException {
ArrayList<Entry> entries = new ArrayList<Entry>(); ArrayList<Entry> entries = new ArrayList<>();
entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {}); entries.add(new AbstractEntry(TIFF.TAG_SOFTWARE, "TwelveMonkeys ImageIO") {});
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
Directory directory = new AbstractDirectory(entries) {}; Directory directory = new AbstractDirectory(entries) {};
@ -254,7 +235,7 @@ public class EXIFWriterTest {
@Test @Test
public void testComputeIFDSize() throws IOException { public void testComputeIFDSize() throws IOException {
ArrayList<Entry> entries = new ArrayList<Entry>(); ArrayList<Entry> entries = new ArrayList<>();
entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_ORIENTATION, 1, TIFF.TYPE_SHORT));
entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT)); entries.add(new EXIFEntry(TIFF.TAG_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {}); entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});

View File

@ -0,0 +1,72 @@
package com.twelvemonkeys.imageio.metadata.iptc;
import com.twelvemonkeys.imageio.metadata.Directory;
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
import com.twelvemonkeys.imageio.metadata.MetadataWriterAbstractTest;
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
import org.junit.Test;
import javax.imageio.stream.MemoryCacheImageOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeNotNull;
/**
* IPTCWriterTest.
*
* @author <a href="mailto:harald.kuhr@gmail.com">Harald Kuhr</a>
* @author last modified by $Author: harald.kuhr$
* @version $Id: IPTCWriterTest.java,v 1.0 05/06/15 harald.kuhr Exp$
*/
public class IPTCWriterTest extends MetadataWriterAbstractTest {
@Override
protected InputStream getData() throws IOException {
return getResource("/iptc/iptc-jpeg-segment.bin").openStream();
}
@Override
protected MetadataWriter createWriter() {
return new IPTCWriter();
}
private IPTCReader createReader() {
return new IPTCReader();
}
@Test
public void testRewriteExisting() throws IOException {
IPTCReader reader = createReader();
Directory iptc = reader.read(getDataAsIIS());
assumeNotNull(iptc);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
createWriter().write(iptc, stream);
stream.close();
Directory written = reader.read(new ByteArrayImageInputStream(bytes.toByteArray()));
assertEquals(iptc, written);
}
@Test
public void testWrite() throws IOException {
List<IPTCEntry> entries = new ArrayList<>();
entries.add(new IPTCEntry(IPTC.TAG_KEYWORDS, new String[] {"Uno", "Due", "Tre"}));
Directory iptc = new IPTCDirectory(entries);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
MemoryCacheImageOutputStream stream = new MemoryCacheImageOutputStream(bytes);
createWriter().write(iptc, stream);
stream.close();
Directory written = createReader().read(new ByteArrayImageInputStream(bytes.toByteArray()));
System.err.println("written: " + written);
assertEquals(iptc, written);
}
}