2010年7月23日金曜日

Perl+ExifTool で写真の日付をいじる

この間,多数の写真をもらったのだが,困ったことに写真の日付がかなり実際からずれていた。1枚や2枚ならいいのだが,100枚程度あり,どうしたものかと困っていた。そこで,PerlExifToolを使って写真の日付を一括して変更するルーチンを書いてみた。と言っても,インターネットで検索して,他の方のルーチンをコピーした,というのが正しいが…

 ExifToolについては,ExifTool by Phil Harveyに英語の文章があり,Perl用のモジュールについてはThe Image::ExifTool Perl Library Moduleに英語の説明がある。また,ExifToolの処理で使われる「Shift」についてはImage::ExifTool::Shift.pl に英語の説明があるので,見てみて欲しい。
 また,以前このblogでthumbnailを作るというのを書いたが,そこでもExifToolについて少し書いている。こちらも見て欲しい。

 やり方としては,(1) 処理するファイルのマスク(*.JPGなど)を引数とする,(2) 日付はShiftを用いてずらす。ずらす量は,2つの日付・時刻を引数として入力させて,その差を取る,(3) バックアップファイルを残す,残さない,を引数で指定する,(4) ファイルの日付を写真のCreateDate タグと一致させるかどうかも引数で指定する,としてみた。

(1) 処理するファイルのマスク(*.JPGなど)を引数とする
 これは,Perlglob関数を用いるのを念頭において,複数のマスクをスペースで区切って入力してもらう。複数のマスクパターンがある場合は,引用符「"」や「'」で囲んでもらえばよい。
マスクパターン:$file_mask = "*.JPG *.jpg" など
実際に処理する際には,
for $file_target(glob($file_mask)) {
    ($file_targetに対する処理)
}
として,実行する。ここでは,current directoryを念頭に置いている。

(2) 日付はshiftを用いてずらす
 これが一番肝心な部分だが,これにはExifToolを使う。Perlのスクリプトのはじめの方に
use Image::ExifTool;
use Time::Local;
として,Image::ExifToolTime::Localを指定する。Time::Localは,引数として与えた日付と時刻の情報からシフト量を求める際に用いる(後述)。シフト量が$shift_timeとして与えられている場合には,下記のようにすれば画像データの日付を変更できる。ここで,$shift_directionは+方向にシフトさせる(時刻を遅らせる)か,−方向にシフトさせる(時刻をさかのぼらせる)かを「+1」か「-1」で指定している。
$exifTool = new Image::ExifTool;
$exifTool->ImageInfo($file_target);
# 時刻をずらす
$exifTool->SetNewValue($_=>$shift_time, Shift=>$shift_direction)
for('CreateDate','DateTimeOriginal','ModifyDate');
# $file_target.".org"にコピーして,current フォルダに出力する
# バックアップが不必要なら,$file_target.".org"を削除すればよい。
    rename($file_target, $file_target.".org");
    $exifTool->WriteInfo($file_target.".org",'./'.$file_target);

# 変更後の時刻データを表示する
    $exifTool->ImageInfo($file_target);
    print "¥t".($exifTool->GetValue($_)) 
    for('CreateDate','DateTimeOriginal','ModifyDate');

# ModifyDateとCreateDateが違っていたら,注意喚起
    if (($exifTool->GetValue('ModifyDate')) ne ($exifTool->GetValue('CreateDate'))) {
    print "¥t".'different [[ModifyDate]]';
}
print "¥n";
のように処理をさせる。$exifTool->SetNewValueの引数で「$_=>$shift_time」とあるが,これは次の行の「for」の中の1個1個を順に「$_」に入れて,その値を $shift_timeだけずらす,というのを表している。つまり,画像データがもつ3つのタグ(CreateDate, DateTimeOriginal, ModifyDate)に対して,同じだけ時刻情報をずらす処理を表している。デジカメの製造メーカーによっては,画像データの中の日付・時刻を表すタグに他のものもある場合がある。しかし,基本は「CreateDate, DateTimeOriginal, ModifyDate」の3つであり,この3つに対してのみ処理をしている。
 また,情報の出力先として,元の画像を「$file_target.".org"」と名前を変更してから,元の名前で画像を書き出している。こうしておけば,バックアップファイルが必要なら xxxx.org というファイルを残せばよいし,バックアップが不要なら xxxx.org というファイルを消せばよい。
 出力後($exifTool->WriteInfo の後)に新しくなった CreateDateModifyDate の情報を表示している。これは,思ったように変更されたかを確認する意味と,あるタグだけが突飛な値を取っていないかを見るためである。実際に「ModifyDate」がおかしな値になっていることがあった(未来の日付になっていた)ので。そのため,ModifyDateCreateDate と異なっている場合には,注意喚起を表示するようにしてある。

時刻のシフト量は以下のように求めている。
@buff=reverse split(/[: ¥/]/,$original_datetime);
$buff[4]--; # month を1減らす
# epoch に変換する
$original_time=timelocal @buff;
# ---------------------------------
@buff=reverse split(/[: ¥/]/,$target_datetime);
$buff[4]--; # month を1減らす
# epoch に変換する
$target_time=timelocal @buff;
# ---------------------------------
$time_difference = $target_time - $original_time;
if ($time_difference >= 0) {
    $shift_direction = "+1";
} else {
    $shift_direction = "-1";
    $time_difference = abs($time_difference);
}

$date = int($time_difference/86400);
$time_difference = $time_difference-$date*86400;
$hour = int($time_difference/3600);
$time_difference = $time_difference-$hour*3600;
$min = int($time_difference/60);
$sec = $time_difference-$min*60;

$shift_time = $date." ".$hour.":".$min.":".$sec;
ここで「$original_datetime」と「$target_datetime」は「2010/07/17 12:00:00」のようなデータであり,入力の際に "2010/07/17 12:00:00" と入力する。これを「/」,スペース,「:」を区切り文字としてばらして配列にいれている。「/」を「¥/」と書いているのはPerlの構文で「/」が使われているので,「/」そのものをデータとして与えるには「¥/」としなければならないからである。また,正しい時刻情報(ある日付からの秒数をlong integerで与えたもの)にするには,monthの数値を1減らしてからTime::Localを用いないといけないらしい。「timelocal @buff」がTime::Localを使っている部分である。シフトの向き(+1か-1か)は,2つの時刻情報の差の符号で決めている。シフト量は「61 01:22:30」のように与えている。最初の数字はシフトさせる日数を与え,残りがシフトさせる時間を与えている。上の処理では多少どんくさい処理をしているが,プロではないのでお許し願いたい(もっとカッコいい書き方があれば教えてください)。

(3) バックアップファイルを残す,残さない,を引数で指定
 これは(2)で出てきた「$file_target.".org"」というファイルを残すか残さないか,で処理している。

(4) ファイルの日付を写真のCreateDateと一致させるかどうかを引数で指定
 この処理はPerlumask 関数を用いている。
if (xxxxx) {
    $exifTool = new Image::ExifTool;
    $exifTool -> ImageInfo($file_target);
    @timestamp=reverse split(/[: \/]/,$exifTool->GetValue('CreateDate'));
    $timestamp[4]--; # month を1減らす
# epoch に変換する
    $timestamp=timelocal @timestamp;
# ファイルのタイムスタンプを変更する
    utime $timestamp,$timestamp,$file_target;
}
ここで参照するタグとして CreateDate を使っている。他の ModifyDate などでもよいが,個人的には CreateDate がよいと思っている。

 今回は,current directoryの中の画像ファイルの日付情報を一括してシフトさせている。そのため,ModifyDateだけずれている場合にModifyDateだけを処理するというのは不得意である。しかし,上記のルーチンで「CreateDate, DateTimeOriginal, ModifyDate」の3つに対して処理している部分を,特定のタグだけに対して処理させればよい。具体的には
$exifTool = new Image::ExifTool;
$exifTool->ImageInfo($file_target);

if ($original_tag !~ /[0-9]+:[0-9]+:[0-9]+/) {
    $new_value = $exifTool->GetValue($original_tag);
} else {
    $new_value = $original_tag;
}

# TAGの内容を変更する
$exifTool->SetNewValue($target_tag, $new_value);
のように処理させればよい。ここで,$original_tagCreateDateなどを表し,$original_tag のタグの情報を ModifyDate などの $target_tag にコピーするのを念頭に置いている。$original_tag に「2010/07/17 12:00:00」のようなデータを入れれば,その日付・時刻に書き換えることもできるようなルーチンにしてある。

 今回のスクリプトがあれば,時刻情報がずれたデジカメで撮った大量の写真の日付を一括してずらすことができる。それ以前にデジカメの日付・時刻をまめに正しい値にセットしておくのが一番大切だと思う。特に複数台のデジカメを使って撮影するときには気をつけたい。そうしないと写真の整理の時に,写真の順序が違ってしまうことがあるので。

0 件のコメント: