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