UWSCでEXIF情報を読んでみる

前に、写真の撮影日時の秒単位がShell.Applicationでは取れない、というのがあった。
stuncloudさんが回答してくれた「Wia.ImageFile」がベストだけど、気が向いたのでバイナリーを読んでみた。



スクリプト

Binaryモジュールに依存しています。
UWSCでバイナリーファイルの読み書き - じゅんじゅんのきまぐれ

OPTION EXPLICIT

CALL ..\lib\Binary

DIM h = Binary.Open(INPUT("target jpeg file?"))
IF h = Binary.INVALID_HANDLE OR !Binary.Exist(h) THEN EXITEXIT

CONST BYTE_NUM = 256

// jpegファイルヘッダー確認
DIM buf = Binary.ReadArray(h, 2)
IFB buf[0] = $ff AND buf[1] = $d8 THEN
	buf = Binary.ReadArray(h, 4)
	IFB buf[0] = $ff AND buf[1] = $e0 THEN
		buf = Binary.ReadArray(h, ConvNum(buf, 2, 2) - 2)
		buf = Binary.ReadArray(h, 4)
	ENDIF
	IFB buf[0] = $ff AND buf[1] = $e1 THEN
		DIM size = ConvNum(buf, 2, 2)
		// Exif確認
		buf = Binary.ReadArray(h, 14)
		IFB buf[0] = $45 AND buf[1] = $78 AND buf[2] = $69 AND buf[3] = $66 AND buf[4] = 0 AND buf[5] = 0 THEN
			size = size - 6
			DIM e = 0
			IFB buf[6] = $4d AND buf[7] = buf[6] AND buf[8] = 0 AND buf[9] = $2a THEN
				// big endian
				e = 1
			ELSEIF buf[6] = $49 AND buf[7] = buf[6] AND buf[8] = $2a AND buf[9] = 0 THEN
				// little endian
				e = -1
			ELSE
				MSGBOX("invalid endian")
			ENDIF
			IFB e THEN
				DIM offset = ConvNum(buf, 10, 4, e) - 8
				IF offset > 0 THEN buf = Binary.ReadArray(h, offset)
				offset = offset + 8	// TIFFヘッダーからのオフセットに補正
				WHILE size > offset
					offset = ReadTag(h, e, offset)
				WEND
			ENDIF
		ELSE
			MSGBOX("not tiff")
		ENDIF
	ELSE
		MSGBOX("not exif")
	ENDIF
ELSE
	MSGBOX("not jpeg")
ENDIF

Binary.Close(h)


FUNCTION ReadTag(h, e, offset)
	DIM tagNum, i, tag, typ, num, val, nxt, buf
	buf = Binary.ReadArray(h, 2)
	tagNum = ConvNum(buf, 0, 2, e)
PRINT offset + " " + tagNum
	num = 12 * tagNum + 4
	buf = Binary.ReadArray(h, num)
	offset = offset + num + 2
	FOR i = 0 TO tagNum - 1
		tag = ConvNum(buf, 12 * i + 0, 2, e)
		typ = ConvNum(buf, 12 * i + 2, 2, e)
		num = ConvNum(buf, 12 * i + 4, 4, e)
		val = ConvNum(buf, 12 * i + 8, 4, e)
		offset = ReadData(h, e, typ, num, val, offset)
		IF offset = $FFFFFFFF THEN BREAK
PRINT i + " " + tag + " " + typ + " " + num + " " + val
		IFB tag = 34665 AND i < tagNum - 1 THEN
			IFB val > offset THEN
				Binary.ReadArray(h, val - offset)
				offset = val
			ENDIF
			IFB val = offset THEN
				offset = ReadTag(h, e, offset)
			ELSE
				MSGBOX("シーケンシャルリード失敗 Exif")
			ENDIF
		ENDIF
	NEXT
	nxt = ConvNum(buf, 12 * tagNum, 4, e)
	RESULT = offset
FEND

FUNCTION ReadData(h, e, typ, num, var val, offset)
	RESULT = offset
	DIM r = 0, i, buf
	SELECT typ
	CASE 1	// BYTE
		IF num > 4 THEN r = 1
	CASE 2	// ASCII
		IFB num > 4 THEN
			r = -1
		ELSE
			buf = val
			val = ""
			FOR i = 0 TO num - 1
				val = val + CHR(INT(buf / POWER($100, 4 - i - 1)) AND $FF)
			NEXT
		ENDIF
	CASE 3	// SHORT
		IF num > 2 THEN r = 2
	CASE 4	// LONG
		IF num > 1 THEN r = 4
	CASE 5	// RATIONAL(long*2)
		r = 8
	CASE 7	// UNDEFINED(BYTE)
		IF num > 4 THEN r = 1
	CASE 9	// SLONG
		IFB num > 1 THEN
			r = 4
		ELSEIF val > $7fffffff THEN
			val = val - $100000000
		ENDIF
	CASE 10	// SRATIONAL(slong*2)
		r = -8
	DEFAULT
		MSGBOX("invalid tag type")
		RESULT = $FFFFFFFF
	SELEND
	IFB r THEN
		IFB RESULT < val THEN
			Binary.ReadArray(h, val - RESULT)
			RESULT = val
		ENDIF
		DIM toStr = (r = -1), sr = (r = -8)
		IF toStr THEN r = 1
		IF sr THEN r = 8
		IFB RESULT = val THEN
			val = Binary.ReadArray(h, num * r)
			RESULT = RESULT + num * r
		ELSE
			toStr = FALSE
			MSGBOX("シーケンシャルリード失敗")
		ENDIF
		IFB r = 8 THEN
			buf = ConvNum(val, 0, 4, e)
			IF sr AND buf > $7fffffff THEN buf = buf - $10000000
			i = ConvNum(val, 4, 4, e)
			IF sr AND i > $7fffffff THEN i = i - $100000000
			val = buf / i
		ENDIF
		IFB toStr THEN
			buf = val
			val = ""
			FOR i = 0 TO num - 1
				val = val + CHR(buf[i])
			NEXT
		ENDIF
	ENDIF
FEND

FUNCTION ConvNum(buf, offset, len, endian=1)
	RESULT = 0
	DIM i
	FOR i = offset TO len + offset - 1
		IF endian > 0 THEN RESULT = RESULT * BYTE_NUM + buf[i] ELSE RESULT = RESULT * BYTE_NUM + buf[len + offset * 2 - 1 - i]
	NEXT
FEND

けんしのページ - Exifファイルフォーマット -
を見ながら適当に書きました。
番号の意味もそこらへんを見てください。