world2015/02/12Exifの日付だけを修正する先日、新しいカメラを持ってイベントに行って写真を撮ってきた。まではよかったが、PCに取り込む段になって写真の日付が1年前になっていることに気づいた。原因は容易に想像できるとおり、初めにカメラの時計を設定したときに年を間違えていた。画像ファイルのExifに記録された日付も1年前になっていたが、Exifの情報は色々な写真管理の基礎となるので、これはいかにもまずい。そこで既存ツールを試してみたが、Exifのサムネイルが維持されなかったりして、ツールを確かめて回るのも手間。それなら自分でできないかと思い、ただし、真っ当にExifを編集しようとするとファイルサイズが変わるので、年を1年直すだけでそれは負けた気がする、ということで直接ファイルのバイナリを修正しようと考えた。HH:MM:SS"のフォーマットのASCII文字列と決まっているので、そんな無理な話でもないはず。実際、バイナリエディタで簡単に見ることができたので、ゴーアヘッド。バイト配列用のIndexOfとReplaceメソッドまずはバイト配列に含まれる他のバイト配列の位置を調べて置換するメソッドが必要なので(StringにおけるString.IndexOfとString.Replaceメソッドに相当)、書いてみた。主眼は位置を調べるメソッドの方で、複数用(SequenceIndicesOf)と単数用(SequenceIndexOf)にそれぞれ引数がbyte[]とIEnumerable<byte>のオーバーロードを作った。複数用byte[]メソッド+単数用byte[]メソッド複数用byte[]メソッド+単数用IEnumerable<byte>メソッド複数用IEnumerable<byte>メソッドの3通りあるが、SequenceReplaceメソッド中の呼び出し元の引数がbyte[]なので、このままだと1のルートが走る。引数にAsEnumerable<byte>()を付けてルートを変えつつ、最終的なコードで300のJPGファイル(計1.06GiB)を処理したところ、所要時間は以下のようになった。21.6秒46.7秒36.1秒byte[]で通した1のルートが基本にして最速という結果だが、2のルートが2倍以上遅いのはSkipが時間を食っているのではないかと思う(実行の度に先頭からループが回るわけで)。自分的に期待したのは3のルートだが、1のルートには全く及ばず。最終的なコードでは置換数=検索数の上限を指定するmaxCount引数を使ってないが、実はヒット回数は各ファイル3回でかつ位置は先頭部分と分かっているので、これに3を指定すれば画像データ部分を無駄に検索する必要がなくなって所要時間はたいして変わらなくなる、というオチ。[修正]複数用IEnumerable<byte>メソッドのmaxCountの処理にバグがあったので修正した。複数用byte[]メソッドは問題なかったが、比較のためこれに合わせた。数字を挙げた所要時間を計った際にはmaxCountを使ってないので、影響はない。Exifの日付部分を置換するメソッドこのSequenceReplace拡張メソッドを使ってJPGファイルのExifの日付部分を置換するメソッド(とそのコンソールアプリ)。各ファイルについて、以下の処理をする。まとめ以上、実用での使用時間は1分以内だったというコーディングだが、バイト配列の置換というのはユーティリティメソッドとしていかにもありそうなので、より効率的な方法があるような気がする。Communitywww.flickr.comコメントについてコメントは承認制です。承認まで時間がかかるときがあります。感想、間違いの指摘など歓迎します。間違いについては、その場所と内容を明記してください。エントリと関係がないもの、不適当と思われるものは承認しません。プロフィールEMOGitHub詳しくないプロフィール当てにならないカスタマイズ。