週休3日サラリーマンのブログ

空気を読まないサラリーマンをやってます。1980生まれ男です。既婚。2011年生まれ息子、2013年生まれ娘あり。

Googleフォトの動画容量を10分の1に節約する方法

2021年6月にGoogleフォトが「有料化」してから約1年が経過した。
私は有料化後も撮影した全ての写真と動画をGoogleフォトにアップロードする生活を続けた。その結果、Googleドライブのストレージがどれくらい圧迫されたか。
2021年5月31日のGoogleドライブの使用容量が2.85GBだったのが、この1年間で11.9GBになった。約9GBの増加だ。GmailGoogle Docsによる容量消費は私の場合はほとんどないから、この増加分はほぼイコールGoogleフォトによるもの。このままだとあと半年ほどで無料分の容量を使い切ってしまう。
嫌だ!課金したくない笑!
でも、日時はもちろん、顔識別や地図上から写真や動画を絞り込める検索機能やインターネット環境さえあれば全ての写真が見られる利便性は捨て難い。

そこで、Googleフォトに格納する動画を低画質化してGoogleフォトの容量を節約することにした。
Googleフォトの仕様(初期設定)では、動画ファイルは比較的画質の良いHD解像度(1080p)での圧縮となるので、高い圧縮率は望めない。
私はオリジナルの写真・動画は自宅のNASに保管し、二重化バックアップしているので、Googleフォトに高画質は求めない。そりゃあ、Googleフォトでも高画質で見られるならそれに越したことはないが、それで延々と課金が必要になるなら無料の範囲で利用できるように低画質に圧縮する方を選ぶ。
ファイルの保管は自宅NASのHDD、ファイルの検索とスマホでの閲覧はGoogleフォトという使い分け。

まず、1年分の動画ファイルをGoogleフォトから一括ダウンロードして容量を調べる。
402ファイル、6.14GBだった。
9GBのうち6.14GBは動画が使っていたことになる。やはり動画ファイルが占める割合は大きく、これを圧縮することができれば効果は大きい。

動画圧縮のサンプルとして用いる動画ファイルは、iPhone SE(第3世代)で撮影した約60秒のMOVファイル。「MediaInfo」というアプリで動画情報を調べると、映像と音声の仕様は以下の通り。
・映像が(15.3Mbps, 1920*1080(16:9), 30fps, AVC(High@L4))
・音声が(175kbps, 44.1kHz, 2ch, PCM)

この動画ファイルのオリジナルの容量と、Googleフォト上の容量(Googleフォトに一度アップロードした後ダウンロード)は以下の通り。
・オリジナル(iPhoneSE3オリジナル):126MB
Googleフォト上の容量:66MB

これを低画質で再エンコードする。Macのアプリ「HandBrake」を用いた。この分野では定番のオープンソース・ソフトウェアで、無料で使える。

「HandBrake」はキュー機能により複数ファイルを順次エンコードしていくこともできるが、今後日常的に実施するタスクになることを考えるとコマンドラインで動かしたい。CLI版の「HandBrake CLI」というのが見つかったのでこれを使う。

「HandBrake CLI」のインストールは以下の通り。

# この1行のコマンドでインストール完了。
$ brew install handbrake
# インストール確認。5画面分くらいの長〜いヘルプが表示されれば成功。
$ handbrakecli --help

エンコードのパラメータはコマンドラインオプションで指定できる。いろいろ試して、以下に決めた。スマホの画面の大きさで見て、少しブロックノイズが見える位の画質だ。
・コンテナ方式はMP4(出力ファイルの拡張子で自動判定される)
・フレームレート30fps, 固定フレームレート
・2passエンコード
・映像設定(480kbps, ビデオサイズ縦横最大500ピクセル・記録アスペクトと表示アスペクトが同じ)
・音声設定(64kbps, モノラル)

$ handbrakecli -i original.MOV -o test.mp4 -r 30 --cfr --two-pass --vb 480 --maxHeight 500 --maxWidth 500 --non-anamorphic --ab 64 --mixdown mono

この設定で、先ほどのサンプル動画ファイルが4.5MBになった。Googleフォトのデフォルト設定の容量と比べるとこうなる。
Googleフォト上の容量:66MB
・上記設定で低画質エンコード:4.5MB
Googleフォトにそのままアップロードする場合に比べ、10分の1以下に圧縮される計算!いい感じだ。

さて、ファイルサイズをいい感じに圧縮することに成功したが、その副作用として、動画ファイルが持っていた「撮影日時」に相当する情報は失われ、全てエンコードを実施した時刻に置き換わってしまう。
写真・動画管理において、「撮影日時」は極めて重要な情報なので、ここはしっかりやりたい。オリジナルの動画ファイルの撮影日時を取得・記憶しておき、エンコード実施後にそれを書き戻す。

Exifに規格が統一された静止画と異なり、動画ファイルのメタデータは規格が統一されておらず、撮影した機器などによって「撮影日時」の取得方法が異なるが、私の場合、いずれのオリジナル動画ファイル(iPhone撮影、SONY Handycam撮影)のタイムスタンプ(mtime:変更日時)も本来の撮影日時と一致していたのでMac標準の"date"コマンドでこれを取得しておく。

$ date -r  original.MOV
2022430日 土曜日 123456秒 JST

あらかじめ取得しておいたこの日時を、エンコード後のMP4動画ファイルのどこにセットするのか。
MP4動画ファイルのメタデータコマンドラインツール"exiftool"で見ると次のようになる。

$ exiftool test.mp4 
ExifTool Version Number         : 12.01
File Name                       : test.mp4
()
File Modification Date/Time     : 2022:06:08 21:00:00+09:00  # (F1)ファイルタイムスタンプ(mtime:変更日時)
File Access Date/Time           : 2022:06:08 21:00:00+09:00  # (F2) ファイルタイムスタンプ(atime:アクセス日時)
File Inode Change Date/Time     : 2022:06:08 21:00:00+09:00  # (F3) ファイルタイムスタンプ(ctime:inode変更日時)
()
Create Date                     : 2022:06:08 21:00:00  # (M1)
Modify Date                     : 2022:06:08 21:00:00  # (M2)
()
Track Create Date               : 2022:06:08 21:00:00  # (M3)
Track Modify Date               : 2022:06:08 21:00:00  # (M4)
()
Media Create Date               : 2022:06:08 21:00:00  # (M5)
Media Modify Date               : 2022:06:08 21:00:00  # (M6)
()

(F1)〜(F3)はファイルとしてのメタデータ(タイムスタンプ)で、(M1)〜(M6)はMP4コンテナが持つメタデータ

Googleフォトは、基本的には画像/動画ファイルのメタデータから撮影日時相当の情報が取得できればそれを優先して使用し、そうでなければファイルタイムスタンプを利用する模様。実際に、メタデータとして日時情報を持たない規格(多分)であるPNGファイルをGoogleフォトにアップロードすると、「(F1)ファイルタイムスタンプ(mtime:変更日時)」が撮影日時として使用される。
なお、上記3つのファイルタイムスタンプはLinuxファイルシステム由来で、MacOSXでは他に4つのタイムスタンプが追加されているそう。MacのFinderでは「変更日」と「作成日」が表示されるが、「変更日」はLinuxファイルタイムスタンプのmtimeで、「作成日」はOSXで追加された日時情報。
以下のページの説明が分かりやすかった。
https://zariganitosh.hatenablog.jp/entry/20130404/attribute_datetime

以上から、(F1)および(M1)〜(M6)の日時情報を正しい撮影日時で上書きしておけば、Googleフォトにアップロードしたときに正しい撮影日時が認識されるはずだ。
(M1)〜(M6)の日時情報の書き換えコマンドは次の通り。

$ exiftool -overwrite_original -alldates='2022:04:30 12:00:00+09:00' test.mp4

書き換え結果を確認すると、あれれ?
・(F1)〜(F3)のファイルタイムスタンプが現在時刻になった。これはOK.
・(M1),(M2)は指定日時に書きかわった。これも想定通り。
・(M3)〜(M6)の日時は変化なし、これは想定外。なぜ?
・「(M7)Date/Time Original」という項目が新規にでき、指定日時がセットされた。これも想定外。
いろいろ想定外の事もあるが、このまま続ける。

$ exiftool test.mp4 
ExifTool Version Number         : 12.01
File Name                       : test.mp4
()
File Modification Date/Time     : 2022:06:08 22:00:00+09:00  # (F1)現在日時に書きかわった
File Access Date/Time           : 2022:06:08 22:00:00+09:00  # (F2)現在日時に書きかわった
File Inode Change Date/Time     : 2022:06:08 22:00:00+09:00  # (F3)現在日時に書きかわった
()
Date/Time Original              : 2022:04:30 12:00:00+09:00  # (M7)新規に項目ができ、指定日時がセットされた
Create Date                     : 2022:04:30 12:00:00  # (M1)指定日時に書きかわった
Modify Date                     : 2022:04:30 12:00:00  # (M2)指定日時に書きかわった
()
Track Create Date               : 2022:06:08 21:00:00  # (M3)変化なし
Track Modify Date               : 2022:06:08 21:00:00  # (M4)変化なし
()
Media Create Date               : 2022:06:08 21:00:00  # (M5)変化なし
Media Modify Date               : 2022:06:08 21:00:00  # (M6)変化なし
()

「(F1)ファイルタイムスタンプ(mtime:変更日時)」は以下コマンドで書き換える。ついでに、OSXの「作成日時」も書き換えておく。

# (-d: 作成日時, -m: 変更日時)
$ setfile -d "04/30/2022 12:00:00" -m "04/30/2022 12:00:00"  test.mp4

これで低画質版の動画ファイルの作成がようやく完了。
シェルスクリプトを書いてこの処理を1年分の動画ファイル402個に適用すると、合計サイズが719MBになった。Googleフォト上の動画容量は6.14GBだったから、約8.5分の1のサイズに圧縮された。サンプル動画で試したときより圧縮率が悪いが、条件によって変わるのだろう。
Googleフォトに再エンコード動画をアップロードして従来の動画を削除する。計算によれば、6.14GB-0.72GB = 5.42GBの容量が節約できる計算。
Googleフォトのストレージ欄を確認すると、使用量が11.9GBから6.6GBになり5.3GB減少!再エンコードにより圧縮した容量とほぼ計算が一致し、今回の目論みは一応成功した。

しかし、細かい問題点がふたつ。
一つ目は、撮影日時が実際よりも9時間遅く認識されてしまうこと。9時間ずれといえば、原因が日本のタイムゾーン(JST, +9)の解釈の問題にあることはほぼ間違いない。規格上、タイムゾーンを記録することが明確に定められていればこのような問題は起こらないが、規格に解釈の余地があったりするとこういう問題につながるのだろう。この問題については、原因を深追いしてもいいことがなさそうなので、サクッと対症療法を行う。Googleフォトで今回の動画を複数選択して、9時間シフトさせて、解決した。
二つ目の問題点は、元々のiPhoneのMOVファイルには存在したGPS座標情報が、MP4変換により失われてしまうこと。これは今回は諦めた。2年くらい後に再調査すれば、ツールのUpdateなどにより解決するかもしれない。

最後に、今回作成したシェルスクリプトを添付する。おわり。

#!/bin/sh

# encodeMovForGooglePhoto.sh
# 動画ファイルをGooglePhotoにアップロードするために低画質エンコードするスクリプト

# パラメータ)
# 引数1: 変換したい動画ファイルが格納されたディレクトリ

# 機能説明)
# ・引数1で指定したディレクトリ内の動画ファイル(*.MOV/MP4/mp4)を低画質エンコードし、
# ・"MovForGooglePhoto/" という名称のディレクトリを新規作成し、その中にエンコードした動画ファイルを出力する。
# ・出力動画ファイル名は"orgfilename_s-size.mp4"

# 使用例)
# ./encodeMovForGooglePhoto.sh  photo_dir

# 1.指定されたディレクトリの存在有無チェック
in_dirpath="$1"
if [ ! -e "$in_dirpath" ] ; then
    echo "指定されたディレクトリが存在しません"
    exit 1
fi

# 2.ディレクトリ内の動画ファイル数を調べる
file_num=`find "$in_dirpath" -name "*.MOV" -or -name "*.MP4" -or -name "*.mp4" -type f | wc -l`
echo "動画ファイル数: $file_num"
if [ "$file_num" -eq 0 ] ; then
    echo "動画ファイルが存在しません"
    exit 2
fi

# 3.出力ディレクトリ削除・再生成
rm -rf "MovForGooglePhoto"
mkdir "MovForGooglePhoto"

# 4.ファイル毎のループ処理
# (空白を含むPathだと誤動作するので注意)
count=1
infile_paths=`find "$in_dirpath" -name "*.MOV" -or -name "*.MP4" -or -name "*.mp4" -type f`
for infile_path in $infile_paths;
do
    echo "$count : $infile_path"

    # (1) オリジナルの動画ファイルのタイムスタンプを記憶しておく
    date_str=`date -r  "$infile_path"  '+%Y/%m/%d %H:%M:%S'`

    # (2) 出力動画ファイルパスの文字列処理
    infile_name=`basename "$infile_path"` # filename.MOV
    outfile_name=`echo "$infile_name" | sed 's/\.[^\.]*$/_s-size.mp4/'` # filename_s-size.mp4
    outfile_path="MovForGooglePhoto/$outfile_name" # MovForGooglePhoto/filename_s-size.mp4

    # (3) 動画ファイルをエンコード
    # ・コンテナ方式はMP4(出力ファイルの拡張子で自動判定される)
    # ・フレームレート30fps, 固定フレームレート
    # ・2passエンコード
    # ・映像設定(480kbps, ビデオサイズ縦横最大500ピクセル・記録アスペクトと表示アスペクトが同じ)
    # ・音声設定(64kbps, モノラル)
    # (標準出力と標準エラー出力を両方ともログファイルに出力)
    handbrakecli -i "$infile_path" -o "$outfile_path" -r 30 --cfr --two-pass --vb 480 --maxHeight 500 --maxWidth 500 --non-anamorphic --ab 64 --mixdown mono >& MovForGooglePhoto.log

    # (4) 日時のフォーマットを変換する文字列処理

    # exiftoolコマンド用:(yyyy/mm/dd hh:mm:ss) -> (yyyy:mm:dd hh:mm:ss)
    date_str2=`date -j -f "%Y/%m/%d %H:%M:%S"  "$date_str"  +"%Y:%m:%d %H:%M:%S"`

    # setfileコマンド用:(yyyy/mm/dd hh:mm:ss) -> (mm/dd/yyyy hh:mm:ss)
    date_str3=`date -j -f "%Y/%m/%d %H:%M:%S"  "$date_str"  +"%m/%d/%Y %H:%M:%S"`

    # (5) エンコードした動画ファイルのメタデータを書き換え
    exiftool -overwrite_original -alldates="$date_str2"  "$outfile_path"

    # (6) エンコードした動画ファイルのタイムスタンプを書き換え
    # (-d: 作成日時, -m: 変更日時)
    setfile -d "$date_str3" -m "$date_str3"  "$outfile_path"

    ((count=$count+1))
done