watermint.org - Takayuki Okazaki's note

Disk image file (.dmg) from command line

I prefer .dmg instaed of zip for archiving project data, etc. .dmg is handy for refering files, modify contents without extract files to somewhere.

.dmg can usable as like USB drive. Disk Utility tool can create/update .dmg from folder with various options. Options are like encryption, readonly, compression, etc.

But if you have tens of folders to archive, it’s better to use command line tools.

Create encrypted .dmg file

hdiutil is command line version of Disk Utility app. This command can mount/unmount/create/update disk image files. Please see man hdiutil for more detail.

Below script is part of my workflow of archiving project files. I’m using encrypted .dmg for archive. The script require prepare password file under $HOME/.dmg-password. Please create and store password for .dmg without LF.

And update permission like chmod 600 $HOME/.dmg-password to prevent read from other users. This sequence using password and encryption. But it’s not strong enough, reason described below.

#!/bin/sh

if [ $# -lt 2 ]; then
  echo $0 SRC_DIR DEST_DIR
  exit 1
fi

SRC=$1
DST=$2
PWD="$HOME/.dmg-password"

if [ ! -e $PWD ]; then
  echo Disk Image password not found
  exit 2
fi

for t in "$SRC"/*; do
  if [ -d "$t" ]; then
    echo Creating: $t
    n=$(basename $t)
    cat $PWD |                \
      hdiutil create          \
        -srcfolder "$t"       \
        -fs HFS+              \
        -encryption AES-128   \
        -format UDBZ          \
        -stdinpass            \
        "$DST/$n.dmg"
  fi
done

Preset password for .dmg in Key Chain

It’s kind of pain in neck entering password for opening .dmg everytime. If you open .dmg from Finder.app, the password dialog refuse copy & paste operation.

Password dialog

There is option “remember password in my keychain”. Concept is similar to this.

The password for disk image is stored in keychain which identified by UUID of .dmg. The UUID is referable by command like below.

$ hdiutil isencrypted YOURDISKIMAGE.dmg

Now you can store password through security command.

$ security add-generic-password -a (UUID above) -D "disk image password" -s (YOUR DISK IMAGE).dmg -w (PASSWORD OF DISK IMAGE)

Unfortunatelly, there are no option like -stdinpass. So the password must be passed through command line argument. This mean optential leak through ps command or shell history.

By the way, I’m using below script for preset password to disk images.

#!/bin/sh

if [ $# -lt 1 ]; then
  echo $0 [dmg file]...
  exit 1
fi

PWD=$HOME/.dmg-password

for FILE in "$@"; do
  UUID=$(hdiutil isencrypted "$FILE" 2>&1 | grep uuid | awk '{print $2}')
  BASE=$(basename $FILE)

  echo File: $BASE
  echo UUID: $UUID

  security add-generic-password -a $UUID -D "disk image password" -s $BASE -w $(cat $PWD)
done

When opening .dmg from Finder, operating system ask authorisation of using password by diskimages-helper.

Confirmation dialog

You can skip this dialog by -A option of security command, but this option authorise for all applications. It’s better not use this -A option for better security.

ディスクイメージファイル(.dmg)をコマンドラインから

Macでたくさんのファイルをひとまとめにするのであればzipで圧縮するよりも、ディスクイメージファイルにしたほうが便利なこともあります。zipだと標準では中身を確認するのに毎回どこかに展開しないといけないですし、ファイルの内容を変更するのも面倒です。zip用のツールを使えばおそらくそれらも解決できると思いますが、個人的にはディスクイメージファイル(.dmg)を使うことの方が多いです。

ディスクイメージファイルは外付けディスクのようなイメージでマウントして使えます。macOSに標準添付されている「ディスクユーティリティー」ツールを使えばこのイメージファイルを簡単に作ることができます。読み取り専用、読み書き可能、暗号化、圧縮などオプションを選ぶこともできます。

終了したプロジェクトのファイル一式は.dmgファイルとしてまとめていますが、まとめたいプロジェクトファイルがたくさんあったり、ワークフローを自動化したいとなってくるとコマンドライン操作を覚えると便利です。

ディスクイメージファイルの作成

hdiutilコマンドを使うとディスクイメージファイルの作成や設定変更、ディスクイメージファイルのマウント、アンマウントなどさまざま操作可能です。詳しくはman hdiutilをご参照いただくとして手元での利用例をご紹介します。

フォルダ以下にあるサブフォルダをそれぞれ.dmgとして一括変換したい場合に使っているスクリプトです。それぞれ一応暗号化しておこうということで、暗号化用のパスワードを$HOME/.dmg-passwordというファイルにあらかじめ格納してあります。パスワードファイルには最後に改行が入らないようにつくっておいてください。

あとパーミッションも chmod 600 $HOME/.dmg-passwordなど他者から読み出せないようにしておきます。そうはいっても、後述しますが一連のプロセスはさほど安全性が高いわけではありませんので「添付ファイルはパスワードつきzipでパスワードは別メールですぐ送信!」とあまり変わらないセキュリティーレベルとお考えいただいていいかと思います。

#!/bin/sh

if [ $# -lt 2 ]; then
  echo $0 SRC_DIR DEST_DIR
  exit 1
fi

SRC=$1
DST=$2
PWD="$HOME/.dmg-password"

if [ ! -e $PWD ]; then
  echo Disk Image password not found
  exit 2
fi

for t in "$SRC"/*; do
  if [ -d "$t" ]; then
    echo Creating: $t
    n=$(basename $t)
    cat $PWD |                \
      hdiutil create          \
        -srcfolder "$t"       \
        -fs HFS+              \
        -encryption AES-128   \
        -format UDBZ          \
        -stdinpass            \
        "$DST/$n.dmg"
  fi
done

ディスクイメージファイルのパスワードをKeyChainにプリセットしておく

暗号化しておいたのはいいですが、作成したディスクイメージファイルを開く際にいちいちパスワードを入力するのは面倒です。Finderからダブルクリックしてマウントしようとしたときに聞かれるパスワードダイアログはコピー&ペーストを受け入れてくれないというのもさらに面倒さを増しています。

パスワードダイアログ

パスワードをキーチェインに記憶させるというオプションがありますが、あらかじめコマンドラインから記憶させておけばいいという発想です。

ディスクイメージファイルに対するパスワードは一般パスワードとして各ディスクイメージファイルのUUIDと対応して管理されています。このUUIDは

$ hdiutil isencrypted ディスクイメージファイル.dmg

というようにisencryptedオプションで知ることができます。キーチェインにパスワードを追加するには

$ security add-generic-password -a (上記のUUID) -D "disk image password" -s (ファイル名).dmg -w (パスワード)

というように実行すればよいでしょう。securityコマンドの大変残念な点はhdiutil-stdinpassのように標準入力からパスワードを受け取る手段がどうやらなさそうな点です。

コマンド引数として実行してしまうと、シェルの履歴に残ったり、psコマンドなどでパスワードが漏えいしてしまうケースがあります。このような点から上述のように「添付ファイルはパスワードつきzipでパスワードは別メールですぐ送信!」というセキュリティーレベルとたいして変わらないと思っています。

さて、上記を合わせて次のようなスクリプトにしてあります。

#!/bin/sh

if [ $# -lt 1 ]; then
  echo $0 [dmg file]...
  exit 1
fi

PWD=$HOME/.dmg-password

for FILE in "$@"; do
  UUID=$(hdiutil isencrypted "$FILE" 2>&1 | grep uuid | awk '{print $2}')
  BASE=$(basename $FILE)

  echo File: $BASE
  echo UUID: $UUID

  security add-generic-password -a $UUID -D "disk image password" -s $BASE -w $(cat $PWD)
done

このようにキーチェインにパスワードを設定したファイルを開こうとすると次のようにキーチェインからパスワードを取り出していいか確認されます。

確認ダイアログ

このダイアログをsecurityコマンドの-Aオプションでスキップする手段もありますがすべてのアプリケーションに対して許可を出してしまいます。少し面倒ですが、ここは一手間かけておいたほうがいいでしょう。

TumblrからJekyll + Github Pagesへ

3年ほど前にWordPressからTumblrへ移行しました。特に大きな問題があったわけではないのですが、最近ソースコードを挿入した記事をいくつか書こうとしたところTumblrのMarkdownではうまく表現され切れない点にすこしストレスを感じていました。表示をあれこれいじっている時間ももったいなくなってきたのでいっそのこと、GFM (GitHub Flavored Markdown)で書けるようなサービスか、システムに移行しようと考え始めました。

あれこれ調べているうちに、どうやらJekyllをつかってGithub Pagesへ移行するのがスムーズそうだなと調べ始めて評価し、次の点で問題なさそうだったので移行することにしました。

  • GFMで書ける
  • 既存のURL構成を引き継げる
  • Tumblrからの記事取り込みもできる
  • シンプルなテーマがある

逆に移行に当たってできなくなる機能もあります。たとえばソーシャルメディア連携です。Tumblrでホストしてもらえば、Tumblr上のソーシャルメディアでリブログなど拡散手段があります。ほかには予約投稿やTwitter連携といった機能も使えなくなります。このあたりはトレードオフですが、ソーシャルメディアで拡散していただくよりも、そもそも記事が書きづらくて筆無精になるのであれば優先順位は明らかです。

ソーシャルメディア連携もあとからIFTTTなど何か使えば実現できるでしょうし、予約投稿も必要になれば決まった時間にgit pushするよう何か構成を考えればいいだけです。

移行の効果

移行中のあれこれは後述することにしてまずは、移行によってどういったメリットがあったかみてみます。

WordPressから移行した際にはサイトの速度が劇的に改善しましたが、今回は比較的軽微な差に収まっています。

TumblrでのPageSpeed結果

まずはTumblrでのPageSpeed計測結果から。

Tumblr / モバイル

モバイル用のスコアは67点。画像サイズが大きいと警告がでているのと、キャッシュ設定などのところで最適化の余地があるとの判定です。

Tumblr / デスクトップ

デスクトップ用のスコアは77点。モバイルと同様に画像が大きいと警告が出ています。いずれにしても体感的にはもっさりした感覚は全くなく、表示速度などの不満は特にありませんでした。

Jekyll + Github PagesでPageSpeed結果

Jekyll + Github Pages / モバイル

モバイル用のスコアは72点と少しだけTumblrでの表示より改善しています。これはTumblrのインフラスピードとの差というよりは、採用しているテーマのCSSやJavaScriptの差がほとんどだと考えられます。

Jekyll + Github Pages / デスクトップ

デスクトップ用のスコアは77点とTumblrでの結果と同じです。

その他の違い

Tumblrではモバイル対応していないテーマを使っていたので、TumblrのUIが優先して表示されています。一方、Jekyllで今回利用したLanyonはモバイルフレンドリーなテーマで、今回どちらでも同じLook and Feelでサイトを表示できるようになりました。

移行

移行作業はJekyllの使い方を覚えるところから全部でおおよそ5〜6時間かかったかと思います。Jekyllはかなり活発に開発されているようなのであまりここで細々と手順を書いてもすぐ使いもにならなくなってしまう気がするのでおおまかな流れだけご紹介しておきます。

Jekyll環境の設定

最近はこの手のツールを使うときはすべてDockerを使って独立した環境で実行するようにしています。再現性もありますし、手元の環境もシンプルに保てます。Jekyllを使うには既存のDockerイメージを使えば充分ですが、いくつかテーマを試したりしているうちに依存関係のあるライブラリをまとめてイメージとして持っていたほうが便利だったので次のような内容のDockerfileを作っています。

FROM jekyll/jekyll:builder

RUN apk add --no-cache --virtual build-dependencies build-base
RUN apk add --no-cache libxml2-dev libxslt-dev
RUN apk add --no-cache ruby-dev curl-dev zlib-dev yaml-dev
RUN gem install nokogiri
RUN gem install minima
RUN gem install jekyll-import

jekyll-importは後述のTumblrから記事を取り込んだときに使ったものです。このDockerfileをビルドしておきます。たとえばsiteディレクトリ以下につくっていくならこんな感じにコンテナを実行して、テンプレートを作ったりjekyll-importを実行したりできます。

$ docker run --rm -i $(pwd)/site:/srv/jekyll -p 4000:4000 -t (ビルドしたときのタグ名) bash

今回はあれこれテーマをダウンロードし、jekyll-importでTumblrから記事をざっくり取り込んで表示確認をしながらレイアウトを決定しました。

Tumblrからのインポート

jekyll-importのTumblrについての記事にコマンドが書かれていますのでこれを実行します。 移行といっても今回はTumblrで書いた記事が12記事しかなかったので、ある程度の部分は詳しく調べずに手作業でファイルを修正していくというスタイルで解決しました。

今回実行した際は次のように実行しました。

ruby -rubygems -e 'require "jekyll-import";
    JekyllImport::Importers::Tumblr.run({
      "url"            => "http://(TumblrのID).tumblr.com",
      "format"         => "html",
      "grab_images"    => true,
      "add_highlights" => true,
      "rewrite_urls"   => true
    })'

最初formatはMarkdown(md)を指定していましたが、うまく記事が取りこめなかったのでhtmlで取り込み、あとで手動でMarkdownに変換しました。

Tumblrでは記事のURLが /post/(記事ID)/(記事タイトル)というフォーマットでしたが、Jekyllでは/(年)/(月)/(日)/(記事タイトル)となるので変換が必要です。rewrite_urlsで相当するアドレスにリダイレクトするファイルを作ってくれるので、リンクはそのまま維持できます。

ただ困ったことに生々されたリダイレクト用のindex.htmlでは/(年)-(月)-(日)-(記事タイトル)へのリダイレクトになっていてうまく動きません。あれこれ探すのも面倒だったのでここは手動で直してしまいました。

grab_imagesという設定があり画像をもってきてくれそうな印象がありましたが、これもうまく動かなかったので手作業で画像をもってきたり、手元に保存してあったものを使ったりして再構成しました。

テーマ選び

いくつかサンプルを見ながら選びましたがシンプルなものがよかったのでLanyonにしました。ほかにもシンプルなデザインのものはありましたが、Lanyonは欲しい機能がおおむね入っていることと、CSSやJavaScriptを含めても全体の構成がシンプルで全体が見渡しやすいことが気に入りました。

切り替え

できあがったJekyllのページ一式をGithub Pagesへプッシュし、あとはCloudFlareでDNSの切り替えをすれば終了です。

Jekyllも開発スピードが速そうなので、開発についていくとしたらかなり大変そうです。ただでき上がったサイトはかなりシンプルなHTML、CSS、JavaScriptと画像ファイルだけで構成されているのでセキュリティー上の理由などで更新しなければいけないケースはかなり少ないと思います。

そういった意味では、一度環境ができ上がってしまえばTumblrでプレビューにあれこれ悩みながら記事を書くよりも手元の環境で確実なプレビューを見てから公開できるワークフローをつくったほうがより生産性が高そうだということで今は満足しています。

画像を含めたオーサリングのワークフローをつくったり、プレビューから予定投稿などまでいろいろ作り込みたいところですがそのあたりはまた今度じっくり作り込んでみることにします。

Dropbox Business utilities written in Golang

今夏ごろからDropbox Business向けのツールをちょこちょこと開発しています。Dropbox Business APIは一般的なOAuth2を使った認証と、HTTPでのAPIのためたいていのプログラミング言語で簡単に扱えます。また、DropboxからPython、Ruby、Java、JavaScript、.NET、Objective-C、SwiftなどSDKもリリースされているのでこれらを使えばより簡単です。

ちなみにこれほど多くのSDKをメンテナンスするのはかなり手間ですが、それを解消するためにStoneというオープンソースが公開されています。StoneはAPI仕様記述からSDKを生成するツールで一度この仕様記述をメンテナンスしていけばSDKは自動生成できます。Dropbox APIの仕様についても同様にStoneでの仕様記述があり、公開されているSDKは自動生成されたものをベースにしています。

つくったツール

作成したのは下記二つのツールです。

  • dcfg … G Suiteのグループで管理されている組織、グループ構成をDropbox Businessチーム用に同期する
  • dreport … Dropbox Businessチームに関するメンバー一覧などをレポートとしてCSVで出力する

いずれもCLIツールでシステム管理者が利用する想定のものです。ただ、昨今はシステム管理といってもWindowsだけでなくMacなど様々なシステム環境があります。このように利用用途からかんがえて複数プラットホームで利用できたほうが便利なので、個人的には初めてとなるGolangを採用してみました。

はじめてのGolang

一番最近よく使っていたプログラミング言語はScalaだったので、カルチャーギャップというか、プログラミング言語がもつ信念のようなところに大きな差があって少し戸惑いました。

Scalaのようなオブジェクト指向的にも関数型的にもかけるプログラミング言語の直後ということもあって、Golangの書き方はややまどろっこしく感じます。ただこのまどろっこしさも次第に慣れていくことで、また考え方がわかってきたことでさほど気にならなくなってきました。

どこに書いてあったかソースは失念しましたがたとえば、「関数のなかで、計算量のオーダーが O(1) → O(n)のように変わるような書き方はあえてさせないことで、関数を使う側がどういうオーダーの計算量を常に意識させるようにしている」というような説明がありました。Golangのテストフレームワークがassertをあえて実装していない、といったこともそうですが、プログラミング言語やフレームワークで様々なヒューマンエラーを減らしていこうとする考え方とはまた違う思想を感じます。

この考え方を真に受けてコードを書いていったので、でき上がったプログラムはにたようなforループやif分岐がたくさんでき上がりました。今回、コードをかくに当たってあまり他の方のコードを読んだりしなかったので本流とはだいぶ外れているかもしれません。

パッケージ管理

Golangで戸惑ったのがパッケージ管理です。GoのプログラムはGOPATH環境変数で指定したディレクトリ以下にすべての依存ファイル、そして自分のプロジェクトファイルも置いていくという方式です。

自分のプロジェクトをたとえば、github.com/watermint/dcfg で公開する予定ならば下図のように GOPATH以下に置いていきます。外部ライブラリたとえば、github.com/dropbox/dropbox-sdk-go-unofficialを使うのであればこれらもgo getコマンドを経由して同様にGOPATH以下に配置します。

$GOPATH / src / github.com / watermint / dcfg

ひょっとすると、Goの考え方でAPIは一度公開したならば同じ名前で同じように使えるべき。という信念があるのかもしれません。 この方式で困るのは複数プロジェクトを進行しているときに、異なるバージョンのライブラリを利用したいや、ライブラリのバージョンをしばらく固定しておきたいときにはやっかいです。

また別の環境でビルドする際にも依存するライブラリの管理が煩雑になります。こういった問題に対しRubyであればgem、Javaなどではmaven、gradle、sbtなどパッケージ管理は様々あり、これらで管理するのが当たり前という風なイメージを持っています。Golangでも同じようなものを探してみましたが、いまひとつこれといったものがどれなのかわかりませんでした。

これも今回はあまり細かく調べずに、glideというツールを利用しました。glideをつかうと、依存するライブラリ郡を自分のプロジェクトフォルダ以下のvendorというフォルダにまとめてくれます。バージョン指定も可能ですし、一括してライブラリ郡をダウンロードするなど細々面倒を見てくれます。

また今回つかったdropbox-go-sdk-unofficialは名前の通りアンオフィシャルSDKで、まだ活発に大規模なリファクタリングがおこなわれていてバージョンを固定しないとまともに開発できない。。という現状もありglideによるパッケージ管理はもっとはやく導入しておけばと悔やまれました。

おそらく他のツールでも同様によしなに準備してくれるのだと思います。

ビルド

本格的にコーディングを始める前に、ビルド環境だけは念入りに準備することにしました。ビルド環境のOSのバージョンが違うことで何か問題がでたりするとかなりの時間を浪費してしまいますし、いろんなところで新しくビルドしようとおもったときにも手間がかかります。

今回はご多分に漏れずDockerを使っています。Dockerfileを用意しておいて、Dockerコンテナ上でビルドをしてそのバイナリを手元環境でテストするというワークフローを確立しました。

ついでにTravis CIでもテストが自動的に実行されるようにしておきました。テストカバレッジなども集計するという流れを自動化しておくこともできますが、これもなかなか情報が分散していて地味に手間のかかる作業でした。

これから環境を用意されようと思われる方は下記の.travis.ymlをご参考にしていただければと思います。

language: go
go:
 - 1.7
 - tip

before_install:
 - go get golang.org/x/tools/cmd/cover
 - go get github.com/modocache/gover
 - go get github.com/mattn/goveralls
 - go get github.com/Masterminds/glide

install:
 - glide install

script:
 - go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(glide novendor) | xargs -L 1 sh -c
 - gover

after_success:
 - goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN

ここで登場するcover、gover、goverallsはカバレッジを取得、集約するためのものです。よく見るとgoverは前職の同僚が作ったものですね。こんなところでお目にかかるとは誇らしいものです。

ところでscript:のセクションはややトリッキーな書き方をしています。これもあれこれ情報を集めながら試行錯誤したものでこれでいいのか微妙なところですがひとまず動作しているのでこれで。。

- go list -f '{{if len .TestGoFiles}}"go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}"{{end}}' $(glide novendor) | xargs -L 1 sh -c

go testというのでカバレッジがとれますが残念ながらgo testでは1つのソースファイルについてのみテストとカバレッジ測定となってプロジェクト全体ではとれません。そこで、ソースコードひとつずつについてgo testを実行してカバレッジデータのファイルを個別につくってあとで結合するという力技です。

Retrospective

今回はCLIツールを作りましたが、利用シーンを考えるとGUIツールもぜひ作ってみたいところです。 GolangでのGUIライブラリはまだこれといった決定打がないようで、ひょっとしたらJavaでつくるか、SwiftをつかってiOSアプリにしてしまうほうがいいのかもしれません。

今回CLIとは別にGinというライブラリを使ってWebサーバを立ち上げるパターンも試作してみました。Play framework等と比べればまだまだツールサポートなども弱いので生産性に課題を感じましたがしっかり作り込めばGUIでつくるよりも将来性がありそうです。

このあたりはまた次回の研究課題です。

10万枚を超える写真データの整理とストレージ選び

ハードディスク

最初にデジタル一眼レフを買ったのがちょうど10年前。カメラも、ハードディスクも順調に増え場所も手間もかかるようになってしまったので写真の整理法から、ストレージの使い方まで半年がかりでやり方をかえていきました。

事前準備

最初はすこし軽い気持ちで始めましたが、結果的にはかなり大掛かりな作業になってしましました。

  • データ容量、内容の確認
  • ファイル形式の統一
  • 重複の排除
  • ファイルの分類
  • 新しいストレージへの移行 最初から最終形態を設計/想定していた訳ではありませんが、随時微調整しながら進めています。

整理を始める前のデータと容量

ハードディスクを整理してみると、ハードディスクはNASに搭載しているものもあわせると15本、NASは2台、総容量は30TBと一般家庭にしてはかなりの容量です。それぞれのディスクのおおよそ6〜7割は使用中で単純計算で18TBものデータが存在していました。

ただそのほとんどはバックアップ用途の重複保存です。同じデータを最低2本以上のディスクに保存することにしていたので、単純計算でデータ容量は9TB程度です。 またさらに、写真管理ソフトの形式によって重複している部分もありました。写真の管理は2001年ごろからはiPhoto、2007年ごろからはAperture、2013年からはCapture Oneを使っていました。2013年は少しだけLightroomも使っていたのでこのデータもありました。

Apertureのサポート終了に伴う移行では最終的に、Capture Oneに落ち着きましたが慣れの問題もあり最終的にCapture Oneに一本化できたのは2014年後半でした。この間、データはAperture形式とCapture One形式の二通りのライブラリとして管理していました。

ここまでの見積もりメタデータやサムネイルファイル、重複などををおおまかに調査したところ最終的なファイルサイズはおおよそ3〜4TB程度になると見積もりました。

整理を始める前の写真

もう一つの問題点は、簡単には狙った写真が見つけられないということです。連射した写真や似たような構図の写真が大量にあると、毎回無視できないほど時間がかかります。多いときには1日に1,000枚以上撮影していることもあり写真を見直すことがおっくうになってきますが、そのように大量に撮影しているときにいい写真があるのでまたもどかしい気持ちになります。

たとえば3年前の旅行で撮った写真を加工して印刷したい、といったときに該当のRAWデータを見つけるのは結構な手間がかかりました。1週間の旅行で5,000〜7,000枚程度の枚数にはなるので、ApertureやCapture Oneのライブラリを開くだけでもしっかりと時間がかかります。

撮影当時にApertureやCapture Oneで調整した結果からさらに編集したいとき、目的のApertureライブラリやCapture Oneセッションを探し出すというのはかなりの労力がかかりました。仕方なく、現像済みJPEGから再調整するということもありましたがせっかくRAWで保存してあるのに活用できないのはもったいない気がします。

このように整理を始める前はデータも、写真自体も管理がばらばらで再利用するのがとにかく面倒で、一念発起しないとできない。という状況でした。

目標と方針を決める

写真を撮るのは楽しいですが、整理するのは面倒だと感じています。写真は趣味で、ビジネスとして撮っているわけではないので、整理に割ける時間も限られます。趣味の範囲という前提に立つと「写真を撮るのも、後から見ることも楽しい」ぐらいが目標となり、このための雑務を減らし、秩序をつくっていくことがアクションとして考えられます。

この目標実現のために最初に決めた方針は次のようなことでした。

  • 資産として再利用できるようにする。
  • フラットな構造に管理する。たとえば、複数のディスクに分散しないようにする。
  • すでに機械化されている、または将来機械化されそうな作業をしなくて良いようにする。

NASからの移行

まずは重複を排除しつつ、データを一ヶ所にまとめることにしました。3年まえからほとんどのデータは、NASのDrobo 5N (実効容量9TB)にまとめていました。このNAS上にすべてのディスクにあるデータをまとめ、整理していくのが順当ではあったのですが一つ問題がありました。

Mac OS XはNASへのアクセス性能がきわめて悪いことです。データ転送などある程度まとまった処理はさほど悪くないのですが、ファイル一覧などファイルを探す処理が極端に遅いのです。 Finderでファイル一覧を開くだけで数十秒〜数分かかることもあり作業に大きな支障がありました。通常、NASをMac OS Xから参照するとAFP (Apple File Protocol)で接続されます。これを、SMB2というプロトコルに変更すると若干改善するように見えるのですが、それでも体感速度はさほど変わりませんでした。Drobo 5NではmSSDを使ったキャッシュを搭載することができますが、このキャッシュを搭載してみてもさほど体感上変わりはありませんでした。

かわりに、Mac OS X上のVirtualBoxにUbuntu Trustyをインストールし、SMBクライアントでマウントしてみると見違えるように高速に動作し原因はMac OS Xの問題とほぼ限定されました。実際に、Mac OS Xからrsyncなどのコマンドでファイル転送してもシステムコールレベルでNAS側に制御ファイルを作成したり削除しているような振るまいが見られこのオペレーションが低速につながっていると想像できました。

作業をUbuntu上で行うことも選択肢にはありましたが、Capture OneやApertureなどMac OS X上のアプリケーションも利用する必要があることから、Drobo 5Nに集約することはあきらめて6TBのハードディスクを新たに購入しここにすべてのデータを集約することにしました。

Drobo 5Nの純粋な性能と比べても、ネットワーク越しのアクセスとUSB 3.0の直接接続では大きくスループットも違いがでます。

データのコピーとデータ形式の統一

データのコピーは基本的にすべてrsyncコマンドを使いました。信頼性があるのと、中断せざるを得ないときにも停止して安全に再開することもできます。Mac OS Xの性能問題もあり、コピーは基本的にUbuntu上で行いました。それでもDrobo 5Nからの転送はおおよそ1〜2週間程度かかったと思います。

最初にApertureライブラリを転送しました。Capture OneにApetureライブラリをインポートする機能もあるので、こういった機能も使いつつJPEG、TIFF、RAWファイルなどの元データとメタデータをApertureに依存しない形式に変換していきました。この作業も1〜2週間かかったと思います。

Capture OneはEIPというファイル形式で出力することができます。EIPはRAWデータ、メタデータ、調整データをすべてひとつのファイルにまとめたものです。EIPファイルさえあれば、調整データもメタデータも含め写真1枚ずつ開いて加工することができます。EIPファイルの実態はZIPファイルで、中にはRAWデータや調整データを含むファイルが格納されています。

Apertureのライブラリ、Lightroomのライブラリ、Capture Oneのライブラリはそれぞれライブラリとして一つ大きなパッケージとして管理するので操作が高速であったり、アルバム管理など便利ですがEIPファイルのように1枚一枚別々に管理できるとApertureやCapture Oneなど専用ソフトでなくても操作できるので、後述の重複排除など操作では重宝します。

このように、Capture OneのデータはEIP、ApertureのデータはRAW + XMPファイルとしてエクスポートして一ヶ所にまとめることができました。

重複排除

重複排除にはGeminiというアプリケーションを利用しました。ファイル数が多すぎるとUI操作がもたつくこともありますが、操作がわかりやすいのとファイルをプレビューしながら操作できる点が大きなメリットです。

Geminiでおおまかに重複排除したあとは自作のスクリプトで重複排除を進めました。RAWから現像したファイルはいったん削除することにしたので、EXIFデータを見ながら日時、カメラなどのデータが一致するJPEGファイルを削除。他には、前述のEIPファイルではZIPファイル中にRAWファイルがあるため、Gemini等の重複排除では期待通り重複を排除できないためEIPファイル中のRAWファイルデータを読み出して比較しながら重複排除するスクリプトなどを作成しました。

99%以上のファイルは有効なEXIFデータが入っているのですが、かなり古いデジカメで撮影した写真などデータが含まれていなかったり、EXIFなどのデータが破損している場合があったのでこういったファイルはそれぞれ目視確認しつつ作業を進めました。

このような作業を繰り返し重複排除が終わったときには13万枚程度の写真ファイルが整然と並びようやく全貌が明らかになりました。

分類する

写真をおおまかに2つのグループにわけることにしました。1つは、何度も見返したい写真。もうひとつはそれ以外の写真のグループです。

何度も見返したい写真は比較的しっかりバックアップがとられるように管理したい写真で、それ以外の写真は最悪なくなっても気にしないといったポリシーで管理することにしました。

グループ分けの考え方

「すでに機械化されている、または将来機械化されそうな作業をしなくて良いようにする。」という方針から、写真のレーティングで機械化されそうなところは機械にたよることにしました。

たとえば写真のなかの顔認識についてはかなり技術的に進んでいて、特定の人が写っている写真を撮り出すということは今日たやすい操作です。この画像認識技術はまだ多少精度の問題もあります。生物や植物の図鑑をつくるような目的である程度厳密に分類をするといった用途には向きません。今回の目的は必要な写真をすぐ見つけたい。というぐらいですから、多少違う写真がピックアップされたところであまり影響ありません。

Siriのようなアシスタントに「AさんとBさんが写っている去年の集合写真をさがして」とか「Cさんが写っている沖縄の海辺の写真を探して」といったら数枚がピックアップされ、ピックアップされた数枚から「ピンボケしていないものだけ」といった絞り込みもそう遠くない未来に実現するでしょう。

すでにGoogle Photosやflickrでも機械学習などの成果から「建築物」とか「食べ物」と自動的に分類したり、GPSデータがなくても建造物などから場所を推測してタグ付けするといったサービスが提供されていますのでこういった分類は手作業で時間をかける必要はありません。

今回の写真整理では利用しませんでしたが顔と名前、人のつながり、時間や場所とイベントといった付加情報のデータベースについていえばfacebookが圧倒的に大きなデータベースを持っているので、facebookが面白い提案をしてくるかもしれません。

一方すぐに機械化できないと思うのは個人的な体験による重要度の重みづけです。何の変哲もない風景写真だが自分には印象深いとか、ピンボケしているが人生の大きなターニングポイントのタイミングを象徴するとかデータとして記録していないし、定型的な表現の難しい体験による重要度はいかに機械化による分類はやや難しいでしょう。facebookやtwitterなどに記録されたイベント、発言など複合的に機械学習することでおもしろい写真の組み合わせを提案する。といったことはできたとしても、自分の印象に残ったあの写真を探す。というのとは少し方向性がちがうと思うからです。

レーティング

こう考えると機械化できるところは機械に任せるとして、印象深いシーンのみをピックアップして分類していくということにしました。ピックアップする際にはApertureやCapture Oneでは★1〜5のようにレーティングをつけることができますが、これを利用しています。

過去にもレーティングをつけていましたが統一した基準をもっていませんでした。このため時期によってほぼ★1〜3しかつかっていないとき、★3を平均として1〜5の範囲で使っているときとばらつきが生まれていました。前述の通り、個人的な体験による重要度を尺度とするので時間の経過とともに重要度が下がっているものもあれば、重要度が上がるものもあります。

たとえば、スキューバダイビングをしていて最初のころは何でもすべて珍しい魚、地形と感じ、重要と位置づけていますが、次第に慣れてくると、同じ魚/地形でも天候や構図などが優れた場合が重要であるというように変化することもあります。

レーティングは新しく★3を平均点として、★4を優秀、★5は最優秀としてつけることにしました。厳密な定義はありませんが、★5は印刷して壁に飾っておきたいような写真、★4はパソコンやスマートフォンのデスクトップ画像にしたいような写真といった感覚で点数をつけています。

★2は★3相当だが構図が似ているのでボツ、★1はピンボケや構図がかなりわるいといった使い分けです。全部の写真にレーティングをつけると面倒なので、★3以上だなとおもうときだけレーティングをつけ、同じシーンで似たような構図が多いなとおもったら★2に下げるといった調整をしています。

枚数の比率としては★1〜★2相当の枚数くらべ★3以上の枚数が1/10以下になるよう目標として、全部で13万枚ありましたので★3以上は1万枚以下を目標として調整しました。この作業が最も時間がかかり2ヶ月以上かかりましたが結果として★3はおおよそ6,000枚、★4はおおよそ700枚、★5は60枚と目標通りの枚数に調整できました。

10数年分で7,000枚、デジタル一眼レフを使い始めた近年でも1年当たり1,000枚前後となったので見通しはかなりよくなりました。

最終的に容量にして★3以上の写真は170GB、★なし〜2の写真は2TB弱とかなりコンパクト化することができました。

ストレージ

写真の保存としてこれまでRAWデータは自宅のNASやUSB接続のハードディスクに、現像済みのJPEGデータはflickrに保管していました。

今回はRAWデータも含めてクラウドストレージ上に管理することにしました。使ったのは次のストレージです。

  • Amazon Drive : Amazonプライム会員になると写真データは無制限に保存することができるのでCaptureとした★2以下の写真を置きます。無制限となるのはJPEGだけでなく、NEF、ARWなどRAWファイルも対象となりますが、EIP形式などは対象外なのでEIPではなくもとのNEF/ARWなどのRAWファイルに変換しておきます。
  • Dropbox : EIP形式にしたRAWファイルと調整済みの現像JPEGデータを保存しておきます。★3以上の写真のみ置きます。Dropboxはバージョン履歴をとれるので、編集履歴も自動的に保存されるのが利点です。
  • flickr : flickrにはほぼすべての現像データがすべて置いてあります。flickrは使い始めてちょうど10年ほどになりますが、容量無制限のころのProアカウントを利用しているので気兼ねなくアップロードすることができます。flickrも機械学習による自動分類ができるので発見目的に利用しています。
  • Google Photos : オリジナル解像度ではなく、Googleの設定した解像度以下であれば無制限に置くことができます。機械学習により自動分類されるのでここにも★2以下の写真を置きます。 多くのクラウドストレージは1TB程度以内までと容量制限がありますが、今回★3以上と★なし〜★2のように分類したことで★3以上の部分(約170GB分)については選択肢が大きく広がりました。Dropboxのようなクラウドストレージを使えばバックアップも気にすることなく自動的にとられているので大量のハードディスクやNASを管理する必要がなくなりました。

新しいワークフロー

こういった整理を進めながらも写真は日々ふえていきましたが、最終的な保存場所がきまったことでワークフローが整いました。写真を撮影したら、新しいCapture Oneのセッションを作成します。このCapture OneのセッションファイルはDropbox上に置き、すべてのファイルがDropbox上にバックアップされるようにします。

次にレーティングをつけて、★3以上の写真については調整と現像を行いEIP形式への変換などをしたうえで所定のディレクトリにエクスポートします。現像データはflickrにアップロードします。 ★なし〜★2の写真についてはRAWデータを所定の場所におきAmazon DriveとGoogle Photosにアップロードされるようにします。

ここまでの作業が完了したら該当のCapture Oneセッションは削除します。Dropbox上に削除済みファイルも保存されているので必要とあらば再度取り出せますから、心置きなく削除してしまいます。

整理をおえて

今回の整理をおえて当初もくろんだ通り、再利用については非常にやりやすくなりました。2012年の旅行写真をとりだして編集するといった基本的な操作がストレス無くできるようになりました。Google PhotosをつかってAさんが写っている写真、といったような写真のとりだしかたもできるようになりました。

まだ新しいワークフローにしてまもないこともあり、AmazonやGoogleへのアップロード作業など一部自動化できていない部分もありますが最初からDropboxに保存することでクラウド上で処理できることも多くなりました。

Dropboxに追加されたら自動的にデフォルトのレシピで現像して、Amazon、Google、flickrにアップロードするとか、レーティング情報が追加されたら長期保存対象として所定のディレクトリに置くといった仕組みを作ることもできると思います。