mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-04 03:55:28 -04:00
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:
parent
bbaa3e1186
commit
f6d5a60600
@ -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;
|
||||
}
|
@ -71,6 +71,8 @@ final class EXIFEntry extends AbstractEntry {
|
||||
return "IPTC";
|
||||
case TIFF.TAG_PHOTOSHOP:
|
||||
return "Adobe";
|
||||
case TIFF.TAG_PHOTOSHOP_IMAGE_SOURCE_DATA:
|
||||
return "ImageSourceData";
|
||||
case TIFF.TAG_ICC_PROFILE:
|
||||
return "ICCProfile";
|
||||
|
||||
@ -189,7 +191,7 @@ final class EXIFEntry extends AbstractEntry {
|
||||
case EXIF.TAG_WHITE_BALANCE:
|
||||
return "WhiteBalance";
|
||||
case EXIF.TAG_DIGITAL_ZOOM_RATIO:
|
||||
return "DigitalZoomRation";
|
||||
return "DigitalZoomRatio";
|
||||
case EXIF.TAG_FOCAL_LENGTH_IN_35_MM_FILM:
|
||||
return "FocalLengthIn35mmFilm";
|
||||
case EXIF.TAG_SCENE_CAPTURE_TYPE:
|
||||
|
@ -31,6 +31,7 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
||||
import com.twelvemonkeys.imageio.metadata.CompoundDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.MetadataWriter;
|
||||
import com.twelvemonkeys.lang.Validate;
|
||||
|
||||
import javax.imageio.IIOException;
|
||||
@ -48,7 +49,7 @@ import java.util.*;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @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 LONGWORD_LENGTH = 4;
|
||||
@ -58,6 +59,7 @@ public class EXIFWriter {
|
||||
return write(new IFD(entries), stream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean write(final Directory directory, final ImageOutputStream stream) throws IOException {
|
||||
Validate.notNull(directory);
|
||||
Validate.notNull(stream);
|
||||
|
@ -184,6 +184,18 @@ public interface TIFF {
|
||||
*/
|
||||
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.
|
||||
* @see java.awt.color.ICC_Profile
|
||||
|
@ -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$
|
||||
*/
|
||||
public interface IPTC {
|
||||
static final int ENVELOPE_RECORD = 1 << 8;
|
||||
static final int APPLICATION_RECORD = 2 << 8;
|
||||
int ENVELOPE_RECORD = 1 << 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) */
|
||||
public static final int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
int TAG_RECORD_VERSION = APPLICATION_RECORD; // 0x0200
|
||||
|
||||
/** 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) */
|
||||
public static final int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
int TAG_OBJECT_ATTRIBUTE_REFERENCE = APPLICATION_RECORD | 4;
|
||||
/** 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 */
|
||||
public static final int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
int TAG_EDIT_STATUS = APPLICATION_RECORD | 7;
|
||||
/** 2:08 Editorial Update */
|
||||
public static final int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
int TAG_EDITORIAL_UPDATE = APPLICATION_RECORD | 8;
|
||||
/** 2:10 Urgency */
|
||||
public static final int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
int TAG_URGENCY = APPLICATION_RECORD | 10;
|
||||
/** 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 */
|
||||
public static final int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
int TAG_CATEGORY = APPLICATION_RECORD | 15; // 0x020f
|
||||
/** 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 */
|
||||
public static final int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
int TAG_FIXTURE_IDENTIFIER = APPLICATION_RECORD | 22;
|
||||
/** 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) */
|
||||
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) */
|
||||
public static final int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
int TAG_CONTENT_LOCATION_NAME = APPLICATION_RECORD | 27;
|
||||
/** 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 */
|
||||
public static final int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
int TAG_RELEASE_TIME = APPLICATION_RECORD | 35;
|
||||
/** 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 */
|
||||
public static final int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
int TAG_EXPIRATION_TIME = APPLICATION_RECORD | 38;
|
||||
/** 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) */
|
||||
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) */
|
||||
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) */
|
||||
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) */
|
||||
public static final int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
int TAG_REFERENCE_NUMBER = APPLICATION_RECORD | 50;
|
||||
/** 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 */
|
||||
public static final int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
int TAG_TIME_CREATED = APPLICATION_RECORD | 60;
|
||||
/** 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 */
|
||||
public static final int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
int TAG_DIGITAL_CREATION_TIME = APPLICATION_RECORD | 63;
|
||||
/** 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) */
|
||||
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) */
|
||||
public static final int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
int TAG_OBJECT_CYCLE = APPLICATION_RECORD | 75;
|
||||
/** 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) */
|
||||
public static final int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
int TAG_BY_LINE_TITLE = APPLICATION_RECORD | 85; // 0x0255
|
||||
/** 2:90 City */
|
||||
public static final int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
int TAG_CITY = APPLICATION_RECORD | 90; // 0x025a
|
||||
/** 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 */
|
||||
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 */
|
||||
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 */
|
||||
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 */
|
||||
public static final int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
int TAG_ORIGINAL_TRANSMISSION_REFERENCE = APPLICATION_RECORD | 103; // 0x0267
|
||||
/** 2:105 Headline */
|
||||
public static final int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
int TAG_HEADLINE = APPLICATION_RECORD | 105; // 0x0269
|
||||
/** 2:110 Credit */
|
||||
public static final int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
int TAG_CREDIT = APPLICATION_RECORD | 110; // 0x026e
|
||||
/** 2:115 Source */
|
||||
public static final int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
int TAG_SOURCE = APPLICATION_RECORD | 115; // 0x0273
|
||||
/** 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 */
|
||||
public static final int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
int TAG_CONTACT = APPLICATION_RECORD | 118;
|
||||
/** 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) */
|
||||
public static final int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
int TAG_WRITER = APPLICATION_RECORD | 122; // 0x027a
|
||||
/** 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 */
|
||||
public static final int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
int TAG_IMAGE_TYPE = APPLICATION_RECORD | 130;
|
||||
/** 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 */
|
||||
public static final int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
int TAG_LANGUAGE_IDENTIFIER = APPLICATION_RECORD | 135;
|
||||
|
||||
// TODO: 2:150-2:154 Audio
|
||||
|
||||
@ -150,9 +155,28 @@ public interface IPTC {
|
||||
*
|
||||
* @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: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import java.util.Collection;
|
||||
*/
|
||||
final class IPTCDirectory extends AbstractDirectory {
|
||||
IPTCDirectory(final Collection<? extends Entry> entries) {
|
||||
// TODO: Normalize multiple entries with same key to single entry w/array
|
||||
super(entries);
|
||||
}
|
||||
}
|
@ -47,6 +47,30 @@ class IPTCEntry extends AbstractEntry {
|
||||
switch ((Integer) getIdentifier()) {
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
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:
|
||||
return "Source";
|
||||
case IPTC.TAG_CAPTION:
|
||||
|
@ -43,8 +43,9 @@ 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.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* IPTCReader
|
||||
@ -60,61 +61,88 @@ public final class IPTCReader extends MetadataReader {
|
||||
|
||||
private int encoding = ENCODING_UNSPECIFIED;
|
||||
|
||||
|
||||
@Override
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<Entry> entries = new ArrayList<Entry>();
|
||||
Map<Short, Entry> entries = new LinkedHashMap<>();
|
||||
|
||||
// 0x1c identifies start of a tag
|
||||
while (input.read() == 0x1c) {
|
||||
short tagId = input.readShort();
|
||||
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) {
|
||||
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 {
|
||||
Object value = null;
|
||||
private IPTCEntry mergeEntries(final short tagId, final Object newValue, final Entry oldEntry) {
|
||||
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) {
|
||||
case IPTC.TAG_CODED_CHARACTER_SET:
|
||||
// TODO: Mapping from ISO 646 to Java supported character sets?
|
||||
// TODO: Move somewhere else?
|
||||
encoding = parseEncoding(pInput, pLength);
|
||||
return null;
|
||||
case IPTC.TAG_RECORD_VERSION:
|
||||
// TODO: Assert length == 2?
|
||||
// A single unsigned short value
|
||||
value = pInput.readUnsignedShort();
|
||||
break;
|
||||
default:
|
||||
// Skip non-Application fields, as they are typically not human readable
|
||||
if ((pTagId & 0xff00) != IPTC.APPLICATION_RECORD) {
|
||||
pInput.skipBytes(pLength);
|
||||
return null;
|
||||
// TODO: Create Tags.getType(tag), to allow for more flexible types
|
||||
if ((pTagId & 0xff00) == IPTC.APPLICATION_RECORD) {
|
||||
// Treat Application records as Strings
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Non-Application fields, typically not human readable
|
||||
byte[] data = new byte[pLength];
|
||||
pInput.readFully(data);
|
||||
value = data;
|
||||
}
|
||||
|
||||
// fall through
|
||||
}
|
||||
|
||||
// If we don't have a value, treat it as a string
|
||||
if (value == null) {
|
||||
if (pLength < 1) {
|
||||
value = null;
|
||||
}
|
||||
else {
|
||||
value = parseString(pInput, pLength);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -148,7 +176,7 @@ public final class IPTCReader extends MetadataReader {
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -66,7 +66,8 @@ class PSDEntry extends AbstractEntry {
|
||||
field.setAccessible(true);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -59,7 +59,7 @@ public final class PSDReader extends MetadataReader {
|
||||
public Directory read(final ImageInputStream input) throws IOException {
|
||||
Validate.notNull(input, "input");
|
||||
|
||||
List<PSDEntry> entries = new ArrayList<PSDEntry>();
|
||||
List<PSDEntry> entries = new ArrayList<>();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
|
@ -79,7 +79,7 @@ public abstract class MetadataReaderAbstractTest {
|
||||
assertNotNull(directory);
|
||||
}
|
||||
|
||||
protected final Matcher<Entry> hasValue(final Object value) {
|
||||
protected static Matcher<Entry> hasValue(final Object value) {
|
||||
return new EntryHasValue(value);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -28,24 +28,17 @@
|
||||
|
||||
package com.twelvemonkeys.imageio.metadata.exif;
|
||||
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractDirectory;
|
||||
import com.twelvemonkeys.imageio.metadata.AbstractEntry;
|
||||
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||
import com.twelvemonkeys.imageio.metadata.Entry;
|
||||
import com.twelvemonkeys.imageio.metadata.*;
|
||||
import com.twelvemonkeys.imageio.stream.ByteArrayImageInputStream;
|
||||
import com.twelvemonkeys.imageio.stream.URLImageInputStreamSpi;
|
||||
import com.twelvemonkeys.io.FastByteArrayOutputStream;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import javax.imageio.stream.ImageInputStream;
|
||||
import javax.imageio.stream.ImageOutputStream;
|
||||
import javax.imageio.stream.ImageOutputStreamImpl;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@ -61,37 +54,25 @@ import static org.junit.Assert.assertNotNull;
|
||||
* @author last modified by $Author: haraldk$
|
||||
* @version $Id: EXIFWriterTest.java,v 1.0 18.07.13 09:53 haraldk Exp$
|
||||
*/
|
||||
public class EXIFWriterTest {
|
||||
static {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new URLImageInputStreamSpi());
|
||||
ImageIO.setUseCache(false);
|
||||
}
|
||||
public class EXIFWriterTest extends MetadataWriterAbstractTest {
|
||||
|
||||
protected final URL getResource(final String name) throws IOException {
|
||||
return getClass().getResource(name);
|
||||
}
|
||||
|
||||
protected final ImageInputStream getDataAsIIS() throws IOException {
|
||||
return ImageIO.createImageInputStream(getData());
|
||||
}
|
||||
|
||||
// @Override
|
||||
@Override
|
||||
protected InputStream getData() throws IOException {
|
||||
return getResource("/exif/exif-jpeg-segment.bin").openStream();
|
||||
}
|
||||
|
||||
// @Override
|
||||
protected EXIFReader createReader() {
|
||||
return new EXIFReader();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EXIFWriter createWriter() {
|
||||
return new EXIFWriter();
|
||||
}
|
||||
|
||||
@Test
|
||||
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_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
@ -139,7 +120,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
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 EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
@ -174,7 +155,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
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 EXIFEntry(TIFF.TAG_IMAGE_WIDTH, Integer.MAX_VALUE, TIFF.TYPE_LONG));
|
||||
Directory directory = new AbstractDirectory(entries) {};
|
||||
@ -254,7 +235,7 @@ public class EXIFWriterTest {
|
||||
|
||||
@Test
|
||||
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_IMAGE_WIDTH, 1600, TIFF.TYPE_SHORT));
|
||||
entries.add(new AbstractEntry(TIFF.TAG_IMAGE_HEIGHT, 900) {});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user