未来スナップ

こんにちは。このんです。

カメラを持ってお出かけしてしました。

目的は休日おでかけスナップ写真。

気が赴くままに歩いて、日常の光の形を写真に収めていく。

お出かけの最初の方でカメラの時間設定が3分遅れていることに気付き修正してお散歩スタート。


f:id:co__non:20210404183053j:plain


f:id:co__non:20210404183428j:plain


f:id:co__non:20210404183917j:plain


お散歩も兼ねていたので、たくさんあるいたなあと満足して帰宅しました。

お夕飯を済ませ、パソコンに取り込んだのです。 その時、このんさんは気づいてしまったのです。

・・・3分修正した時に間違えて1ヶ月未来の日時設定をしてしまっていたことに。


こまりました。

写真管理の一部をExif情報で行っているので、なんとしてでももどしたい...。

パッと以下の2つの方法が思いつきました。優先度をつけて見るこんな感じでしょうか。

  1. 公式のアプリを使って修正する
  2. Exifを書き換える

1.の方法では、もしかしたらRAWデータの方も修正できるかもしれない。 2.の方法は確実性はあるけども、RAWデータの方は修正できない。もしかしたらRAWの方も書き換えられる...?

なんていろいろ模索してみたのですが、(ソフト・私の環境では)いろいろな理由で1.は厳しそうでした。 いますぐJPEGの時間は修正したい、という理由があったので、2.の方法で進めていくことにしました。

ここでは、複数のJPEGファイルのExif情報を書き換えるプログラムを作ってみます。


概要

複数のJPEGファイルのExifファイルを書き換えるプログラムを作る!

そしてこの記事は作りながら書いてます...。

環境: Python3 + pip

コマンドラインオプション取得: argparse

コマンドラインオプション取得はとりあえずargparseを使っていきます。 適当にコマンドラインの形式はこんなイメージ。

例: カレントディレクトリの.JPG拡張子を持つ全てのファイルに対し日時設定を1ヶ月戻して(4月は30日しかないので--delta-days -30)_modifiedexifという語尾をつけて保存

moxify --delta-days -30 --suffix "_modifiedexif" *.JPG

-d, -s, -H, -Mオプションを用いて時間変化量を入力します。 これらのオプションを用いてdatetime.timedeltaオブジェクトを作成します。 なので、オプション指定時の動作はdatetime --- 基本的な日付型および時間型 — Python 3.9.3 ドキュメントに従います。

また、変換後のファイルを区別するためにサフィックスを指定できるようになったらいいなあって。

まとめると以下です。

オプション 意味 デフォルト値
--delta-days, -d 日の変化量 0
--delta-seconds, -s 秒の変化量 0
--delta-hours, -H 時間の変化量 0
--delta-minutes, -M 分の変化量 0
--suffix, -f 修正後ファイルのサフィックス指定 "_modifiedexif"

これを取得するコードはこんな感じかしら。

import argparse

parser = argparse.ArgumentParser(
    description='Moxify: exif timpestamp modification tool')
parser.add_argument('--delta-days', '-d', type=int, default=0,
                    help='timedelta(days), defaults to 0')
parser.add_argument('--delta-seconds', '-s', type=int, default=0,
                    help='timedelta(seconds), defaults to 0')
parser.add_argument('--delta-hours', '-H', type=int, default=0,
                    help='timedelta(hours), defaults to 0')
parser.add_argument('--delta-minutes', '-M', type=int, default=0,
                    help='timedelta(minutes), defaults to 0')
parser.add_argument('--suffix', '-f', type=str, default="_modifiedexif",
                    help='output files suffix, defaults to "_modifiedexif"')
parser.add_argument('files', type=str, nargs='+',
                    help='JPEG file to be processed')

args = parser.parse_args()

print(args)

動かしてみました。

$ python3 moxify.py defaults
Namespace(delta_days=0, delta_seconds=0, delta_hours=0, delta_minutes=0, suffix='_modifiedexif', files=['defaults'])
$ python3 moxify.py -d 1 --delta-seconds=1 --delta-hours 2 -M -2 -f "_mod" one two
Namespace(delta_days=1, delta_seconds=1, delta_hours=2, delta_minutes=-2, suffix='_mod', files=['one', 'two'])

いい感じですね。

Exif情報取得&書き換え: exif

これは標準のライブラリではないので、pipからインストールしました。

$ pip install exif

しかしAPI Reference — exif 1.2.1 documentationを参照してもどこにどんなデータが入っているのかよくわからない...。

そこで、APIリファレンスを参考に試しに適当なJPEGファイルのExif情報を取得してみました。

ここではあたりをつけてdate, timeがつくものをすべて総当たりしてみました。

attributes content
datetime str型で'yyyy:mm:dd HH:MM:ss'が格納されていました。
datetime_digitized str型で'yyyy:mm:dd HH:MM:ss'が格納されていました。
datetime_original str型で'yyyy:mm:dd HH:MM:ss'が格納されていました。
exposure_time float型で0.0003125が格納されていました...ってこれは露光時間やーん。
gps_datestamp 今回のJPEGファイルでは取得できませんでした
gps_timestamp 今回のJPEGファイルでは取得できませんでした
offset_time str型で'+09:00'が格納されていました。GMTとの時差みたいです。
offset_time_digitized str型で'+09:00'が格納されていました。
offset_time_original str型で'+09:00'が格納されていました。

取得できないものはおそらくカメラによって取れたり取れなかったりするのでしょう。 今回はdatetime, datetime_digitized, datetime_originalを取得し、取得できた場合にはdatetime.timedelta分足して書きなおす...こんな感じでしょうか...

変換部分をコードに書き起こしてみるとこんな感じかしら。

from datetime import datetime, timedelta
import exif
import os

exif_timedelta = timedelta(
    days=args.delta_days,
    seconds=args.delta_seconds,
    minutes=args.delta_minutes,
    hours=args.delta_hours)

time_format = '%Y:%m:%d %H:%M:%S'

for input_file_path in args.files:
    with open(input_file_path, "rb") as f:
        image = exif.Image(f)
    
    for arrtibute in ['datetime', 'datetime_digitized', 'datetime_original']:
        if hasattr(image, arrtibute):
            exif_datetime = datetime.strptime(image[arrtibute], time_format)
            exif_datetime += exif_timedelta
            image[arrtibute] = exif_datetime.strftime(time_format)

    input_file_base, input_file_ext = os.path.splitext(input_file_path)
    output_file_path = input_file_base + args.suffix + input_file_ext
    with open(output_file_path, "wb") as f:
        f.write(image.get_file())

完成

できました..! sample.jpgの撮影日時を30日戻してみます。

$ ls
moxify.py sample.jpg
$ python3 moxify.py sample.jpg -d -30
$ ls
moxify.py sample.jpg sample_modifiedexif.jpg

sample.jpgだと...?

f:id:co__non:20210404220759p:plain

sample_modifiedexif.jpgは...?

f:id:co__non:20210404220812p:plain

30日戻っていますね!!

以下みたいにワイルドカードでもうごいてくれますー!健気でかわいい。

python3 moxify.py *.jpg -d -30

めでたしめでたしー!