豪鬼メモ

一瞬千撃

画像データの自動後処理スクリプト cram_image.py

カメラから写真をコンピュータに取り込んだり現像ソフトから書き出したりしてJPEGTIFFのファイルが大量にできるだろう。あなたはそれをハードディスクに保存するだろう。クラウドサービスにアップロードして保存するだろう。そのまま保存してもよいが、何らかの後処理をかけたいことがあるだろう。それを自動化するのが、このスクリプト、cram_image.py である。単にImageMagickとExifToolを順番に呼び出すPythonスクリプトなのだが、なかなかどうして便利なので公開してみる。MacOSLinux、その他のUNIX系と、Windowsで動くはず。
f:id:fridaynight:20180911235639p:plain

説明

典型的なユースケースは、Lightroom等で現像したTIFFデータを、Google Photosの無料ストレージにアップロードできるサイズにリサイズする工程を自動化することである。以前の記事にも書いたが、Google Photosの無料ストレージには、画素数が1600万画素まででデータサイズが50MBまでという条件を満たせば、容量無制限にデータをアップロードすることができる。また、JPEGは再圧縮がかかってしまうが、TIFFは無劣化でそのまま保存してくれるというのもポイントだ。ならば現像データをTIFFファイルにしてGoogle Photosに置くのはよい考えだ。TIFFをアップロードしてもJPEGとして表示してくれるし、元のTIFFをダウンロードすることもできるし、検索や加工や共有もできるし、何より完全無料なのだ。

素数が1600万画素まででデータサイズが50MiBまでという条件で最大品質の画像を保存したいなら、ZIP圧縮16ビットTIFFを1000万画素ほどにリサイズしてアップロードすると良い。1000万画素のTIFFをZIP圧縮しても50MBを切るとは限らないのだが、50MBを超えてしまう場合には、内部データのビット深度を14ビットや12ビットに落として圧縮率を上げる処理を段階的に行い、確実に50MBを下回るようにしてくれる。実際にはありえないことだが、8ビットまで落としても50MBを切らない場合には画素数を落とす処理を段階的に行うように実装されている。おそらく、1000万画素であれば95%以上のケースで16ビットで成功するはずだ、14ビットにフォールバックすることが4%くらい、12ビット以下にフォールバックすることが1%くらいあるかもしれないが、いずれにせよできるだけ高い色深度の画像データを50MiiB以下で自動的に作ってくれる。あとはできたデータをGoogle Photoにアップロードすればよい。

リサイズの際にキリがいいサイズにしてくれるのは、以前の記事で書いた通りのアルゴリズムによる。そして、リサイズ処理を行うにあたっては、これまた以前の記事で述べた色空間の問題やプロファイルの問題にもちゃんと対応している。このスクリプトを実装するにあたって調査しなおしたのだ。

リサイズの他にも、任意のトリミング、アスペクト比を回復するためのトリミング、ノイズ除去とその自動モード、アンシャープマスク、レベル補正とその自動モード、ガンマ補正とその自動モード、シグモイド補正、線形および非線形の彩度調整、およびプロファイルの変換ができる。Lightroomでも同等のことはできるが、コマンド一発でできるのはなかなか楽でいい。

入力フォーマットはTIFFJPEGJPEG 2000、PNG、GIF、BMPに対応していて、出力フォーマットとしてそれらを任意に選択して変換することもできる。

典型的な使い方

私がこう使いたいから作ったという使い方を説明する。まず、現像ソフトから写真をエクスポートする。その際に、16ビットTIFF形式を選択して、プロファイルは高色域なDisplay P3にしておく。書き出し時にリサイズやシャープネスはしない方がいい。それから cram_image.py を実行する。現像データが ~/Pictures に書き出されたとして、それをリサイズしたデータを ~/photos に保存するには、以下のようにする。出力先のディレクトリは予め作っておくこと。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos

そうすると、以下のようなログが出て、処理が完了する。見事、50MBの97%まで使い切った画像ファイルが生成された。

INFO  Processing "/home/mikio/Pictures/P9088382-16.tif" (size=84965818).
INFO  Converting image [in=tif] [out=tif] [resize from 4608x3072 to 3780x2520] [depth=16] [compress=zip].
INFO  Check the file size: 49998028 (97.31%) ... OK.
INFO  Copying EXIF metadata [in=tif] [out=tif] [note=t:tiff,s:4608x3072,d:16,c:65535,p:display-p3].
INFO  Copying ICC profile [Display P3].
INFO  Storing as "/home/mikio/photos/P9088382-16.tif".

ピクセル数の最大値は8ビット深度の画像と16ビット深度の画像で別々に指定できる。デフォルトでは、8ビット画像の上限は1600万ピクセル、16ビット画像の上限は1000万ピクセルになっている。そしてファイルサイズの上限は50MiBだ。つまりデフォルト設定のままで実行すれば、デジカメから取り込んだ8ビットJPEG画像も、現像ソフトで書き出した16ビットTIFF画像も、Google Photos無料枠での最高画質を目指してリサイズされる。リサイズの必要がないデータはそのままコピーされるので、無用な劣化の心配もない。

$ cram_image.py ~/Pictures/*.jpg --destination ~/photo

注意したいのは、Google PhotosにJPEGをアップロードすると、そこで再圧縮がかかることである。よって、JPEGファイルをTIFFにしてからアップロードするとよい。そうすれば再圧縮はかからないし、表示用のJPEGはちゃんと作成されるので、利便性も問題ない。大事なことなのでもう一度。上の方法でJPEGを縮小してからアップロードするのは実は意味がない。JPEGの場合、160万ピクセルを超える画像を送信したとしても、Google Photo側で勝手に縮小処理もかけてくれるからだ。わざわざ劣化を避けてアップロードするならば、TIFFに変換すべきだ。TIFF最強。

$ cram_image.py ~/Pictures/*.jpg --destination ~/photo --output_format=tif

三者に渡すためのJPEGデータを作るには、出力フォーマットをJPEGにして、画素数を減らして、アンシャープマスクをかけて、EXIFデータを削除する。EXIFデータを削除してもプロファイル関係の情報だけは残してくれるところもポイント。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos --output_format=jpg --max_area 3m --unsharp 1 0.5 --strip_meta

Google Photosへアップロードする場合に限らず、とにかくデータサイズを一定以下に抑えたいが、その設定がわからないという場合にもこのツールは有用だ。なにせ、ファイルサイズが閾値以下になるまで試行錯誤しながら画素数の縮小を行ってくれるのだから。例えばWebサイトにアップロードする予定の画像を512KiB以下に抑えて作りたいならこうする。

$ cram_image.py input.jpg --destination ~/photos --max_size=512k --unsharp 1 0.5 --strip_meta

ログを見ると涙ぐましさがよくわかる。まず元のサイズで保存を試みて、そのサイズが目標の1140%だと判明する。その状況から上限の画素数を推定して、その少し上から試しにリサイズして保存してみて、まだ大きかったら少し減らしてリサイズしてという処理を繰り返し、目標を達成したらそれを成果物とする。PDCAサイクル

INFO  Check the file size: 5841878 (1140.99%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 1152x1536] [depth=8].
INFO  Check the file size: 865716 (169.09%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 1080x1440] [depth=8].
INFO  Check the file size: 768321 (150.06%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 1044x1392] [depth=8].
INFO  Check the file size: 723933 (141.39%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 996x1328] [depth=8].
INFO  Check the file size: 664732 (129.83%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 944x1259] [depth=8].
INFO  Check the file size: 602003 (117.58%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 896x1195] [depth=8].
INFO  Check the file size: 548369 (107.10%) ... Retry.
INFO  Converting image [in=jpg] [out=jpg] [resize from 3456x4608 to 850x1133] [depth=8].
INFO  Check the file size: 500744 (97.80%) ... OK.

インストール

(追記:本ソフトウェアは後に「Jakuzure」としてパッケージされたので、そちらの説明をごらんください。)

cram_image.py を動作させるには、PythonImageMagickが必要である。Pythonのバージョンは3以上が必要で、2系では動かない。ImageMagickは6.9系以上が望ましい。ExifToolも入れておいた方が、EXIFタグ関係の処理が高速化するけど、なくても動くようになっている。

cram_image.py をダウンロードしたら、実行可能パーミッションをつけて、コマンド検索パスが通った適当な場所に置く。

$ chmod 755 cram_image.py
$ sudo cp cram_image.py /usr/local/bin

おまけで、各種ICCプロファイルをインストールするとよい。ダウンロードして展開したら、/usr/local/share/color/icc あたりに置く。プロファイル変換をするにはこの手順が必要となる。

$ unzip cram_icc.zip
$ sudo mkdir -p /usr/local/share/color/icc
$ sudo cp cram_icc/*.icc /usr/local/share/color/icc

一連のインストール作業の後に、--config オプションをつけて cram_image.py 実行すると、以下のような出力が得られるはずだ。

$ cram_image.py --config
supported format: TIFF
-- extensions: tif, tiff
supported format: JPEG
-- extensions: jpg, jpe, jpeg
supported format: JPEG 2000
-- extensions: jp2, j2k
supported format: PNG
-- extensions: png
supported format: GIF
-- extensions: gif
external command: identify
-- version: ImageMagick 6.9.3-3 Q16 x86_64 2016-02-09 http://www.imagemagick.org
external command: convert
-- version: ImageMagick 6.9.3-3 Q16 x86_64 2016-02-09 http://www.imagemagick.org
external command: exiftool
-- version: 9.08
icc profile: srgb
-- path: /usr/local/share/color/icc/sRGB.icc
icc profile: adobe-rgb
-- path: /usr/local/share/color/icc/Adobe-RGB.icc
icc profile: display-p3
-- path: /usr/local/share/color/icc/Display-P3.icc
icc profile: dci-p3
-- path: /usr/local/share/color/icc/DCI-P3.icc
icc profile: prophoto-rgb
-- path: /usr/local/share/color/icc/ProPhoto-RGB.icc
icc profile: rec-2020
-- path: /usr/local/share/color/icc/Rec-2020.icc
maximum area for normal color: 16000000
-- maximum size for 1:1: 4000x4000 = 16000000
-- maximum size for 5:4: 4440x3552 = 15770880
-- maximum size for 4:3: 4560x3420 = 15595200
-- maximum size for 7:5: 4620x3300 = 15246000
-- maximum size for 3:2: 4800x3200 = 15360000
-- maximum size for 8:5: 4992x3120 = 15575040
-- maximum size for 16:9: 5184x2916 = 15116544
-- maximum size for 2:1: 5600x2800 = 15680000
maximum area for high color: 10000000
-- maximum size for 1:1: 3100x3100 = 9610000
-- maximum size for 5:4: 3500x2800 = 9800000
-- maximum size for 4:3: 3600x2700 = 9720000
-- maximum size for 7:5: 3696x2640 = 9757440
-- maximum size for 3:2: 3780x2520 = 9525600
-- maximum size for 8:5: 4000x2500 = 10000000
-- maximum size for 16:9: 4160x2340 = 9734400
-- maximum size for 2:1: 4400x2200 = 9680000

ヘルプ

簡易ヘルプを見るには、--help オプションをつけて実行する。ここではオプションの説明の日本語訳を書いておこう。

  • -h, --help : ヘルプメッセージを出す。
  • --version : バージョン番号を表示する。
  • --config : システム設定を表示する。
  • --destination path : 出力ファイルを格納するディレクトリを指定する。デフォルトは ~/cram_image.py_output 。mikio@example.com:/home/mikio/spool のようなSCPの送信先ディレクトリも書ける。
  • --read_stdin : 標準入力から入力ファイルのパスを読み込む。
  • --input_dir path : ディレクトリにある入力ファイルを読み込む。
  • --ignore_fresh : 10秒以内に作成または更新された入力ファイルは無視する。
  • --max_inputs num : 読み込む入力ファイルの数を制限する。
  • --timestamp_name : タイムスタンプを出力ファイル名にする。
  • --sequence_name : シーケンス番号をファイル名にする。
  • --unique_name : 一意の名前を出力ファイル名にする。
  • --prefix str : 出力ファイル名に指定した接頭辞をつける。
  • --decollide_name : 出力ファイル名が既存のものと衝突した場合に、接尾辞をつけて上書きを回避する。
  • --year_subdir : 作成年のサブディレクトリの中に出力ファイルを格納する。
  • --date_subdir : 作成年月日のサブディレクトリの中に出力ファイルを格納する。
  • --avoid_mtime : 作成日時の推定にファイルシステムのmtimeを使わない。
  • --cleanup : 出力が成功したら入力ファイルを消す。
  • --stash path : 入力ファイルを移動させる先のディレクトリ。
  • --ignore_errors : 失敗したファイルがあっても停止せずに処理を続ける。
  • --max_area num : 低深度色(8ビット以下)の出力画像の最大画素数。0以下なら無制限。デフォルトは16m(1600万)画素。
  • --max_area_high num : 高深度色(9ビット以上)の出力画像の最大画素数。0以下なら無制限。デフォルトは10m(1000万)画素。
  • --max_size num : 出力画像の最大データサイズ(バイト数)を指定する。0以下なら無制限。デフォルトは50mi(50MiB)。
  • --max_depth num : 出力画像の最大色深度を指定する。0以下なら無制限。デフォルトは12ビット。
  • --intact : 入力データの縮小処理をしない。--max_area=0 --max_area_high=0 --max_size=0 --max_depth=0.の別名。
  • --trim_top pc : 画面上部のトリミングをパーセントで指定。
  • --trim_bottom pc : 画面下部のトリミングをパーセントで指定。
  • --trim_left pc : 画面左部のトリミングをパーセントで指定。
  • --trim_right pc : 画面右部のトリミングをパーセントで指定。
  • --trim t b l r : 上下左右のトリミングをパーセントで指定。
  • --trim_aspect w h : トリミングしてアスペクト比を指定したものにする。
  • --trim_core : 最大画素数を満たすべくトリミングする。アスペクト比は維持されるか、--trim_aspect に従う。
  • --resize w h : 画像を指定サイズにリサイズする。どちらかがゼロなら、長辺を合わせる。どちらかか両方が負数なら、縮小だけ行う。
  • --denoise num : 指定した半径でノイズ除去を行う。負数なら自動判定する。
  • --unsharp s g : 指定したシグマとゲインでアンシャープマスクをかける。シグマが負数ならぼかし処理を行う。
  • --level b w : 指定したブラックポイントとホワイトポイントのパーセンテージで、レベル補正を行う。
  • --gamma num : ガンマ補正を行う。ゼロか負数なら自動補正を行う。
  • --contrast s m : 指定したシグマとパーセントの中間点でシグモイド補正を行う。 負数なら逆関数が適用される。
  • --saturation num : 指定した係数で彩度を増やす。負数なら彩度のガンマ補正を行う。
  • --filter name : グレースケール変換を行う。[gray, gray-709, gray-r, gray-o, gray-y, gray-g, gray-b]
  • --profile name : 名前を指定してプロファイルの変換を行う。
  • --strip_meta : EXIF、ITPC、XMP等のメタデータを削除する。
  • --output_format fmt : 出力形式を指定する。[tif, jpg, jp2, png, gif, bmp]
  • --log_level level : ログレベルを指定する。[debug, info, warning, error]

細かい話だが、--max_area と --max_area_high と--max_size にはSI接頭辞が使える。160000000と書くのが面倒な場合に、16m と書ける。16M でもいい。k は1000、m は1000^2、g は 1000^3 の意味だ。また、ki と書くと1024、mi と書くと 1024^2、gi と書くと 1024^3 の意になる。

進んだ使い方

16ビットの色深度で保存するのは明らかにオーバースペックだ。ハイエンドのデジカメでも14ビットRAWだし、12ビットRAWの機種も普通にある。そしてImageMagickの作業空間が16ビットであることも考えると、保存時の色深度だけを大きくすることにあまり意味はない。保存時には12ビットで十分であり、cram_image.py のデフォルトもその設定になっている。12ビットの画像データをZIP圧縮TIFFに変換した場合、65%ほどに圧縮されることが多い。1200万画素の画像を圧縮すると44MB程度になる計算になるので、圧縮率の多少の変動があっても50MBに収まることがほとんどだ。できるだけ画素数を大きくしたい人にはこの設定がおすすめである。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos --max_area_high 12m

ちなみに、TIFFPNGの色深度として12ビットを指定した場合、実際のデータにおけるコンポーネントビット数は16ビットに設定される。その16ビット内の上位12ビットを使うということだ。内部で使われるdeflate圧縮の都合上、バイト単位になっていた方が最終的なデータサイズが小さくなるのだ。JPEG 2000の場合はその限りではなく、12ビットと指定したら12ビットのままでエンコード処理をかけて出力する。

このツールを導入してから、プロファイルの変換が楽になったので、ついにREC. 2020を導入したい。インストール手順のところで述べたICCファイルをインストールしたならば、--profile rec-2020 とか指定すると、現在の色空間からREC. 2020にデータを変換した上でそのプロファイルをつけてくれる。REC. 2020はDisplay P3の上位互換のような位置付けで、人間が知覚できる色のかなりの部分をカバーする色域を備えている。それでいてガンマ特性がsRGBと同じなので扱いやすい。色域が広いだけだったらLightroomPhotoShopでも使われているProPhoto RGBでもいいのだが、ProPhoto RGBは人間が知覚できる領域を越していて無駄が多く、ガンマが1.6なのもちょっと扱いにくい。REC. 2020はスーパーハイビジョンHDR+などの規格でも現実的に使われるっぽいので、表示環境もいずれはついてくるだろう。よって、将来の環境で閲覧することを見越したファイルをアップロードしたいなら、16ビットTIFFをREC. 2020プロファイルで書き出すのが理想だ。残念ながら現状でLihgtroomはREC. 2020プロファイルに対応していないので、ProPhoto RGBで書き出してから、それをREC. 2020に変換する手順をとる。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos --profile rec-2020

以前にも述べたが、バックアップはローカルとクラウドの両方に持っておくべきだ。ローカルにはローカルの、クラウドにはクラウドのリスクがある。さて、クラウドの方はGoogle Photosには1000万画素から1600万画素の画像をアップロードしておくとして、ローカルでは縮小しないで持っておいてもいい。その場合には、--intact を指定して画素数制限とデータサイズ制限を外せばいい。そうすると単にプロファイルの変換をしてからデータをコピーするだけのツールになってしまうのだが、それでもそこそこ便利だ。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos --intact --profile rec-2020

溜め込んである既存の大量の画像ファイルに一括処理を行いたい場合、コマンドラインからファイル名のリストを指定する方式だと破綻する。そこで、--read_stdin オプションを指定して標準入力からファイル名のリストを読み込ませることができる。これを find コマンドと組み合わせるのが常套手段だ。

$ find ~/photo-album/2018 -type f -name '*.tif' | cram_image.py --read_stdin --destination ~/photos ---intact --profile rec-2020

ハードディスクの容量単価は安いとはいえ、非可逆圧縮データを保存して容量を節約したいと考えることもあるだろう。JPEGは8ビット深度固定なので、高い色域のデータを保存するにはあまり向いていない。そこで、JPEG 2000を導入してみる。--output_format で出力形式をjp2に指定する。自動縮小処理を回避すべく、--max_area_high 0 --max_size 0 も指定する。

$ cram_image.py ~/Pictures/*.tif --destination ~/photos --max_area_high 0 --max_size 0 --profile rec-2020 --output_format jp2

だがしかし、大変残念なことに、JPEG 2000はプロファイルの持ち方が標準化されておらず、プロファイルを埋め込めないのだ。仕方ないので出力がJPEG 2000の場合はICCProfileNameというタグにプロファイル名だけを埋め込んでいる。それだけではまともな色で閲覧できないわけだが、閲覧時にそのメタデータを見て復元すれば何とか見られるようになる。日常的な閲覧はGoogle Photosで行うので、復元可能性だけ担保してあれば実用になるだろう。

$ find ~/photo-album/2018 -type f -name '*.jp2' | cram_image.py --read_stdin --destination ~/photos --intact --output_format jpg

広色域プロファイルで12ビット以上のTIFFJPEG 2000として画像を保存しておけば、用途に応じて画像を再加工する際にも色が破綻することがない。プリント写真と違ってデジタル写真は、色やコントラストやシャープネスは閲覧環境やその時の気分に合わせて設定すべきものだ。なので、保存する画像データはできるだけニュートラルな設定にして、シャープネスは一切かけない方がいい。もっと言えば、RAWファイルを保存するのが最も潰しがきくのだが、そうすると気軽に閲覧できないのが辛い。なので、私は、Lightroom上で選定したキープ写真の全ては基本的にTIFFGoogle Photosの無料枠にアップロードして、特に気に入ったRAWファイルだけはそのままローカルに保存しておくようにしている。RAWファイルをGoogle Driveにアップロードするのもよい考えだ(Google PhotosだとJPEGに変換されるので)。月に数枚のお気に入りを保存するだけだったら当面は容量が持つだろう。

持っている写真のサムネイルを大量に作る必要がある時は、こんな風にする。--trim_aspect で指定したアスペクト比になるようにトリミングして、その後に --resize でサイズを縮小している。アスペクト比を保ったままにしたい場合、--trim_aspect は省略する。サムネイルはデータサイズを小さくしたいので --strip_meta でメタデータとプロファイルを剥ぎ取っているが、sRGB画像以外でプロファイルを剥ぎ取ると色がおかしくなるので、--profile でsRGBに変換している。より正確に言うと、プロファイルがsRGBの場合にのみプロファイルデータが剥ぎ取られる仕様になっている。

$ find ~/photo-album/2018 -type f -name '*.tif' | cram_image.py --read_stdin --destination ~/photos --trim_aspect 1 1 --resize 256 0 --profile srgb --strip_meta --output_format jpg

出力ファイルが全て同一のディレクトリに入れられると管理が煩雑になるので、入力ファイルの作成日時ごとにサブディレクトリに分ける機能もある。--year_subdir は 1978 のような年のサブディレクトリを作り、--date_subdir は 1978-02-11 のような年月日のサブディレクトリを作る。両方指定すると 1978/1978-02-11 のようになる。作成日時はEXIFのDateTimeOriginalタグから読み取るが、それがなければファイルシステムのmtime属性を読む。mtimeを読みたくない場合には --avoid_mtime をつける。その際に日時が読み取れない場合は 0000 や 0000-00-00 のような名前になる。

$ find ~/photo-album -type f -name '*.tif' | cram_image.py --read_stdin --destination ~/photos --year_subdir --date_subdir --avoid_mtime

出力先のディレクトリは scp の出力指定も使えるので、出力結果をリモートのサーバに送ることもできる。scpは鍵を適切に設定してパスワードなしで通信できるようにしておくこと。

$ cram_image.py ~/Pictures/*.tif --destination mikio@example.com:photo-album --date_subdir

私がよくやるやるのは、Lightroomで書き出したTIFFファイルの出力をポーリングして、リサイズした上で即時に自分のサーバに送るという処理だ。その際には --ignore_fresh を指定しておくと、更新10秒以内のファイルを無視するので、書き出し中のファイルを処理することを防ぐことができる。Lightroomの出力処理がアトミックになっているか否かは知らないが、念のため。また、--cleanup を指定して、処理が成功した際には入力ファイルが消されるようにしている。これによって同じファイルは一度しか処理されなくなる。

$ while true ; do cram_image.py ~/Pictures/*.tif --destination mikio@example.com:photo-album --ignore_fresh --cleanup ; sleep 100 ; done

まともにポーリング処理的なことをしたいならcrontabで定期起動させて、特定のディレクトリのファイルを処理するだろう。その際に、--stash オプションで入力ファイルの退避ディレクトリを指定すると良い。処理中のファイルはどこか別の場所に退避させておき、処理が成功したらそこから消すようにする。処理に失敗した場合には入力ファイルは監視対象のディレクトリから消えるが退避先には残っているので、当該ファイルの回復処理や原因解明に役立つ。ファイル移動はアトミックに行われるので、万が一二重起動してしまった際のレースコンディションを回避することにもなる。ただし、実際のところ、cram_image.py はロックファイルとlockf関数を使った排他制御を実装しているので、二重起動してもどちらかがファイルの読み取りに失敗して終了するだけなので問題ない。

$ cram_image.py /home/mikio/Pictures/*.tif --destination mikio@example.com:photo-album --ignore_fresh --cleanup --stash /home/mikio/cram_image.py_stash

なお、UNIX系でcrontabを使って二重起動を防止しつつ定期起動するには、以下のようにcrontabを設定する。こうすると1分に1回の頻度で /tmp/auto_photo_upload.lock をロックした上で、/usr/local/sbin/auto_photo_upload.sh を実行するようになる。ロックが獲得できない場合には何もしない。で、あとはそのスクリプトに適当な処理を書けばいい。findで入力ファイルのリストを取るとして、findの処理はcram_image.pyの外側の話なので、排他制御を別途に施すのが望ましい。

*  *  *  *  *   flock -n /tmp/auto_photo_upload.lock /usr/local/sbin/auto_photo_upload.sh >>/var/log/auto_photo_upload.log

実際にImageMagickやExiftoolがどんな風に呼ばれているか興味がある人は、--log_level debug をつけて実行するとよい。そんなに複雑ってわけでもないけれど、手でやるのが面倒すぎるってことは分かると思う。

いちおうユニットテストも書いてあるので、実行したい場合には、--test フラグをつける。テストはImageMagickとExiftoolと各種ICCプロファイルがインストールされていることを前提条件とする。

$ cram_image.py --test

まとめ

写真を現像してクラウドサービスとローカルストレージに保存するための一連の前処理を自動化するスクリプトを書いてみた。条件を変えながら投機的にリサイズを試みるのがポイント。みんな大好きだけどスパルタンすぎるImageMagickだが、個々の処理を手で入力するのは面倒すぎる。こうやってスクリプトを書いて自動化すると、かなり強力で便利なツールになる。

sRGBのJPEGを保存するのもいいけど、Display P3やRec.2020の16ビットTIFFJPEG 2000を保存するのも現実的になってきた。容量の問題は大容量ハードディスクの低価格化とクラウドサービスの無料化で克服されたのだから、より高品質なデータの保存を目指すべきだ。このツールがその一助になれば幸いである。