EXIF抽出スクリプトのサンプル
JPEGファイルからExifを取り出すPerlスクリプトです。ソースを短くするため、シンプルな機能に絞っており、あまり最適化もしていません。ひな形としてお使いください。
最初の%target_tagで定義したExifタグのみを取り出すので、必要なデータだけを抽出して利用できます。実際の出力はサブルーチンread_dir_entryで行っているので、ここのprint関数の部分を書き換えると、XHTMLのテーブルなど、好みのフォーマットで出力できます。
コメントフィールドは、とりあえず内容にかかわらずそのまま出力しています。必要に応じて、RDFであることをチェックしたり、XMLを適当な形に変換するなど書き換えてください。
#!/usr/local/bin/perl
#グローバルで使う変数
($buf, $long_tmpl, $short_tmpl) = ();
#抽出するExifタグを定義する連想配列(ハッシュ)。'tagID' => 'label'の形式
%taget_tag = (
'270' => 'imageDescription',
'271' => 'make',
'272' => 'model',
'306' => 'dateTime',
'315' => 'artist',
'33432' => 'copyright',
'33434' => 'exposureTime',
'33437' => 'fNumber',
'37386' => 'focalLength',
'37396' => 'subjectArea',
'37500' => 'makerNote',
'37510' => 'userComment',
'41986' => 'exposureMode',
'41987' => 'whiteBalance',
'41988' => 'digitalZoomRatio',
'41989' => 'focalLengthIn35mmFilm',
'41990' => 'sceneCaptureType',
'41991' => 'gainControl',
'41992' => 'contrast',
'41993' => 'saturation',
'41994' => 'sharpness',
);
my($fld_marker, $tiff_size, $magic);
#入力ストリームは適宜書き換え
open(IN, $ARGV[0]) || die "Can't open $ARGV[0]: $!" if $ARGV[0];
binmode(IN);
read(IN, $_, 2);
return unless(unpack("H4", $_) eq 'ffd8'); #is JPEG
# TIFFタグを解析していくメインループ
while(1){
read(IN, $_, 2);
$fld_marker = unpack("H4", $_);
read(IN, $_, 2);
$tiff_size = unpack("n", $_);
if($fld_marker eq 'ffe1'){
read(IN, $_, 6);
$magic = unpack("A6", $_);
if($magic eq "Exif"){
read(IN, $buf, $tiff_size - 8); # as global buffer variable
read_tiff();
}else{
read(IN, $_, $tiff_size - 8); #dummy
}
}elsif($fld_marker eq 'fffe'){
read(IN, $_, $tiff_size - 2); #dummy
read_comment($_);
}elsif($fld_marker eq 'ffda' or $fld_marker eq ''){
#終了条件は、とりあえずSOSもしくはタグが見つからない(ファイル終了)
last;
}else{
read(IN, $_, $tiff_size - 2); #dummy
}
}
close IN;
###
### Exifを解析するサブルーチン
###
sub read_tiff{
my($byte_order, $offset, $iifd);
$byte_order = unpack("A2", substr($buf, 0, 2));
if($byte_order eq "II"){
($long_tmpl, $short_tmpl) = ("V", "v"); #インテル順
}elsif($byte_order eq "MM"){
($long_tmpl, $short_tmpl) = ("N", "n"); #モトローラ順
}else{
die "Incorrect byte order:$byte_order :$!";
}
$offset = unpack($long_tmpl, substr($buf, 4, 4));
1 while($offset = read_ifd($offset, 0));
}
# IFDの解析
sub read_ifd{
my($offset, $parent_tag_id) = @_;
my($num_dir_entry) = unpack($short_tmpl, substr($buf, $offset, 2));
my($next_offset) = unpack($long_tmpl, substr($buf, $offset + 2 + $num_dir_entry * 12, 4));
$offset += 2;
while($num_dir_entry){
read_dir_entry(substr($buf, $offset, 12), --$num_dir_entry, $parent_tag_id);
$offset += 12;
}
$next_offset;
}
# ディレクトリエントリの解析
sub read_dir_entry{
my($dir_entry, $num_dir_entry, $parent_tag_id) = @_;
my($tag, $type, $num_data, $dir_offset) = unpack("$short_tmpl$short_tmpl$long_tmpl$long_tmpl", $dir_entry);
my $data_value = '';
my($dir_size) = (1, 1, 2, 4, 8)[$type-1] || 1;
if($tag==34665 || $tag==34853 || $tag==40965){
1 while $dir_offset = read_ifd($dir_offset, 0, $nest + 1,$tag);
}elsif($taget_tag{$tag}){
#userCommentは、文字列と想定して出力
$type = 2 if($taget_tag{$tag} eq 'userComment');
if($num_data * $dir_size > 4){
foreach $i (0 .. $num_data - 1){
$data_value .= read_data($type, substr($buf, $dir_offset + $i * $dir_size, $dir_size));
}
}else{
foreach $i (0 .. $num_data - 1){
$data_value .= read_data($type, substr($dir_entry, 8 + $i * $dir_size, $dir_size));
}
}
#この出力部分を変更すれば、好みのフォーマットに
print "$taget_tag{$tag}:$data_value\n";
}
}
# データタイプに応じたデコード
sub read_data{
my ($type, $data) = @_;
if ($type==1){
return unpack("C", $data);
}elsif($type==2){
return unpack("A", $data);
}elsif($type==3){
return unpack($short_tmpl, $data);
}elsif($type==4){
return unpack($long_tmpl, $data);
}elsif($type==5){
return join("/", unpack("$long_tmpl$long_tmpl", $data));
}else{
return unpack("H*", $data); #分からない形式はとりあえず16進で
}
}
# コメントフィールド
sub read_comment{
my $comment_data = shift;
#とりあえずどんなコメントでもそのまま出力
print $comment_data;
}
### End of sample script


