ソース
package com.kmaebashi.exifreadertest;
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.nio.ByteOrder;
public class ExifReader {
public static void main(String[] args) {
String filePath = "JPEGファイルのパスをここに書いてください";
try (DataInputStream inStream = new DataInputStream(
new BufferedInputStream(
new FileInputStream(filePath)))) {
byte[] headersByte = new byte[12];
int[] headers;
int app1Size;
if (inStream.read(headersByte) != headersByte.length) {
System.err.println("ファイルが小さすぎます。");
System.exit(1);
}
headers = unsignedByteArrayToIntArray(headersByte);
if (headers[0] != 0xff || headers[1] != 0xd8) {
System.err.println("JPEGファイルではありません。");
System.exit(1);
}
if (headers[2] != 0xff || headers[3] != 0xe1) {
System.err.println("APP1データが含まれません。");
System.exit(1);
}
app1Size = read2Byte(headers, 4, ByteOrder.BIG_ENDIAN);
if (headers[6] != (byte)'E'
|| headers[7] != (byte)'x'
|| headers[8] != (byte)'i'
|| headers[9] != (byte)'f'
|| headers[10] != 0x00 || headers[11] != 0x00) {
System.err.println("Exifデータが含まれません。");
System.exit(1);
}
byte[] app1DataByte = new byte[app1Size];
if (inStream.read(app1DataByte) < app1Size) {
System.err.println("ヘッダ情報に対し、ファイルサイズが小さすぎます。");
System.exit(1);
}
int[] app1Data = unsignedByteArrayToIntArray(app1DataByte);
ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN;
if (app1Data[0] == 0x49 && app1Data[1] == 0x49) {
byteOrder = ByteOrder.LITTLE_ENDIAN;
} else if (app1Data[0] == 0x4d && app1Data[1] == 0x4d) {
byteOrder = ByteOrder.BIG_ENDIAN;
} else {
System.err.println("バイトオーダーが不正です。");
System.exit(1);
}
if (read2Byte(app1Data, 2, byteOrder) != 0x002a) {
System.err.println("TIFF識別子が002aではありません。");
System.exit(1);
}
int offsetOf0thIFD = read4Byte(app1Data, 4, byteOrder);
System.out.println("0thIFDのオフセット(たいてい8)…" + offsetOf0thIFD);
int numOf0thIFDTags = read2Byte(app1Data, offsetOf0thIFD, byteOrder);
System.out.println("0thIFDのタグの数…" + numOf0thIFDTags);
System.out.println("**** 0th IFD ****");
for (int i = 0; i < numOf0thIFDTags; i++) {
dumpIFDTag(app1Data, 8 + 2 + i * 12, byteOrder);
}
if (exifOffset >= 0) {
int numOfExifIFDTags = read2Byte(app1Data, exifOffset, byteOrder);
System.out.println("**** EXIF IFD ****");
System.out.println("Exif IFDのタグの数…" + numOfExifIFDTags);
for (int i = 0; i < numOfExifIFDTags; i++) {
dumpIFDTag(app1Data, exifOffset + 2 + i * 12, byteOrder);
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(2);
}
}
private static class TagType {
public String name;
public int size;
public TagType(String name, int size) {
this.name = name;
this.size = size;
}
}
private static TagType[] tagTypeData = {
null,
new TagType("BYTE", 1),
new TagType("ASCII", 1),
new TagType("SHORT", 2),
new TagType("LONG", 4),
new TagType("RATIONAL", 8),
null,
new TagType("UNDEFINED", 1),
null,
new TagType("SLONG", 4),
new TagType("SRATIONAL", 8),
};
private static int exifOffset = -1;
private static void dumpIFDTag(int[] array, int offset, ByteOrder byteOrder) {
int tagNo = read2Byte(array, offset, byteOrder);
int tagType = read2Byte(array, offset + 2, byteOrder);
int numOfValues = read4Byte(array, offset + 4, byteOrder);
System.out.print(String.format("%04x", tagNo) + ":"
+ tagTypeData[tagType].name + ":"
+ numOfValues + ":");
if (tagTypeData[tagType].size * numOfValues <= 4) {
printTagValue(array, offset + 8, tagType, numOfValues, byteOrder);
System.out.println("");
} else {
int valueOffset = read4Byte(array, offset + 8, byteOrder);
printTagValue(array, valueOffset, tagType, numOfValues, byteOrder);
System.out.println("");
}
if (tagNo == 0x8769) {
exifOffset = read4Byte(array, offset + 8, byteOrder);
}
}
private static void printTagValue(int[] array, int offset, int tagType, int numOfValues, ByteOrder byteOrder) {
for (int i = 0; i < numOfValues; i++) {
if (i > 0) {
System.out.print(", ");
}
if (tagTypeData[tagType].name == "ASCII") {
if (array[offset + i] == 0) {
System.out.print("'\\0'");
} else {
System.out.print("" + (char)array[offset + i]);
}
} else if (tagTypeData[tagType].size == 1) {
int value = array[offset + i];
System.out.print(String.format("%02x", value));
} else if (tagTypeData[tagType].size == 2) {
int value = read2Byte(array, offset + (i * 2), byteOrder);
System.out.print(String.format("%04x", value));
} else if (tagTypeData[tagType].size == 4) {
int value = read4Byte(array, offset + (i * 4), byteOrder);
System.out.print(String.format("%08x", value));
} else if (tagTypeData[tagType].size == 8) {
int numerator = read4Byte(array, offset + (i * 8), byteOrder);
int denominator = read4Byte(array, offset + (i * 8) + 4, byteOrder);
System.out.print("" + numerator + "/" + denominator);
}
}
}
private static int[] unsignedByteArrayToIntArray(byte[] src) {
int[] dest = new int[src.length];
for (int i = 0; i < src.length; i++) {
dest[i] = Byte.toUnsignedInt(src[i]);
}
return dest;
}
public static int read2Byte(int[] array, int offset, ByteOrder byteOrder) {
int ret;
if (byteOrder == ByteOrder.BIG_ENDIAN) {
ret = array[offset] * 256 + array[offset + 1];
} else {
ret = array[offset + 1] * 256 + array[offset];
}
return ret;
}
public static int read4Byte(int[] array, int offset, ByteOrder byteOrder) {
int ret;
if (byteOrder == ByteOrder.BIG_ENDIAN) {
ret = array[offset] * (256 * 256 * 256) + array[offset + 1] * 65536
+ array[offset + 2] * 256 + array[offset + 3];
} else {
ret = array[offset + 3] * (256 * 256 * 256) + array[offset + 2] * 65536
+ array[offset + 1] * 256 + array[offset];
}
return ret;
}
}
出力例
Galaxy Note 8で撮った縦長写真です。先日しまなみ海道に行った時のやつ。
0thIFDのオフセット(たいてい8)…8
0thIFDのタグの数…13
**** 0th IFD ****
0100:LONG:1:00000fc0
0101:LONG:1:00000bd0
010f:ASCII:8:s, a, m, s, u, n, g, '\0'
0110:ASCII:7:S, C, -, 0, 1, K, '\0'
0112:SHORT:1:0006 ←これが画像の向きを示す。6は、時計回りに90°回せば元に戻る向き。
011a:RATIONAL:1:72/1
011b:RATIONAL:1:72/1
0128:SHORT:1:0002
0131:ASCII:14:S, C, 0, 1, K, O, M, U, 1, C, S, G, 3, '\0'
0132:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4, , 1, 8, :, 0, 0, :, 5, 4, '\0'
0213:SHORT:1:0001
8769:LONG:1:000000ec
8825:LONG:1:0000175a
**** EXIF IFD ****
Exif IFDのタグの数…31
829a:RATIONAL:1:1/40
829d:RATIONAL:1:170/100
8822:SHORT:1:0002
8827:SHORT:1:00c8
9000:UNDEFINED:4:30, 32, 32, 30
9003:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4, , 1, 8, :, 0, 0, :, 5, 4, '\0'
9004:ASCII:20:2, 0, 1, 9, :, 0, 9, :, 1, 4, , 1, 8, :, 0, 0, :, 5, 4, '\0'
9101:UNDEFINED:4:01, 02, 03, 00
9201:SRATIONAL:1:5321/1000
9202:RATIONAL:1:153/100
9203:SRATIONAL:1:93/100
9204:SRATIONAL:1:0/10
9205:RATIONAL:1:153/100
9207:SHORT:1:0002
9208:SHORT:1:0000
9209:SHORT:1:0000
920a:RATIONAL:1:430/100
927c:UNDEFINED:98:07, 00, 01, 00, 07, 00, 04, 00, 00, 00, 30, 31, 30, 30, 02, 00, 04, 00, 01, 00, 00, 00, 00, 20, 01, 00, 0c, 00, 04, 00, 01, 00, 00, 00, 00, 00, 00, 00, 10, 00, 05, 00, 01, 00, 00, 00, 5a, 00, 00, 00, 40, 00, 04, 00, 01, 00, 00, 00, 00, 00, 00, 00, 50, 00, 04, 00, 01, 00, 00, 00, 01, 00, 00, 00, 00, 01, 03, 00, 01, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00
9286:UNDEFINED:5107:41, 53, 43, 49, 49, 00, 00, 00, 0a, 00, 00, 00, <なんかいっぱい出てるので中略> 30, 20, 00
a000:UNDEFINED:4:30, 31, 30, 30
a001:SHORT:1:0001
a002:LONG:1:00000fc0
a003:LONG:1:00000bd0
a005:LONG:1:0000173c
a217:SHORT:1:0002
a301:UNDEFINED:1:01
a402:SHORT:1:0000
a403:SHORT:1:0000
a405:SHORT:1:001a
a406:SHORT:1:0000
a420:ASCII:24:G, 1, 2, Q, S, K, A, 0, 2, S, M, , G, 1, 2, Q, S, K, D, 0, 1, S, A, '\0'