mirror of
https://github.com/haraldk/TwelveMonkeys.git
synced 2025-08-05 04:25:29 -04:00
Various fixes for metadata parsing.
- Added more TIFF/EXIF tags - Clean-up of JPEG segment reading - Better toString in general and XMP specific
This commit is contained in:
parent
2a282cf8e4
commit
5d3fb34e49
@ -109,6 +109,10 @@ public abstract class AbstractEntry implements Entry {
|
|||||||
return String.valueOf(value) + " (" + valueCount() + ")";
|
return String.valueOf(value) + " (" + valueCount() + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value.getClass().isArray() && Array.getLength(value) == 1) {
|
||||||
|
return String.valueOf(Array.get(value, 0));
|
||||||
|
}
|
||||||
|
|
||||||
return String.valueOf(value);
|
return String.valueOf(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +133,8 @@ public abstract class AbstractEntry implements Entry {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Object
|
/// Object
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return identifier.hashCode() + 31 * value.hashCode();
|
return identifier.hashCode() + 31 * value.hashCode();
|
||||||
|
@ -36,7 +36,62 @@ package com.twelvemonkeys.imageio.metadata.exif;
|
|||||||
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
* @version $Id: EXIF.java,v 1.0 Nov 11, 2009 5:36:04 PM haraldk Exp$
|
||||||
*/
|
*/
|
||||||
public interface EXIF {
|
public interface EXIF {
|
||||||
|
// See http://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif.html
|
||||||
|
int TAG_EXPOSURE_TIME = 33434;
|
||||||
|
int TAG_F_NUMBER = 33437;
|
||||||
|
int TAG_EXPOSURE_PROGRAM = 34850;
|
||||||
|
int TAG_SPECTRAL_SENSITIVITY = 34852;
|
||||||
|
int TAG_ISO_SPEED_RATINGS = 34855;
|
||||||
|
int TAG_OECF = 34856;
|
||||||
|
int TAG_EXIF_VERSION = 36864;
|
||||||
|
int TAG_DATE_TIME_ORIGINAL = 36867;
|
||||||
|
int TAG_DATE_TIME_DIGITIZED = 36868;
|
||||||
|
int TAG_COMPONENTS_CONFIGURATION = 37121;
|
||||||
|
int TAG_COMPRESSED_BITS_PER_PIXEL = 37122;
|
||||||
|
int TAG_SHUTTER_SPEED_VALUE = 37377;
|
||||||
|
int TAG_APERTURE_VALUE = 37378;
|
||||||
|
int TAG_BRIGHTNESS_VALUE = 37379;
|
||||||
|
int TAG_EXPOSURE_BIAS_VALUE = 37380;
|
||||||
|
int TAG_MAX_APERTURE_VALUE = 37381;
|
||||||
|
int TAG_SUBJECT_DISTANCE = 37382;
|
||||||
|
int TAG_METERING_MODE = 37383;
|
||||||
|
int TAG_LIGHT_SOURCE = 37384;
|
||||||
|
int TAG_FLASH = 37385;
|
||||||
|
int TAG_FOCAL_LENGTH = 37386;
|
||||||
|
int TAG_IMAGE_NUMBER = 37393;
|
||||||
|
int TAG_SUBJECT_AREA = 37396;
|
||||||
|
int TAG_MAKER_NOTE = 37500;
|
||||||
|
int TAG_USER_COMMENT = 37510;
|
||||||
|
int TAG_SUBSEC_TIME = 37520;
|
||||||
|
int TAG_SUBSEC_TIME_ORIGINAL = 37521;
|
||||||
|
int TAG_SUBSEC_TIME_DIGITIZED = 37522;
|
||||||
|
int TAG_FLASHPIX_VERSION = 40960;
|
||||||
int TAG_COLOR_SPACE = 40961;
|
int TAG_COLOR_SPACE = 40961;
|
||||||
int TAG_PIXEL_X_DIMENSION = 40962;
|
int TAG_PIXEL_X_DIMENSION = 40962;
|
||||||
int TAG_PIXEL_Y_DIMENSION = 40963;
|
int TAG_PIXEL_Y_DIMENSION = 40963;
|
||||||
|
int TAG_RELATED_SOUND_FILE = 40964;
|
||||||
|
int TAG_FLASH_ENERGY = 41483;
|
||||||
|
int TAG_SPATIAL_FREQUENCY_RESPONSE = 41484;
|
||||||
|
int TAG_FOCAL_PLANE_X_RESOLUTION = 41486;
|
||||||
|
int TAG_FOCAL_PLANE_Y_RESOLUTION = 41487;
|
||||||
|
int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 41488;
|
||||||
|
int TAG_SUBJECT_LOCATION = 41492;
|
||||||
|
int TAG_EXPOSURE_INDEX = 41493;
|
||||||
|
int TAG_SENSING_METHOD = 41495;
|
||||||
|
int TAG_FILE_SOURCE = 41728;
|
||||||
|
int TAG_SCENE_TYPE = 41729;
|
||||||
|
int TAG_CFA_PATTERN = 41730;
|
||||||
|
int TAG_CUSTOM_RENDERED = 41985;
|
||||||
|
int TAG_EXPOSURE_MODE = 41986;
|
||||||
|
int TAG_WHITE_BALANCE = 41987;
|
||||||
|
int TAG_DIGITAL_ZOOM_RATIO = 41988;
|
||||||
|
int TAG_FOCAL_LENGTH_IN_35_MM_FILM = 41989;
|
||||||
|
int TAG_SCENE_CAPTURE_TYPE = 41990;
|
||||||
|
int TAG_GAIN_CONTROL = 41991;
|
||||||
|
int TAG_CONTRAST = 41992;
|
||||||
|
int TAG_SATURATION = 41993;
|
||||||
|
int TAG_SHARPNESS = 41994;
|
||||||
|
int TAG_DEVICE_SETTING_DESCRIPTION = 41995;
|
||||||
|
int TAG_SUBJECT_DISTANCE_RANGE = 41996;
|
||||||
|
int TAG_IMAGE_UNIQUE_ID = 42016;
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
case TIFF.TAG_PHOTOSHOP:
|
case TIFF.TAG_PHOTOSHOP:
|
||||||
return "Adobe";
|
return "Adobe";
|
||||||
case TIFF.TAG_ICC_PROFILE:
|
case TIFF.TAG_ICC_PROFILE:
|
||||||
return "ICC Profile";
|
return "ICCProfile";
|
||||||
|
|
||||||
case TIFF.TAG_IMAGE_WIDTH:
|
case TIFF.TAG_IMAGE_WIDTH:
|
||||||
return "ImageWidth";
|
return "ImageWidth";
|
||||||
@ -86,15 +86,41 @@ final class EXIFEntry extends AbstractEntry {
|
|||||||
return "JPEGInterchangeFormat";
|
return "JPEGInterchangeFormat";
|
||||||
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
case TIFF.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH:
|
||||||
return "JPEGInterchangeFormatLength";
|
return "JPEGInterchangeFormatLength";
|
||||||
|
case TIFF.TAG_MAKE:
|
||||||
|
return "Make";
|
||||||
|
case TIFF.TAG_MODEL:
|
||||||
|
return "Model";
|
||||||
case TIFF.TAG_SOFTWARE:
|
case TIFF.TAG_SOFTWARE:
|
||||||
return "Software";
|
return "Software";
|
||||||
case TIFF.TAG_DATE_TIME:
|
case TIFF.TAG_DATE_TIME:
|
||||||
return "DateTime";
|
return "DateTime";
|
||||||
case TIFF.TAG_ARTIST:
|
case TIFF.TAG_ARTIST:
|
||||||
return "Artist";
|
return "Artist";
|
||||||
|
case TIFF.TAG_HOST_COMPUTER:
|
||||||
|
return "HostComputer";
|
||||||
case TIFF.TAG_COPYRIGHT:
|
case TIFF.TAG_COPYRIGHT:
|
||||||
return "Copyright";
|
return "Copyright";
|
||||||
|
|
||||||
|
case EXIF.TAG_EXPOSURE_TIME:
|
||||||
|
return "ExposureTime";
|
||||||
|
case EXIF.TAG_F_NUMBER:
|
||||||
|
return "FNUmber";
|
||||||
|
case EXIF.TAG_EXPOSURE_PROGRAM:
|
||||||
|
return "ExposureProgram";
|
||||||
|
case EXIF.TAG_ISO_SPEED_RATINGS:
|
||||||
|
return "ISOSpeedRatings";
|
||||||
|
|
||||||
|
case EXIF.TAG_EXIF_VERSION:
|
||||||
|
return "ExifVersion";
|
||||||
|
case EXIF.TAG_DATE_TIME_ORIGINAL:
|
||||||
|
return "DateTimeOriginal";
|
||||||
|
case EXIF.TAG_DATE_TIME_DIGITIZED:
|
||||||
|
return "DateTimeDigitized";
|
||||||
|
case EXIF.TAG_IMAGE_NUMBER:
|
||||||
|
return "ImageNumber";
|
||||||
|
case EXIF.TAG_USER_COMMENT:
|
||||||
|
return "UserComment";
|
||||||
|
|
||||||
case EXIF.TAG_COLOR_SPACE:
|
case EXIF.TAG_COLOR_SPACE:
|
||||||
return "ColorSpace";
|
return "ColorSpace";
|
||||||
case EXIF.TAG_PIXEL_X_DIMENSION:
|
case EXIF.TAG_PIXEL_X_DIMENSION:
|
||||||
|
@ -59,6 +59,7 @@ public final class EXIFReader extends MetadataReader {
|
|||||||
public Directory read(final ImageInputStream input) throws IOException {
|
public Directory read(final ImageInputStream input) throws IOException {
|
||||||
byte[] bom = new byte[2];
|
byte[] bom = new byte[2];
|
||||||
input.readFully(bom);
|
input.readFully(bom);
|
||||||
|
|
||||||
if (bom[0] == 'I' && bom[1] == 'I') {
|
if (bom[0] == 'I' && bom[1] == 'I') {
|
||||||
input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
input.setByteOrder(ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
@ -102,6 +103,7 @@ public final class EXIFReader extends MetadataReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data?
|
// TODO: Make what sub-IFDs to parse optional? Or leave this to client code? At least skip the non-TIFF data?
|
||||||
|
// TODO: Put it in the constructor?
|
||||||
readSubdirectories(pInput, entries,
|
readSubdirectories(pInput, entries,
|
||||||
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD
|
Arrays.asList(TIFF.TAG_EXIF_IFD, TIFF.TAG_GPS_IFD, TIFF.TAG_INTEROP_IFD
|
||||||
// , TIFF.TAG_IPTC, TIFF.TAG_XMP
|
// , TIFF.TAG_IPTC, TIFF.TAG_XMP
|
||||||
|
@ -131,6 +131,7 @@ public interface TIFF {
|
|||||||
int TAG_MODEL = 272;
|
int TAG_MODEL = 272;
|
||||||
int TAG_SOFTWARE = 305;
|
int TAG_SOFTWARE = 305;
|
||||||
int TAG_ARTIST = 315;
|
int TAG_ARTIST = 315;
|
||||||
|
int TAG_HOST_COMPUTER = 316;
|
||||||
int TAG_COPYRIGHT = 33432;
|
int TAG_COPYRIGHT = 33432;
|
||||||
|
|
||||||
int TAG_SUB_IFD = 330;
|
int TAG_SUB_IFD = 330;
|
||||||
|
@ -43,16 +43,19 @@ import java.util.Arrays;
|
|||||||
public final class JPEGSegment implements Serializable {
|
public final class JPEGSegment implements Serializable {
|
||||||
final int marker;
|
final int marker;
|
||||||
final byte[] data;
|
final byte[] data;
|
||||||
|
final int length;
|
||||||
|
|
||||||
private transient String id;
|
private transient String id;
|
||||||
|
|
||||||
JPEGSegment(int marker, byte[] data) {
|
JPEGSegment(int marker, byte[] data, int length) {
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
|
this.length = length;
|
||||||
}
|
}
|
||||||
|
|
||||||
int segmentLength() {
|
int segmentLength() {
|
||||||
// This is the length field as read from the stream
|
// This is the length field as read from the stream
|
||||||
return data != null ? data.length + 2 : 0;
|
return length;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int marker() {
|
public int marker() {
|
||||||
@ -61,7 +64,7 @@ public final class JPEGSegment implements Serializable {
|
|||||||
|
|
||||||
public String identifier() {
|
public String identifier() {
|
||||||
if (id == null) {
|
if (id == null) {
|
||||||
if (marker >= 0xFFE0 && marker <= 0xFFEF) {
|
if (isAppSegmentMarker(marker)) {
|
||||||
// Only for APPn markers
|
// Only for APPn markers
|
||||||
id = JPEGSegmentUtil.asNullTerminatedAsciiString(data, 0);
|
id = JPEGSegmentUtil.asNullTerminatedAsciiString(data, 0);
|
||||||
}
|
}
|
||||||
@ -70,6 +73,10 @@ public final class JPEGSegment implements Serializable {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static boolean isAppSegmentMarker(final int marker) {
|
||||||
|
return marker >= 0xFFE0 && marker <= 0xFFEF;
|
||||||
|
}
|
||||||
|
|
||||||
public InputStream data() {
|
public InputStream data() {
|
||||||
return data != null ? new ByteArrayInputStream(data, offset(), length()) : null;
|
return data != null ? new ByteArrayInputStream(data, offset(), length()) : null;
|
||||||
}
|
}
|
||||||
@ -80,26 +87,31 @@ public final class JPEGSegment implements Serializable {
|
|||||||
|
|
||||||
private int offset() {
|
private int offset() {
|
||||||
String identifier = identifier();
|
String identifier = identifier();
|
||||||
|
|
||||||
return identifier == null ? 0 : identifier.length() + 1;
|
return identifier == null ? 0 : identifier.length() + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String identifier = identifier();
|
String identifier = identifier();
|
||||||
|
|
||||||
if (identifier != null) {
|
if (identifier != null) {
|
||||||
return String.format("JPEGSegment[%04x/%s size: %d]", marker, identifier, segmentLength());
|
return String.format("JPEGSegment[%04x/%s size: %d]", marker, identifier, segmentLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
return String.format("JPEGSegment[%04x size: %d]", marker, segmentLength());
|
return String.format("JPEGSegment[%04x size: %d]", marker, segmentLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
String identifier = identifier();
|
String identifier = identifier();
|
||||||
|
|
||||||
return marker() << 16 | (identifier != null ? identifier.hashCode() : 0) & 0xFFFF;
|
return marker() << 16 | (identifier != null ? identifier.hashCode() : 0) & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(final Object other) {
|
||||||
return other instanceof JPEGSegment && ((JPEGSegment) other).marker == marker && Arrays.equals(((JPEGSegment) other).data, data);
|
return other instanceof JPEGSegment &&
|
||||||
|
((JPEGSegment) other).marker == marker && Arrays.equals(((JPEGSegment) other).data, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,9 +28,17 @@
|
|||||||
|
|
||||||
package com.twelvemonkeys.imageio.metadata.jpeg;
|
package com.twelvemonkeys.imageio.metadata.jpeg;
|
||||||
|
|
||||||
|
import com.twelvemonkeys.imageio.metadata.Directory;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.exif.EXIFReader;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.xmp.XMP;
|
||||||
|
import com.twelvemonkeys.imageio.metadata.xmp.XMPReader;
|
||||||
|
|
||||||
import javax.imageio.IIOException;
|
import javax.imageio.IIOException;
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
import javax.imageio.stream.ImageInputStream;
|
import javax.imageio.stream.ImageInputStream;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -44,20 +52,10 @@ import java.util.*;
|
|||||||
public final class JPEGSegmentUtil {
|
public final class JPEGSegmentUtil {
|
||||||
public static final List<String> ALL_IDS = Collections.unmodifiableList(new AllIdsList());
|
public static final List<String> ALL_IDS = Collections.unmodifiableList(new AllIdsList());
|
||||||
public static final Map<Integer, List<String>> ALL_SEGMENTS = Collections.unmodifiableMap(new AllSegmentsMap());
|
public static final Map<Integer, List<String>> ALL_SEGMENTS = Collections.unmodifiableMap(new AllSegmentsMap());
|
||||||
public static final Map<Integer, List<String>> APP_SEGMENTS = Collections.unmodifiableMap(createAppSegmentsMap());
|
public static final Map<Integer, List<String>> APP_SEGMENTS = Collections.unmodifiableMap(new AllAppSegmentsMap());
|
||||||
|
|
||||||
private JPEGSegmentUtil() {}
|
private JPEGSegmentUtil() {}
|
||||||
|
|
||||||
private static Map<Integer, List<String>> createAppSegmentsMap() {
|
|
||||||
Map<Integer, List<String>> identifiers = new HashMap<Integer, List<String>>();
|
|
||||||
|
|
||||||
for (int i = 0xFFE0; i <= 0xFFEF; i++) {
|
|
||||||
identifiers.put(i, JPEGSegmentUtil.ALL_IDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
return identifiers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the requested JPEG segments from the stream.
|
* Reads the requested JPEG segments from the stream.
|
||||||
* The stream position must be directly before the SOI marker, and only segments for the current image is read.
|
* The stream position must be directly before the SOI marker, and only segments for the current image is read.
|
||||||
@ -97,7 +95,6 @@ public final class JPEGSegmentUtil {
|
|||||||
JPEGSegment segment;
|
JPEGSegment segment;
|
||||||
try {
|
try {
|
||||||
while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) {
|
while (!isImageDone(segment = readSegment(stream, segmentIdentifiers))) {
|
||||||
// while (!isImageDone(segment = readSegment(stream, ALL_SEGMENTS))) {
|
|
||||||
// System.err.println("segment: " + segment);
|
// System.err.println("segment: " + segment);
|
||||||
|
|
||||||
if (isRequested(segment, segmentIdentifiers)) {
|
if (isRequested(segment, segmentIdentifiers)) {
|
||||||
@ -119,9 +116,8 @@ public final class JPEGSegmentUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isRequested(JPEGSegment segment, Map<Integer, List<String>> segmentIdentifiers) {
|
private static boolean isRequested(JPEGSegment segment, Map<Integer, List<String>> segmentIdentifiers) {
|
||||||
return segmentIdentifiers == ALL_SEGMENTS ||
|
return (segmentIdentifiers.containsKey(segment.marker) &&
|
||||||
(segmentIdentifiers.containsKey(segment.marker) && (segmentIdentifiers.get(segment.marker) == ALL_IDS ||
|
(segment.identifier() == null && segmentIdentifiers.get(segment.marker) == null || containsSafe(segment, segmentIdentifiers)));
|
||||||
(segment.identifier() == null && segmentIdentifiers.get(segment.marker) == null || containsSafe(segment, segmentIdentifiers))));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean containsSafe(JPEGSegment segment, Map<Integer, List<String>> segmentIdentifiers) {
|
private static boolean containsSafe(JPEGSegment segment, Map<Integer, List<String>> segmentIdentifiers) {
|
||||||
@ -160,16 +156,31 @@ public final class JPEGSegmentUtil {
|
|||||||
|
|
||||||
byte[] data;
|
byte[] data;
|
||||||
|
|
||||||
if (segmentIdentifiers == ALL_SEGMENTS || segmentIdentifiers.containsKey(marker)) {
|
if (segmentIdentifiers.containsKey(marker)) {
|
||||||
data = new byte[length - 2];
|
data = new byte[length - 2];
|
||||||
stream.readFully(data);
|
stream.readFully(data);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data = null;
|
if (JPEGSegment.isAppSegmentMarker(marker)) {
|
||||||
stream.skipBytes(length - 2);
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream(32);
|
||||||
|
int read;
|
||||||
|
|
||||||
|
// NOTE: Read until null-termination (0) or EOF
|
||||||
|
while ((read = stream.read()) > 0) {
|
||||||
|
buffer.write(read);
|
||||||
|
}
|
||||||
|
|
||||||
|
data = buffer.toByteArray();
|
||||||
|
|
||||||
|
stream.skipBytes(length - 3 - data.length);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
data = null;
|
||||||
|
stream.skipBytes(length - 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new JPEGSegment(marker, data);
|
return new JPEGSegment(marker, data, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AllIdsList extends ArrayList<String> {
|
private static class AllIdsList extends ArrayList<String> {
|
||||||
@ -177,6 +188,11 @@ public final class JPEGSegmentUtil {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "[All ids]";
|
return "[All ids]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean contains(Object o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class AllSegmentsMap extends HashMap<Integer, List<String>> {
|
private static class AllSegmentsMap extends HashMap<Integer, List<String>> {
|
||||||
@ -184,5 +200,90 @@ public final class JPEGSegmentUtil {
|
|||||||
public String toString() {
|
public String toString() {
|
||||||
return "{All segments}";
|
return "{All segments}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> get(Object key) {
|
||||||
|
return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key) ? ALL_IDS : null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AllAppSegmentsMap extends HashMap<Integer, List<String>> {
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "{All APPn segments}";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> get(Object key) {
|
||||||
|
return containsKey(key) ? ALL_IDS : null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(Object key) {
|
||||||
|
return key instanceof Integer && JPEGSegment.isAppSegmentMarker((Integer) key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws IOException {
|
||||||
|
List<JPEGSegment> segments = readSegments(ImageIO.createImageInputStream(new File(args[0])), ALL_SEGMENTS);
|
||||||
|
|
||||||
|
for (JPEGSegment segment : segments) {
|
||||||
|
System.err.println("segment: " + segment);
|
||||||
|
|
||||||
|
if ("Exif".equals(segment.identifier())) {
|
||||||
|
InputStream data = segment.data();
|
||||||
|
//noinspection ResultOfMethodCallIgnored
|
||||||
|
data.read(); // Pad
|
||||||
|
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(data);
|
||||||
|
|
||||||
|
// Root entry is TIFF, that contains the EXIF sub-IFD
|
||||||
|
Directory tiff = new EXIFReader().read(stream);
|
||||||
|
System.err.println("EXIF: " + tiff);
|
||||||
|
}
|
||||||
|
else if (XMP.NS_XAP.equals(segment.identifier())) {
|
||||||
|
Directory xmp = new XMPReader().read(ImageIO.createImageInputStream(segment.data()));
|
||||||
|
System.err.println("XMP: " + xmp);
|
||||||
|
}
|
||||||
|
else if ("Photoshop 3.0".equals(segment.identifier())) {
|
||||||
|
// TODO: It's probably a good idea to move some of the Photoshop ImageResource parsing code
|
||||||
|
// to the metadata sub project, as it may be contained in other formats (such as JFIF).
|
||||||
|
// TODO: The "Photoshop 3.0" segment contains several image resources, of which one might contain
|
||||||
|
// IPTC metadata. Probably duplicated in the XMP though...
|
||||||
|
try {
|
||||||
|
Class cl = Class.forName("com.twelvemonkeys.imageio.plugins.psd.PSDImageResource");
|
||||||
|
Method method = cl.getMethod("read", ImageInputStream.class);
|
||||||
|
method.setAccessible(true);
|
||||||
|
ImageInputStream stream = ImageIO.createImageInputStream(segment.data());
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
Object photoShop = method.invoke(null, stream);
|
||||||
|
System.err.println("PhotoShop: " + photoShop);
|
||||||
|
}
|
||||||
|
catch (InvocationTargetException e) {
|
||||||
|
if (e.getTargetException() instanceof EOFException) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ignore) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("ICC_PROFILE".equals(segment.identifier())) {
|
||||||
|
// Skip
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
System.err.println(EXIFReader.HexDump.dump(segment.data));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,4 +54,12 @@ final class XMPEntry extends AbstractEntry {
|
|||||||
public String getFieldName() {
|
public String getFieldName() {
|
||||||
return fieldName != null ? fieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier());
|
return fieldName != null ? fieldName : XMP.DEFAULT_NS_MAPPING.get(getIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String type = getTypeName();
|
||||||
|
String typeStr = type != null ? " (" + type + ")" : "";
|
||||||
|
|
||||||
|
return String.format("%s: %s%s", getIdentifier(), getValueAsString(), typeStr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class JPEGSegmentTest extends ObjectAbstractTestCase {
|
|||||||
byte[] bytes = new byte[14];
|
byte[] bytes = new byte[14];
|
||||||
System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
||||||
|
|
||||||
JPEGSegment segment = new JPEGSegment(0xFFE0, bytes);
|
JPEGSegment segment = new JPEGSegment(0xFFE0, bytes, 16);
|
||||||
|
|
||||||
assertEquals(0xFFE0, segment.marker());
|
assertEquals(0xFFE0, segment.marker());
|
||||||
assertEquals("JFIF", segment.identifier());
|
assertEquals("JFIF", segment.identifier());
|
||||||
@ -60,7 +60,7 @@ public class JPEGSegmentTest extends ObjectAbstractTestCase {
|
|||||||
public void testToStringAppSegment() {
|
public void testToStringAppSegment() {
|
||||||
byte[] bytes = new byte[14];
|
byte[] bytes = new byte[14];
|
||||||
System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
System.arraycopy("JFIF".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
||||||
JPEGSegment segment = new JPEGSegment(0xFFE0, bytes);
|
JPEGSegment segment = new JPEGSegment(0xFFE0, bytes, 16);
|
||||||
|
|
||||||
assertEquals("JPEGSegment[ffe0/JFIF size: 16]", segment.toString());
|
assertEquals("JPEGSegment[ffe0/JFIF size: 16]", segment.toString());
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ public class JPEGSegmentTest extends ObjectAbstractTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testToStringNonAppSegment() {
|
public void testToStringNonAppSegment() {
|
||||||
byte[] bytes = new byte[40];
|
byte[] bytes = new byte[40];
|
||||||
JPEGSegment segment = new JPEGSegment(0xFFC4, bytes);
|
JPEGSegment segment = new JPEGSegment(0xFFC4, bytes, 42);
|
||||||
|
|
||||||
assertEquals("JPEGSegment[ffc4 size: 42]", segment.toString());
|
assertEquals("JPEGSegment[ffc4 size: 42]", segment.toString());
|
||||||
}
|
}
|
||||||
@ -77,6 +77,6 @@ public class JPEGSegmentTest extends ObjectAbstractTestCase {
|
|||||||
protected Object makeObject() {
|
protected Object makeObject() {
|
||||||
byte[] bytes = new byte[11];
|
byte[] bytes = new byte[11];
|
||||||
System.arraycopy("Exif".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
System.arraycopy("Exif".getBytes(Charset.forName("ascii")), 0, bytes, 0, 4);
|
||||||
return new JPEGSegment(0xFFE1, bytes);
|
return new JPEGSegment(0xFFE1, bytes, 16);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user