写真の撮影日時を超超超いい感じに修正するスクリプト

# ざっくり
写真を Amazon Photos などのクラウドサービスにアップした際、
撮影日時が入っていなかったりズレがある写真はあらぬ位置に表示されてしまいます。
![before.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1059738/e578d468-6fb2-c7e7-b97c-7d1037982784.png)
ということで、撮影日時をいい感じに修正するスクリプトを書きました。

https://github.com/s-m0nkey/photo-datetime-fixer

実行後はこんな感じ。
![after.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1059738/da8870c1-f153-8709-89fd-bed3594663c4.png)

ついでに元に戻したい時のためのスクリプトと記録した変更履歴を削除するスクリプトも書きました。

# スクリプトの解説
## `exif_manager.py`
下記スクリプトで利用するクラスを定義したもの。
ExifTool (https://exiftool.org/) を利用した EXIF 情報の更新を行うので、
ExifTool のインストールが必要。
Macの場合は`brew`で簡単インストール。
```
brew install exiftool
```

## `photo_daytime_fixer.py`
日時をいい感じに修正するスクリプト。
### 処理
1. 修正を行うフォルダを指定してスクリプトを実行

    ```
    $ python3 photo_datetime_fixer.py <対象フォルダ>
    ```

1. 指定した指定したフォルダ内のファイルを名前順で確認していく
1. 撮影日時 (`DateTimeOriginal`) の整合性が前後と取れていないファイルを対象に EXIF 情報を更新する
    - `DateTimeOriginal` の更新
        - 前後のファイルの撮影日時の間の日時(年〜秒)とする
        - 対象ファイルが複数の場合は均等に配分する
        - 1番目のファイルについてはなにもしない
        - 最後のファイル(たち)が対象の場合は直前の整合性が取れているファイルの日時に1秒ずつ足した日時に設定
    - `UserComment` の追記
        - `DateTimeOriginal` の更新内容を以下の形式で記録する  
            ```
            FixedDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>]
            ```
        - このコメントは後述の `photo_daytime_restorer` で利用する
        - もともとコメントが存在する場合には半角スペース区切りで追記

### 動作例
#### 実行前
|ファイル名|`DateTimeOriginal`|`UserComment`|
|-|-|-|
|0.JPG|2021:04:01 00:00:00|test0|
|1.JPG||test1|
|2.JPG|1999:01:01 00:00:00|test2|
|3.JPG|2021:04:01 03:06:09|test3|
|4.JPG|2021:04:01 04:08:12|test4|

#### 実行
```
$ python3 photo_datetime_fixer.py /xxx/test
+-------------------------+
| Photo Date & Time Fixer |
+-------------------------+
Target Dir: /xxx/test
Fix ID: 1618139631

0.JPG: Skip: No data before.
1.JPG: Need to fix.
2.JPG: Need to fix.
3.JPG: OK.
Fix!
  1.JPG: Fixed:  -> 2021:04:01 01:22:03
  2.JPG: Fixed: 1999:01:01 00:00:00 -> 2021:04:01 02:44:06
4.JPG: OK.

+-----------+
| 🍻Fixed🍻 |
+-----------+
```

#### 実行後
|ファイル名|`DateTimeOriginal`|`UserComment`|
|-|-|-|
|0.JPG|2021:04:01 00:00:00|test0|
|1.JPG|2021:04:01 01:02:03|test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03]|
|2.JPG|2021:04:01 02:04:06|test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06]|
|3.JPG|2021:04:01 03:06:09|test3|
|4.JPG|2021:04:01 04:08:12|test4|


## `photo_daytime_restorer.py`
日時を元に戻すスクリプト。
### 処理
1. 修正を行うフォルダと `photo_daytime_fixer` を実行したUNIX時間を指定してスクリプトを実行 

    ```
    $ python3 photo_datetime_restorer.py <対象フォルダ> <photo_daytime_fixerを実行したUNIX時間>
    ```

1. `UserComment` を参照することで対象ファイルを探し、EXIF 情報を更新する
    - `DateTimeOriginal` の更新
        - 元の日時に更新
    - `UserComment` の追記
        - `DateTimeOriginal` の更新内容を以下の形式で記録する  
            ```
            RestoredDateTimeOriginal_<このスクリプトを実行したUNIX時間>[<元の日時> -> <変更後の日時>]
            ```
        - もともとコメントが存在する場合には半角スペース区切りで追記

### 動作例
#### 実行前
|ファイル名|`DateTimeOriginal`|`UserComment`|
|-|-|-|
|0.JPG|2021:04:01 00:00:00|test0|
|1.JPG|2021:04:01 01:02:03|test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03]|
|2.JPG|2021:04:01 02:04:06|test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06]|
|3.JPG|2021:04:01 03:06:09|test3|
|4.JPG|2021:04:01 04:08:12|test4|

#### 実行
```
$ python3 photo_datetime_restorer.py test 1618139631
+----------------------------+
| Photo Date & Time Restorer |
+----------------------------+
Target Dir: test
Fix ID: 1618139631
Restore ID: 1618140405

0.JPG: Skip.
1.JPG: Restore.
  2021:04:01 01:02:03 -> 
2.JPG: Restore.
  2021:04:01 02:04:06 -> 1999:01:01 00:00:00
3.JPG: Skip.
4.JPG: Skip.

+-------------+
| 🍻Restored🍻 |
+-------------+
```

#### 実行後
|ファイル名|`DateTimeOriginal`|`UserComment`|
|-|-|-|
|0.JPG|2021:04:01 00:00:00|test0|
|1.JPG|2021:04:01 01:02:03|test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ]|
|2.JPG|2021:04:01 02:04:06|test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00]|
|3.JPG|2021:04:01 03:06:09|test3|
|4.JPG|2021:04:01 04:08:12|test4|


## `photo_daytime_forgetter.py`
日時の更新履歴を消すスクリプト。
### 処理
1. 修正を行うフォルダと `photo_daytime_fixer` もしくは `photo_daytime_restorer` を実行したUNIX時間を指定してスクリプトを実行  

    ```
    $ python3 photo_datetime_restorer.py <対象フォルダ> <photo_daytime_fixer/restorerを実行したUNIX時間>
    ```

1. `UserComment` を参照することで対象ファイルを探し、EXIF 情報を更新する
    - `UserComment` の更新
        - コメント中から指定した実行についての記述のみを削除する

### 動作例
#### 実行前
|ファイル名|`UserComment`|
|-|-|
|0.JPG|test0|
|1.JPG|test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ]|
|2.JPG|test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00]|
|3.JPG|test3|
|4.JPG|test4|

#### 実行
```
$ python3 photo_datetime_forgetter.py test 1618139631
+-----------------------------+
| Photo Fix/Restore Forgetter |
+-----------------------------+
Target Dir: test
Fix/Restore ID: 1618139631

0.JPG: Skip.
1.JPG: Forget.
    test1 FixedDateTimeOriginal_1618139631[ -> 2021:04:01 01:02:03] RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ]
 -> test1 RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ]
2.JPG: Forget.
    test2 FixedDateTimeOriginal_1618139631[1999:01:01 00:00:00 -> 2021:04:01 02:04:06] RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00]
 -> test2 RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00]
3.JPG: Skip.
4.JPG: Skip.

+---------------+
| 🍻Forgotten🍻 |
+---------------+
```

#### 実行後
|ファイル名|`UserComment`|
|-|-|
|0.JPG|test0|
|1.JPG|test1 RestoredDateTimeOriginal_1618140405[2021:04:01 01:02:03 -> ]|
|2.JPG|test2 RestoredDateTimeOriginal_1618140405[2021:04:01 02:04:06 -> 1999:01:01 00:00:00]|
|3.JPG|test3|
|4.JPG|test4|


# 留意事項
- 対象のファイルの EXIF 情報に元から `SubSecDateTimeOriginal` が含まれていた場合は年〜秒までは一緒に更新される
    - `SubSecDateTimeOriginal` には ms 以下の時間やタイムゾーンが書かれている
    - 年〜秒以外の部分はオリジナルのままになる
- 対象フォルダ内に EXIF 情報を更新できないファイル（ExifTool のサポート外、もしくは書き込み権限なし）のファイルがあった場合には、そのファイルについては何もしない
    - ただし、複数ファイルを一気に更新する際の時間間隔計算の分母にはカウントされる
- 対象のはずなのにうまく動作しないファイルもあった
    - サイズが小さいファイル
        - 大昔のiPhoneのスクショ
        - ガラケー時代の遺産ことデコ文字のファイル
    - 他にもあるかも
- PNG は EXIF 情報が正しく更新できた場合でも、Amazon Photos では日付なしに分類されてしまう
    - 悲しい
- 仕様上、大量にファイルがあり前後の正しいファイルの撮影時間の差が小さいと間隔が1秒未満になってしまい、正しい結果が得られない
    - `SubSecDateTimeOriginal` でなんとかしようとしましたが、ファイルによってタイムゾーンの表記があったりなかったりだったので断念
    - まあ、あまり起こり得ない & 起こってもあまり影響はない想定

# 余談
- Google フォトの無料枠が終了するということで Amazon Photos に乗り換えようとしたところ、順序の狂い方が Google のときより酷い気がしてすごくムカついたので2日くらいでバーっと書きました。
    - なので日時を示す EXIF タグのうち、Amazon Photos で日時として認識されているっぽい `DateTimeOriginal` を更新することにしました。
    - あまり Python に触ってこなかった人生ですが今どきそれはさすがにエンジニアとして、、、ということで Python で。
    - 最初は PIL (https://pillow.readthedocs.io/en/stable/#) を使おうとしましたが、HEIC に対応指定なさそうだったので断念。ExifToolを利用することに。
        - ならシェルスクリプトでよくね感もありましたがせっかくなので、、、
    - Python 初心者なので超拙いです多分、レビューお待ちしております。
- これ使っても Amazon Photos にアップ済みのファイルは修正できない
    - 一括削除しようとしましたがいつの間にかできない仕様に、、、
    - 3000件までなら一括選択できるスクリプトを書かれている方がいらっしゃったのでそれを利用しました。
    https://qiita.com/M_Kagawa/items/9b7dc3fa310a0170b6b2
- GitHubプライベートアカウント&Qiitaへの初投稿なのでお手柔らかに。
    - 勝手がアレ。
    - 誤字脱字もめちゃくちゃありそう。

以上、あざした！