macOS X の写真ライブラリの実データたちを抽出してみた

## ことのはじまり
いままで家ではiMacを基幹マシンに据えていたが、いろいろあってMacbookProに一本化。
iMacには嫁が撮った写真が100GBほど。しかしMacbookProのSSDは256GB(しくった)。
iMacは場所取るので置いておけないし、しれっと写真を削除するのはいろんな意味でリスキーなので、外部ストレージに放り込むことに。


## MacOS Xの写真ライブラリ
まず、写真たちはそもそもどこにいるのかというと、特に何もしていなければ
`ホームディレクトリ` > `ピクチャ` にいます。
この`写真ライブラリ.photoslibrary`が写真たちになるのだが、これは`Finder`上で見るとフォルダではなく、ダブルクリックすると`写真アプリ`が起動してしまいます。
この中身を見るためには、`右クリック` > `パッケージの内容を表示`すると見れます。
(ターミナルなら普通に`cd`で入れる)
<img width="367" alt="スクリーンショット 2018-08-28 21.52.30.png" src="https://qiita-image-store.s3.amazonaws.com/0/143089/9815c09c-384f-627e-feab-058df481986f.png">
なにやらたくさんのフォルダがあります。まあサムネやら検索DBやら、写真アプリを快適に利用するための様々なデータが保存されているようです。

で、肝心の実データたちは
`Masters` > `年` > `月` > `日` > `年月日-何かのインデックス` > xxx.PNG
という階層になっています。

階層が深すぎるので、写真アプリではなんの問題もなく写真を一覧で閲覧できていましたが、直接となるとそれがかなり面倒なことになってしまいます。Mastersフォルダ以外に用はないのでストレージの節約もしたいですしね。

## やりたいこと
ということで、前置きがかなり長くなってしまいましたが、今回やりたいことはこちらです。
- **`年` > `月` > xxx.PNG の階層に短縮して、写真アプリを経由せずとも写真たちを閲覧したい**
- **(できれば)Googleフォトにも放り込んで、クラウド経由でも見たい**


## Pythonで簡単なPG書いた
やることは簡単です。余計なファイル(隠しファイルとか)を避けつつ希望に沿ったフォルダ構成でガンガンコピる。これだけ。

```py:extract.py
import os, re, shutil
from os.path import join

def extract():
  rootPath = "path to source images(Masterフォルダ)"
  copyto = "path to destination(コピー先フォルダ)"

  if not os.path.isdir(copyto):
      os.makedirs(copyto)

  cnt = 0

  # year
  for year in list( \
              filter( \
                  lambda yearDir: re.match("^20[0-9]{2}$", yearDir) <= int(yearDir), os.listdir(rootPath) \
              )):
      # month
      for month in list( \
                    filter( \
                      lambda monthDir: re.match("^[0-9]{2}$", monthDir) <= int(monthDir), os.listdir(join(rootPath, year)) \
                    )):
          for root, dirs, files in os.walk(join(rootPath, year, month)):
              if len(files) != 0:
                  for file in list( \
                              filter( \
                                  lambda file: re.match("^[^.].*", file), files \
                              )):
                      print("copy", join(root,file), "to", join(copyto, year, month, file))
                      if not os.path.isdir(join(copyto, year, month)):
                          # ディレクトリなければ作る
                          os.makedirs(join(copyto, year, month))
                      shutil.copy2(join(root,file), join(copyto, year, month, file))
                      cnt += 1
  else:
      print("copied", cnt, "files")

if __name__ == "__main__":
    extract()
```
プログラムの完全版はこちら：
https://github.com/vira0223/extract_files

強いて挙げるとすれば、ポイントは２つ。
#### ①正規表現
超簡単なやつですが、正規表現を使うことで不要なディレクトリを読まないようにしています。
`^20[0-9]{2}$`: 2009, 2018など、西暦のフォルダ名のみをターゲットにする。90年代は知りません。
`^[^.].*`: `.`からはじまる隠しファイルは除く。`.DS_Store`とか。

#### ②lambda式
Pythonはループ処理やif文が入り交じるとどうしても階層が深くなりがちです。
今回、lambda式を使ったことで3,4階層は浅くできたと思います。

```py:lambda
list( \    ..a
  filter( \    ..b
  lambda yearDir: re.match("^20[0-9]{2}$", yearDir) <= int(yearDir), os.listdir(rootPath) \    ..c
)):
```
`os.listdir()`を元の集合(リスト)として、`c`にマッチする要素だけにフィルタリング(`b`)し、部分集合(リスト)として返す(`a`)。

## 実行結果
おぉー。期待通り。
<img width="563" alt="スクリーンショット 2018-09-01 0.10.00.png" src="https://qiita-image-store.s3.amazonaws.com/0/143089/ffca2ba5-281e-29a4-a7b5-99d90144dd34.png">


## まとめ
- 最近仕事はJavaばっかでPython久しぶりすぎたので、なんかいろいろ勉強しなおすいい機会になった
- JavaのStreamプログラミングに慣れているせいか、Pythonのlambda式のバリエーションの無さになんか腹立つ。知らないだけなのであれば、そうであってほしい
- Python階層深すぎ
- でもやっぱりちょろっとしたこと書くにはPython便利。ちゃんと勉強しよ

## 参考
[Python]ファイル/ディレクトリ操作
https://qiita.com/supersaiakujin/items/12451cd2b8315fe7d054
【Python入門】lambda(ラムダ式)の使い方
https://www.sejuku.net/blog/23677