Python初心者のための目的志向のプログラミング。前回までで記述した、連番JPEGからのタイムラプス動画作成コードに一部追加し、本質の部分ではないですが、おまけ機能を追加しようと思います。
具体的にはタイムスタンプの付与です。前回までのコードを使って動画を作ると、PC上で動画が作られた日時でタイムスタンプが作られてしまうので、実際撮影された日時になりません。動画をサーバ上に上げると、この作成された日時の動画として認識されたりしてとても残念なので何とかします。
やりたいこと
前回までのコードでできたタイムラプス動画に対して、ソースのJPEGが撮影された日時をタイムスタンプとして付与する。
方針
最終フレームのJPEG画像のEXIF情報から、撮影日時を取得する。その日時を完成した動画に対して付与する。を加える。
コード
今回は動画作成のコード部は完全に同じなので、割愛。追加したEXIFからの撮影日時の取得部にのみ着目します。関数とその使い方です。
from PIL import Image from PIL.ExifTags import TAGS import datetime ############################### # jpegファイルからexifデータがあればそこから、なければファイル更新日時を取得する関数 def getDate(fname): try: img = Image.open(fname) exif = img._getexif() for id, val in exif.items(): tg = TAGS.get(id, id) if tg == "DateTimeOriginal": return datetime.datetime.strptime(val, '%Y:%m:%d %H:%M:%S') except AttributeError: return datetime.datetime.fromtimestamp(os.path.getmtime(fname)) return datetime.datetime.fromtimestamp(os.path.getmtime(fname))
解説
どうやらEXIFの取得はOpenCVではできないようです。(調べ方が足りないかなぁ)
PILを使います。PIL(Python Image Library)。現在はPillowという名前で開発が継続しているようです。なので環境に追加するライブラリ名はPillowの方です。少しはまりました。
EXIFタグを取得するコードを調べた際のサンプルで、try/catchを使っていたので、そのまま使ってみました。Pythonでも使えるんですね。使い方は一般的なそれと同じ記述のようです。tryの中で例外が起こるとexceptのところでその例外をcatchするみたいです。
img = Image.open(fname) exif = img._getexif()
ここでPIL.Imageからexif情報の構造体(クラス)を取り出しています。
for id, val in exif.items(): tg = TAGS.get(id, id) if tg == "DateTimeOriginal":
このitems()でループを回して、DateTimeOriginalタグの値を取り出しています。
もちろんここで別の例えばカメラの名前とかのタグを取り出して、それをファイルのメタ情報として追加する。とかもできれば面白いかもしれませんが、mp4動画にそれら情報を追加する簡単な方法が見当たらなかったので、今回は撮影日時だけ取り出します。
datetime.datetime.strptime(val, '%Y:%m:%d %H:%M:%S')
この記述で、DateTimeOriginalに入っている特定のフォーマットの文字列をdatetime型の変換されます。この時のvalを覗けばわかりますが、EXIF内の撮影日時が書かれているタグは、文字列として’%Y:%m:%d %H:%M:%S’というフォーマットになっているので、この文字列フォーマットからdatetime型に変換しています。
この仕様はやってみて調べたので、実はEXIFフォーマットとしてはこれ以外のフォーマットも許容しているかもしれません。今回試したCanonのコンデジに記載のEXIFでは上記フォーマットでした。
ここで例外が発生するかまたは撮影日時タグが無いと、
datetime.datetime.fromtimestamp(os.path.getmtime(fname))
へ切り替えます。これはEXIF無しJPEGのための代替手段で、ファイルのmtime(更新日時)を取ってきています。EXIF情報がないときはこれを変わりに返します。os.path.getmtime()でできてしまいます。
atime,ctime等で、OSが管理するファイルのアクセス日時や、作成日時も取得できるようですが、ファイルのコピー等で動いてしまうアクセス日時や、OSの種類で意味が異なる作成日時を使うよりは、更新日時を使うのがベターと判断しました。
関数の機能としては以上です。
この関数を使って、連番JPEGの最終画像から日時を取り出して、その日時を完成した動画のタイムスタンプとして付与してあげるだけです。具体的には、
dt = getDate(files[len(files)-1]) #最終フレームの更新日時を取得
として日時を取得し、
video.release() os.utime(SAVE_FILE, (dt.timestamp(), dt.timestamp()))
の箇所で、動画ファイルとしてクローズした後に、os.utime関数を使ってアクセス日時として上書きします。
こうしてやることで、出来上がったビデオの作成日時が連番JPEGファイルの最終画像が撮影された日時になるので、動画完成のタイムスタンプとしては妥当なタイミングのものになります。
まとめ
タイムラプス動画のソースとなるJPEG画像から、撮影日時を取ってきて、それを動画のタイムスタンプとして付与してみました。
凝りだしたらきりがないですが、個人的に動画ファイルの日時情報が狂っているのはどうも不便だったので、追加してみました。Google Photosを便利に使っているので、このタイムスタンプ追加のおかげで正しい日時の動画として認識してくれてとても便利になりました。
タイムスタンプを書き換えるツールはいくらでもあるのだと思いますが、これもライブラリをちょいとたたくだけでできてしまったので、なんとも簡単な作業でした。
いったん本体コードの方は機能的には追加するところが思いつかないので、仕上げにかかります。ちょっとダサい部分や、今後GUIを導入したときに不便になりそうな箇所を修正しておしまいにしようと思います。
コメント