写真のExifをsqlite3でデータベース化して、 指定した時刻近傍のファイルリストを作成してみた。
データベースを一度使ってみたかったからというか、 写真を閲覧して探しだす作業がすごくメモリを食って私の環境には厳しい。 なので、可能性を試そうかと考えた。
- linux(debian)のpyenv環境で、3.7.2。
- Exifはpyexifinfoを使う。exiftoolのラッパー。
- sqliteはpython標準で。
- dbの中身は、小さくすべきと思って、画像ファイル名(パス含む)、ユリウス年、緯度、経度。
- ユリウス年は julianを使って処理。
スクリプト
header and oters
#!~/.pyenv/shims/python """Generate exif(Exchangeable Image File Format) database.""" import logging import sqlite3 from pathlib import Path from datetime import datetime from contextlib import closing import exifread import julian import logzero import pyexifinfo as pex from pytz import timezone TABLE_EXIF = '''create table if not exists exifdata ( id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(128), juda REAL, bla REAL, blo REAL, time_stamp DEFAULT CURRENT_TIMESTAMP )''' SQL_E = '''insert into exifdata (name, juda, bla, blo) values (?,?,?,?)''' TFORM = "{0:%Y-%m-%d %H:%M:%S %Z}"
文字列からdatetimeとjulian年を作成する。
私が保管している写真のExifは、フォーマットなど色々だったので、 いくつか対応するようにした。't_zone'は、文字列がUTCかどうかの指定。
def tstr2dtime(t_str, zo, _type): """Convert string to datetime.""" if _type == 0: # 2018:06:22 11:20:42 t_t = datetime.strptime(t_str, '%Y:%m:%d %H:%M:%S') elif _type == 1: # 2018-06-22 11:20:42 t_t = datetime.strptime(t_str, '%Y-%m-%d %H:%M:%S') elif _type == 2: # 2019-01-04T07:06:23Z t_t = datetime.strptime(t_str, '%Y-%m-%dT%H:%M:%SZ') elif _type == 3: # 2019-01-04 07:06:23.000011 t_t = datetime.strptime(t_str, '%Y-%m-%d %H:%M:%S.%f') elif _type == 4: # 2019:01:04 07:06:23.000011 t_t = datetime.strptime(t_str, '%Y:%m:%d %H:%M:%S.%f') elif _type == 5: # 2005:01:30 09:59:48+09:00 t_t = datetime.strptime(t_str, '%Y:%m:%d %H:%M:%S%z') if zo == 'UTC': st_utc = t_t st_loc = st_utc.astimezone(timezone('Asia/Tokyo')) else: st_loc = t_t st_utc = st_loc.astimezone(timezone('UTC')) ju_val = julian.to_jd(st_utc) mju_val = ju_val - 2400001 return st_utc, st_loc, ju_val, mju_val
緯度経度をdegreeに変換
def conv2deg(lat_v): """Convert to degree.""" a_deg, a_min, a_sec = int(lat_v[0]), int(lat_v[2][:-1]), float(lat_v[3][:-1]) return a_deg + a_min / 60. + a_sec / 3600.
ファイルを指定して、Exifのリストを作成
デジカメによってExifの中身が異るので、 時刻に関連する項目はほぼ全てリスト化してみた。中身が無いのは'None'。
pyexifinfoは、jsonとかcsvで返してくれるので、素人にやさしい。 速度がどうかはわからなし。exiftoolのラッパー。
def get_exifs(p_file): """Get EXIF of an image if exists.""" p_data = pex.get_json(p_file)[0] # logzero.logger.info(p_data) # show all meta data c_exver = p_data.get('EXIF:ExifVersion', 'None') c_model = p_data.get('EXIF:Model', 'None') c_time = [p_data.get('EXIF:DateTimeOriginal', 'None'), p_data.get('EXIF:CreateDate', 'None'), p_data.get('EXIF:GPSDateStamp', 'None'), p_data.get('EXIF:GPSTimeStamp', 'None'), p_data.get('EXIF:DateTime', 'None'), p_data.get('EXIF:DateTimeDigitized', 'None'), p_data.get('File:FileModifyDate', 'None'), p_data.get('EXIF:TimeZoneOffset', 'None')] c_geos = [p_data.get('EXIF:GPSLatitude', 'None'), p_data.get('EXIF:GPSLongitude', 'None'), p_data.get('EXIF:GPSImgDirection', 'None'), p_data.get('EXIF:GPSAltitude', 'None')] if 'None' in c_time[2:4]: # camera の時間はローカルタイムとして扱う。TimeZoneOffset が無いから。 if c_time[0] != 'None': st_utc, st_loc, ju_val, mju_val = tstr2dtime(c_time[0], 'Asia/Tokyo', 0) elif c_time[1] != 'None': st_utc, st_loc, ju_val, mju_val = tstr2dtime(c_time[1], 'Asia/Tokyo', 0) elif c_time[4] != 'None': st_utc, st_loc, ju_val, mju_val = tstr2dtime(c_time[4], 'Asia/Tokyo', 0) else: st_utc, st_loc, ju_val, mju_val = tstr2dtime(c_time[6], 'Asia/Tokyo', 5) # これしかないデータがあった。 else: # gps時があれば、これをUTCとして使う。 st_utc, st_loc, ju_val, mju_val = tstr2dtime(c_time[2] + ' ' + c_time[3], 'UTC', 4) if 'None' not in c_geos[0:2]: # gps の座標情報があるときは、変換する。 g_locate = [conv2deg(c_geos[0].split()), conv2deg(c_geos[1].split())] else: g_locate = [None, None] return [str(p_file), c_exver, c_model, st_loc, ju_val, mju_val, g_locate, c_time, st_utc]
ユリウス年の区間を指定して抽出
def search_byjud(s_jud, e_jud, sqdb_file): """Search picture file in ju_date.""" logzero.logger.info("- Results:") with closing(sqlite3.connect(sqdb_file)) as conn: db_curs = conn.cursor() db_curs.execute("SELECT * FROM exifdata") sel_sql = '''select * from exifdata where juda > ? and juda < ?''' for i, row in enumerate(db_curs.execute(sel_sql, (s_jud, e_jud, ))): mes_st = " {0:3d}, {1:s}".format(i, Path(row[1]).name) p_time = timezone('UTC').localize(julian.from_jd(row[2], fmt='jd')) mes_st += ", " + TFORM.format(p_time.astimezone(timezone('Asia/Tokyo'))) mes_st += ", MJD {0:.9f}".format(row[2] - 2400001) mes_st += ", {0:9.6f},{1:10.6f}".format(row[3], row[4]) logzero.logger.info(mes_st)
メイン
def main(): """Do main prcess.""" # 画像ファイル p_path = Path('/home/hogehoge/Pictures/CAMERA') p_files = sorted(list(p_path.glob("NIKON/**/*.JPG"))) # データベースファイル sqldb_file = 'pic_exif.sqlite' with closing(sqlite3.connect(sqldb_file)) as conn: db_curs = conn.cursor() # tableがあれば削除させたい時 # db_curs.execute("DROP TABLE IF EXISTS exifdata") db_curs.execute(MAKE_EXID_TABLE) db_curs.execute("SELECT * FROM exifdata") for p_f in p_files: # 登録済か確認 db_curs.execute("SELECT name FROM exifdata WHERE name = ?", (str(p_f),)) ck_row = db_curs.fetchall() db_curs.execute("SELECT * FROM exifdata") f_nam = "{0:s}:".format(str(p_f.relative_to(p_path))) if ck_row: # 登録あればパス mes_st = "Has " + f_nam else: # なければ、Exifを読んで登録 p_table = get_exifs(p_f) p_jdate = p_table[4] p_lat = p_table[6][0] if p_table[6][0] else 0.0 p_lon = p_table[6][1] if p_table[6][1] else 0.0 db_curs.execute(SQL_IN, (str(p_f), p_jdate, p_lat, p_lon)) # メッセージ作成 mes_st = "New " + f_nam + TFORM.format(p_table[3]) mes_st += ", JD {0:.9f}".format(p_table[4]) mes_st += ", PO({0:},{1:})".format(p_table[6][0], p_table[6][1]) logzero.logger.info(mes_st) # 終了したら、保存、圧縮。 conn.commit() db_curs.execute('VACUUM') # ユリウス年を指定して検索してみる search_byjud(2457970.53, 2457970.54, sqldb_file) if __name__ == '__main__': LOG_FORMAT = '%(color)s[%(module)s:%(lineno)d]%(end_color)s %(message)s' FORMATTER = logzero.LogFormatter(fmt=LOG_FORMAT) logzero.setup_default_logger(formatter=FORMATTER) logzero.loglevel(logging.INFO) logzero.logfile("./_logs/log.log", maxBytes=3e5, backupCount=3) main()
出力例
$ python exifdb.py Has zenpad_Z380M/20161219/P_20161219_103516_1_p.jpg: Has zenpad_Z380M/20161219/P_20161219_103530_1_p.jpg: ;; ;; - Results: 0, RIMG1141.JPG, 2017-08-05 09:47:31 JST, MJD 57969.533007292, 35.312625,136.015147 1, RIMG1142.JPG, 2017-08-05 09:47:31 JST, MJD 57969.533007292, 35.312625,136.015147
このスクリプトに、gpxのログから'gpxpy'の'get_time_bounds'を使って ユリウス年を求めて使うようにすると、以下のような出力が得られる。
- Read log [hoge.gpx]: - (2017-08-04 21:01:36 UTC) - (2017-08-05 08:50:11 UTC) - (2017-08-05 06:01:36 JST) - (2017-08-05 17:50:11 JST) - Results: 0, CAMERA/WG-4T/140_0805/RIMG1139.JPG, 2017-08-05 06:14:17 JST, 2457970.384930555, 0.000000, 0.000000 1, CAMERA/WG-4T/140_0805/RIMG1140.JPG, 2017-08-05 09:30:39 JST, 2457970.521286458, 35.274528,136.010953 2, CAMERA/WG-4T/140_0805/RIMG1141.JPG, 2017-08-05 09:47:31 JST, 2457970.533007292, 35.312625,136.015147 3, CAMERA/WG-4T/140_0805/RIMG1142.JPG, 2017-08-05 09:47:31 JST, 2457970.533007292, 35.312625,136.015147 ; ;
使ってみた感想なんだが、 デジカメの時刻設定がデタラメな時期があったことを知った。だけだった。 次はトラックポイントとの紐付けだと思っていたのに、萎えた。
まあ初心者でも、sqliteが少しは使えたのかも、ということで良しとした。
0 件のコメント:
コメントを投稿