コマンドラインから画像の撮影時刻のズレを補正してAndroid端末に転送する

撮影時刻がなぜかズレている画像ファイルのexifを、Macのコマンドラインから修正し、最後にそれをAndroid端末に転送してついでファイル変更日にも反映することで、Android端末のGoogleフォトアプリで正しい日時でタイムライン表示されるようにしたメモ。

# 前提

- 4年前に行ったキャニオニングで、スタッフさんがデジカメで撮ってくれた写真をCD-Rで購入してたので、バックアップのためにそれを再度PCに取り込んでみたら、以下の問題があった
    - なぜか途中で撮影時刻が1日ずれていた
    - 中には動画もあり、それらの動画ファイル内にexif的な撮影日時情報が（カメラが古くて？）存在しないようだった
    - 複数のデジカメから取り込まれているようだったが（ファイル名が違った）、その1つのデジカメデータが撮影日よりも1年以上古いファイル作成日になっていた（おそらく取り込んだPCの時刻設定の問題）
- その結果、それらをGoogleフォトにアップロードすると、各ズレがそのまま反映され、タイムライン上で画像や動画がばらけて表示された

という状況で、

- 撮影日時のずれている静止画ファイルと、撮影日時情報を持たない動画ファイルを、正しい日付に補正したい
- それをAndroid端末に取り込んで、正しい日付で表示したい

を目標とした。

# 環境

Mac

```
% sw_vers
ProductName:	Mac OS X
ProductVersion:	10.15.5
BuildVersion:	19F101

% uname -v
Darwin Kernel Version 19.5.0: Tue May 26 20:41:44 PDT 2020; root:xnu-6153.121.2~2/RELEASE_X86_64

% echo $ZSH_VERSION
5.7.1

% exiftool -ver 
12.00
```

Android

```shell
% adb shell getprop ro.build.version.release
10
% adb shell getprop ro.build.id
QQ3A.200805.001
 % adb shell getprop ro.product.model
Pixel 3
```

# １日のズレを補正する

Macのプレビュー.appで撮影日時を調べると、なぜか撮影の途中（連番ファイル名の途中）で日付が１日巻き戻っているようだったので、それらを検知して+1日を加算することにした。

## まず、ズレてる画像だけを抜き出す

具体的なズレ幅が事前に分かっている（正しくは2016年10月1日だが、１日前の9月30日になっている）ので、Exifの撮影日タグをexiftoolで取り出し、2016年9月かどうかで判別し、一時フォルダに入れる。

```shell
mkdir date_converted
for file in `\find . -maxdepth 1 -type f -name '*.JPG'`; do
    DATETIME=`exiftool $file -s -s -s -DateTimeOriginal`
    if [[ $DATETIME =~ "2016:09:" ]] then
        cp $file date_converted
        echo "$file $DATETIME"
    fi
done
```

撮影タグの日付フォーマットは `YYYY:MM:DD HH:MM:SS`
see: https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimeoriginal.html

`-s -s -s` というオプションはタグの値のみを取り出すオプションで、sを重ねるごとに出力が短くなるようだ。 `-s3` でもよかったみたい。

```shell
% exiftool RIMG1465.JPG -EncodingProcess    
Encoding Process                : Baseline DCT, Huffman coding
% exiftool RIMG1465.JPG -EncodingProcess -s
EncodingProcess                 : Baseline DCT, Huffman coding
% exiftool RIMG1465.JPG -EncodingProcess -s -s
EncodingProcess: Baseline DCT, Huffman coding
% exiftool RIMG1465.JPG -EncodingProcess -s -s -s
Baseline DCT, Huffman coding
% exiftool RIMG1465.JPG -EncodingProcess -s3     
Baseline DCT, Huffman coding
```

## 取り出したフォルダ内の全ファイルに対して、撮影日時を1日進める

相対指定で1日を加算する。
今回は使わなかったが、絶対指定もできるようだ。

```shell
exiftool "-DateTimeOriginal+=0:0:1 0:0:0" date_converted
exiftool "-CreateDate+=0:0:1 0:0:0" date_converted

mv date_converted/*.JPG .
```

`CreateDate` とは [DateTimeDigitized]( https://www.awaresystems.be/imaging/tiff/tifftags/privateifd/exif/datetimedigitized.html)のことで、Macのプレビュー.appでは「デジタル化日時」として表示されるもの。これも同じ日時にしておく。

処理後、オリジナルファイルは `_original` というsuffixつきで保存されてるのでそれはそのままに、変換済みファイルを元フォルダに書き戻す。

## ファイルの変更日（ついでにファイル名にも）を、撮影日で更新する

- 撮影日とファイル変更日がズレているものを合わせる
- 動画ファイルの撮影日を、直前の静止画の撮影日から計算し、それをファイル変更日に設定する
- 後のために、その日時をファイル名にも使用しておく

がやること。

```shell
mkdir filename_converted
for file in `\find . -maxdepth 1 -type f -name '*.JPG' -or -name '*.mp4' | sort`; do
    FILENAME=`basename -- $file`
    DATETIME=`exiftool $file -s -s -s -DateTimeOriginal`
    if [[ $DATETIME =~ "0000:00:00" ]] then
        TIMESTAMP=`date -r $PREV_TIMESTAMP -v+1S "+%s"`
    else
        TIMESTAMP=`date -j -f "%Y:%m:%d %H:%M:%S" "$DATETIME" "+%s"`
    fi
    echo "$file $TIMESTAMP"
    cp $file "filename_converted/${FILENAME:t:r}_$TIMESTAMP.${FILENAME:t:e}"

    PREV_TIMESTAMP=$TIMESTAMP
done
```

findで対象ファイルを絞り込みつつ、sortでファイル名順でforを回し、
撮影日がないファイル（動画ファイルのみのはず）は直前ファイルの1秒後とみなしつつ、
dateコマンドで日時文字列をUNIX timeに変換して、
それをファイル名に使う形で、別フォルダにコピー。

その後、別フォルダ上で、

```shell
cd filename_converted
for file in `\find . -maxdepth 1 -type f -name '*.JPG' -or -name '*.mp4' | sort`; do
    FILENAME=`basename -- $file`
    TIMESTAMP=`echo ${FILENAME:t:r} | cut -d _ -f2`
    touch -t `date -r $TIMESTAMP "+%Y%m%d%H%M"` $file
    echo $TIMESTAMP
done
```

ファイル名の中のタイムスタンプをcutコマンドで取り出しつつ、
dateコマンドでtouchコマンド用の日付文字列に変換して、touchコマンドでファイル変更日を更新。

これで完成。

ちなみに、単に撮影日時をファイル変更日に設定するけなら、exiftoolのコマンド一発で行ける。
https://qiita.com/suin/items/04f08c3d1c410264e94f

# Android端末に転送して、Android上のファイルのタイムスタンプも更新

```shell
% adb push * /path/to/push/
% adb shell
```

`adb pull` には `-a: preserve file timestamp and mode` というタイムスタンプを維持するオプションがあるが、pushにはないで、push後にファイル名から撮影日時を特定してtouchで更新する。

```shell:adb-shell
for file in `\find . -maxdepth 1 -type f`; do
    FILENAME=`basename -- $file`
    TIMESTAMP=`echo $FILENAME | cut -d . -f1 | cut -d _ -f2`
    touch -t `date -d "@$TIMESTAMP" "+%Y%m%d%H%M"` $file
    echo $TIMESTAMP
done
```

※ せっかくCD-RのデータをGoogleフォトに入れておくなら高画質のままとしたかったので、Pixel3からのアップロードは静止画も動画も全て無制限という特典を活用したく、このような面倒な手順となった。