EXIF : してログ

EXIFでは少数を表すのに、RATIONAL型という分数を使います。 PHPのEXIF関数で取り出し可能ですが、正しく取り出せない場合があるので注意が必要です。 RATIONAL型は、分子と分母で2つの符号なし32bit整数だということですが、PHPのEXIF関数では符号付き32bit整数で取り出してしまうようです。 通常は符号のありなしが、問題になるような桁が出てくるのは稀で、単に分数として計算すれば良いはずです。 しかし、稀にGPSの緯度経度情報でマイナス値が取得されることがあり、対策をしておいた方が良いようです。 (符号なし整数の大きな値は、符号あり整数ではマイナス値となります)

ちなみにPHPの整数値は、環境に依存しており PHP_INT_SIZE で取得(バイト数)できます。 しかし、64bit OS(PHP_INT_SIZE=8 の環境)でもEXIF関数はマイナス値を返しました。 マニュアルを見ると「PHP 7 より前のバージョンにおける Windows は例外で、Windows で PHP 7 より前のバージョンを使う場合はは常に 32 ビットとなります」とあり、それなら 64bit Linux はどうかと試してみましたが、やっぱりだめでした。 この件は、EXIF関数のバグだと思われます。

原因が特定できたので、対策は割りと簡単にできます。 4バイト符号付きを符号なしに読み替えれば良いので、まず16進数に直してから、下位4バイトを残せばいいだけなので、下記のようになります。

hexdec( substr( dechex( $v ), -8 ) );

このコードですが、不思議な事に 32bit 環境でもうまく行きます。 理由は、hexdex 関数のマニュアルに「この関数は、プラットフォームの integer 型に収まらない大きな数も変換できます。 その場合、結果は float で返します。」とあり納得しました。 というか型付けができないので、dechex('FFFFFFFFFFFFFFFF') としても -1 にならず、大きな浮動小数点数に変換されます。

話をEXIFに戻すと、気をつけなければならないのは、LONG型とRATIONAL型です。 LONG型は符号なし32bit整数であり、RATIONAL型はLONG型のペアで表される分数です。 従って、これらの数値にマイナスが入ることは無く、その場合のみ前述の読み替えをすれば良いのです。 これとは別に、SIGNED LONG型とSIGNED RATIONAL型もあるので注意が必要です。 言うまでもなく、これらの型は符号ありなため、負数を許容しています。

これらを踏まえて、LONG型、RATIONAL型、及び緯度経度を正しい値で読み込む関数を作成しました。 実際は、レンズの焦点距離(RATIONAL型)や画像幅(LONG型)では問題になることは無く、そのまま使用して差し支えないと思います。 しかし、緯度経度では実際にそのようなデータを確認したことがあるため、対策をしておいた方が良いでしょう。

// LONG型を修正
function exif_long($v) {
	if ($v<0) {
		return hexdec(substr(dechex($v),-8));
	} else {
		return $v;
	}
}

// RATIONAL型を修正
function exif_rational($v) {
	list($num,$den) = explode('/', $v);
	$num = exif_long($num);
	$den = exif_long($den);
	return $num/$den;
}

// GPS緯度経度を十進に変換(西経と南緯の場合は負数にする処理が必要)
function exif_dms($dms) {
	$d = 0;
	for ($i=0;$i<3;$i++) {
		$v = $dms[$i];
		$d += exif_rational($v)/pow(60,$i);
	}
	return $d;
}

EXIF 情報の無い複数のファイルに、更新日付だけ持った EXIF 情報を付けたい場合は、jhead を使おう。Linux、Windows、MacOSX で利用可能なコマンドラインツールだ。

Exif Jpeg header manipulation tool
http://www.sentex.net/~mwandel/jhead/

インストールしたら、下記のように -mkexif オプションを付けて実行するだけで OK。

jhead -mkexif *.jpg

ウェブカメラのキャプチャー画像に EXIF が付いておらず、後々撮影日時が分からなくなる恐れから、ファイルのタイムスタンプを EXIF に埋め込みたくて調べてみました。