googleフォトの空き容量を広げる（iphone動画の縮小編）

この記述で、動画を縮小すると同時に、動画のメタデータと更新日時を撮影日時にセットすることが出来ます。撮影日時が入ることで、ローカルでもクラウドでも管理が楽になります。


**環境**
iPhone 11 iOS ver 15.1
Windows10 (iTtunes入り)



**課題**
・FHDで撮影した動画がgoogleフォトにアップロードされて１５G制限の容量を圧迫している。
・iphoneとUSB接続したwindowsで読み込んだ動画（MOVファイル）は撮影日時データを持っている様だが、ファイルの更新日時には反映されていない。
・ffmpegを使って（Windows上で）縮小画像を簡単に得ることが出来るが、撮影日時データが失われるので、縮小動画をgoogleフォトにアップロードしたときに、作業日の日付になってしまう。

**解決策**
pythonで簡単なプログラムを作成し、撮影日時が反映された縮小動画をwindows上で作成する。
これをgoogleフォトにアップロードする。大きな元画像はブラウザなどでgoogleフォトにアクセスし別途削除する。削除対象はgoogleフォトで「mov」を検索することで見つけられる。また、縮小動画の作成の際はmp4ファイルとすることで識別しやすくした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/8f1e534f-7e7d-6764-4367-fc09619fe9ba.png)

**よく分からないこと**
googleフォトで元画像がmovで検索出来るが、変換後画像をmp4で検索しても一部しか出てこない。反映されるのに時間がかかるのか？
しかし、jpgで検索すると先ほどアップロードした写真がちゃんと表示される。

**その他**
googoleフォトはクラウド側が**正**なので、ローカルフォルダ内で縮小や加工をしても反映されない。このため、変換前の大きなサイズの動画や写真は手動で削除する必要がある。（良い方法があれば教えてください）



## 縮小動画作成の手順
### ffmpegの準備
公式サイトから、ソースコードをダウンロードしてビルドする。のは面倒なので、公式のリンクからビルド済みのEXEファイルをダウンロードして解凍する。pathなど設定はpythonプログラムから直接指定するので特に不要。今回、リンク先からダウンロードしたのはffmpeg-2022-10-17-git-3bd0bf76fb-full_build.7z　７Z圧縮ファイルの解凍は「7-Zip File Manager」を使った。
https://ffmpeg.org/download.html#build-windows
https://www.gyan.dev/ffmpeg/builds/

### pythonの実行環境
今回はminicondaでpython3.9.12（作業時最新）をダウンロードしインストールした。
anacondaでも良い。Sony NNCのpython環境を流用することもできる。
https://docs.conda.io/en/latest/miniconda.html
https://qiita.com/katakaku/items/39a2694d45a2c8fcebd0

### エディタ
試行錯誤するなら必要。ここでは、VScodeを用いた。pythonのファイルを開いて、python拡張機能を追加する。pythonインタプリンタを指定するよう求められるので、anaconda miniconda等のpython.exeのありかを指定する。
https://azure.microsoft.com/ja-jp/products/visual-studio-code/

jupyternotebookでもよい。命令の実行だけなら、ターミナルで python test.py　でも。


## 全体のプログラム
#### ライブラリ
**subprocess　os　datetime glob**　の４つ。pipやcondaでインストールしなくても標準で入っている。

#### パスの指定
ffmpegと日付を含むメタデータを出力するtempファイルを指定している。
windowsで動かすので、区切り文字がバックスラッシュ2個であることに注意。
ffmpeg_path="D:\\temp_ffmpeg\\bin\\ffmpeg.exe"
temp_file="D:\\temp_ffmpeg\\bin\\temp.txt"

#### 関数　get_mytime(mp4_file):
動画ファイルを引数として、撮影日時を返す。mp4でもmovでもいい。
コマンドラインで、
```
ffmpeg -i 動画ファイル
```
　と命令すると得られる出力（標準エラー出力）をtempファイルに保存する。
　なお、標準出力はリダイレクト **＞**でファイルに落とし込めるが、標準エラー出力は**２＞**でリダイレクトする。
そのファイルから、creation_time　をキーワードに、撮影日時を含む行を抽出し、「：」の位置を手がかりに撮影日時を抽出し戻り値とする。撮影日時は日本時間ではないので+0000と明言している。
subprocess.call(命令文 ,shell=True)とすると、DOS窓（コンソール）で打ち込んだ時と同じことが出来る。

メタデータを得る手段としては、
```
ffmpeg.exe -i 動画ファイル -f ffmetadata temp.txt
```
もあるが、日時が入っていないiphoneで撮影した動画ファイルもあったため、上記の法が堅牢である。

#### 関数　kousin_time_set(my_time,out_mov_file):
縮小後の動画の更新日時（ファイルエクスプローラやファイルのプロパティで確認できる）を撮影時刻にセットする関数。引数として、get_mytime関数の戻り値と動画ファイルのパスを与える。
os.utime というメソッドを利用する。

### 本文
original_mov_file_folder="D:\\temp_ffmpeg\\original_mov\\*.MOV"
out_mp4_file_folder="D:\\temp_ffmpeg\\out_mp4\\"
入力動画用のフォルダと出力動画用のフォルダを指定する。

original_mov_files = glob.glob(original_mov_file_folder)
入力用フォルダの複数の動画のフルパスをリスト形式で得る。
以下、forループで動画ファイル1つずつループを回す。

out_mp4_file = out_mp4_file_folder + os.path.splitext(os.path.basename(original_mov_file))[0]+"_HD.mp4"
出力動画のファイル名を、オリジナルのファイル名に_HDを加え、拡張子をmp4にする。

エスケープ文字をうまくページで表せないので、画像で記載。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1520250/60b6d3e5-9960-be95-c065-29419c8dce6c.png)

final_cmd として、ffmpegで動画を縮小しつつ、日時メタデータを加えるコマンドを作成する。
-vf scale=-1:ih*2/3 とすることで、元画像の２／３のサイズ（縦横の長さ）になる。ihが高さで、幅は－１としているのは自動設定の意味。FHDの２／３のサイズとするとHDサイズになる。ファイルサイズは約半分になる。
https://irilyuu.com/ffmpeg/ffmpeg-resize/
-c:a copy を加えることで、音声は再エンコードしない。処理時間も早くなる。

なお、GPUボードがあれば、-c:v h264_nvencを加えると処理が早い。（GTX1050で8-10倍速）
https://neos21.net/blog/2022/01/28-01.html
https://www.usagi1975.com/202107182149/
今回ダウンロードしたffmpegはオプションを加え、Nvidiaのドライバを更新するだけで利用できた。


動画作成後、ファイルの更新日時を撮影日時に合わせるため、kousin_time_set関数を実行して終わり。（これを利用して、オリジナルの動画の更新日時を修正することもできる）20221116追記：標準時を標準時と明記せずdatetimeメソッドに渡していたので、9時間ずれていたのを修正。tzinfo=datetime.timezone.utcを入れた。

以下、今回の全コード。パスを適当に改変して利用する。


```py
import subprocess
import os
import datetime
import glob
ffmpeg_path="D:\\temp_ffmpeg\\bin\\ffmpeg.exe"
temp_file="D:\\temp_ffmpeg\\bin\\temp.txt"

# ファイルパスの区切りがLinuxでは/スラッシュだが、windowsではバックスラッシュ￥なのでエスケープが必要。

def get_mytime(mp4_file):

    if(os.path.isfile(temp_file) == True):
        os.remove(temp_file)#あらかじめTempファイルを削除する
        
    # 作成時刻などをtempファイルに出力する。標準エラー出力をtempファイルに入力する
    subprocess.call(ffmpeg_path + ' -i ' + mp4_file + ' 2> ' + temp_file ,shell=True)

    #tempファイルに出力した内容から、作成日時をmy_time文字列変数として抽出する
    with open(temp_file) as f:
        for i, line in enumerate(f):
            if 'creation_time' in line:# creation_timeを含む行を調べる
                break

    print(i)
    print(line)
    index = line.find(":")
    print(index)
    print(line[index+2:])
    my_time = line[index+2:index+21]#最後の改行は落とす
    print("+++++++++++++++++++++++++++")
    print(my_time)
    return my_time +'+0000'# 作成日時をmy_time文字列変数として戻す　creation_timeは標準時なので、明記する


def kousin_time_set(my_time,out_mov_file):
    y=int(my_time[0:4])
    m=int(my_time[5:7])
    d=int(my_time[8:10])
    h=int(my_time[11:13])
    min=int(my_time[14:16])
    s=int(my_time[17:19])
    atime = datetime.datetime(year=y, month=m, day=d, hour=h, minute=min, second=s, microsecond=0,tzinfo=datetime.timezone.utc)

    print(y,m,d,h,m,s)

    os.utime(path=out_mov_file,times=(atime.timestamp(),atime.timestamp()))



original_mov_file_folder="D:\\temp_ffmpeg\\original_mov\\*.MOV"
out_mp4_file_folder="D:\\temp_ffmpeg\\out_mp4\\"

original_mov_files = glob.glob(original_mov_file_folder)

for original_mov_file in original_mov_files:

    #original_mov_file="D:\\temp_ffmpeg\\bin\\IMG_3515cp.MOV"
    #original_mov_file=original_mov_files[0]　１つめのファイルを個別に指定するとき

    out_mp4_file = out_mp4_file_folder + os.path.splitext(os.path.basename(original_mov_file))[0]+"_HD.mp4" 
    #元の名前にHDを加えて、拡張子をｍｐ４に変える。パスも指定している。

    my_time=get_mytime(original_mov_file)

    meta_option = "-metadata" + " creation_time"+"=" + "\"" + my_time + "\""
    print(meta_option)

    #CPUのとき final_cmd = ffmpeg_path + " -i " + original_mov_file + " " + meta_option + " -vf scale=-1:ih*2/3 " + out_mp4_file
    final_cmd = ffmpeg_path + " -i " + original_mov_file + " " + meta_option + " -vf scale=-1:ih*2/3 " +"-c:v h264_nvenc "+ "-c:a copy "+out_mp4_file#GPU対応

    # -vf scale=-1:ih*2/3 とすると、元画像FHDの２／３の大きさでHD相当の大きさになる。

    subprocess.run(final_cmd, shell=True)#ffmpegのコマンドを実行して、縮小されたmp4ファイルを作成する。

    kousin_time_set(my_time,out_mp4_file)#作成した動画の更新日時を撮影日時にする。
```

## その他のトライアル

このサイトから大きなヒントを得ましたが、そのままでは自分の環境では動かなかった。

https://udon.little-pear.net/ffmpeg-fix-gopro-creation-time/


簡潔に利用法が記述されているサイト。メタデータのコピーについて記載があるが、自分の環境では動作しなかった。

https://xshop2.blogspot.com/2021/06/ffmpeg.html


メタデータがグローバルやチャプターごとなど適用範囲があることを記述している。

https://nico-lab.net/adding_metadata_with_ffmpeg/

公式サイト（翻訳して読むが難しい。wwwがあるのと、ないのとどっちを見るんだ？）

https://ffmpeg.org/ffmpeg.html

https://www.ffmpeg.org/ffmpeg-all.html

subprocessrun(   )がうまく使えなかった。バージョンアップにより機能が次々増えている模様。 shell=Trueオプションを付けることでうまくいった

https://docs.python.org/ja/3/library/subprocess.html

ファイル更新日時の操作については、以下のサイトを参考にしました。

https://www.curict.com/item/a1/a193618.html