ことのはじまり
いままで家ではiMacを基幹マシンに据えていたが、いろいろあってMacbookProに一本化。
iMacには嫁が撮った写真が100GBほど。しかしMacbookProのSSDは256GB(しくった)。
iMacは場所取るので置いておけないし、しれっと写真を削除するのはいろんな意味でリスキーなので、外部ストレージに放り込むことに。
MacOS Xの写真ライブラリ
まず、写真たちはそもそもどこにいるのかというと、特に何もしていなければ
ホームディレクトリ
> ピクチャ
にいます。
この写真ライブラリ.photoslibrary
が写真たちになるのだが、これはFinder
上で見るとフォルダではなく、ダブルクリックすると写真アプリ
が起動してしまいます。
この中身を見るためには、右クリック
> パッケージの内容を表示
すると見れます。
(ターミナルなら普通にcd
で入れる)
なにやらたくさんのフォルダがあります。まあサムネやら検索DBやら、写真アプリを快適に利用するための様々なデータが保存されているようです。
で、肝心の実データたちは
Masters
> 年
> 月
> 日
> 年月日-何かのインデックス
> xxx.PNG
という階層になっています。
階層が深すぎるので、写真アプリではなんの問題もなく写真を一覧で閲覧できていましたが、直接となるとそれがかなり面倒なことになってしまいます。Mastersフォルダ以外に用はないのでストレージの節約もしたいですしね。
やりたいこと
ということで、前置きがかなり長くなってしまいましたが、今回やりたいことはこちらです。
年
>月
> xxx.PNG の階層に短縮して、写真アプリを経由せずとも写真たちを閲覧したい- (できれば)Googleフォトにも放り込んで、クラウド経由でも見たい
Pythonで簡単なPG書いた
やることは簡単です。余計なファイル(隠しファイルとか)を避けつつ希望に沿ったフォルダ構成でガンガンコピる。これだけ。
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
強いて挙げるとすれば、ポイントは2つ。
①正規表現
超簡単なやつですが、正規表現を使うことで不要なディレクトリを読まないようにしています。
^20[0-9]{2}$
: 2009, 2018など、西暦のフォルダ名のみをターゲットにする。90年代は知りません。
^[^.].*
: .
からはじまる隠しファイルは除く。.DS_Store
とか。
②lambda式
Pythonはループ処理やif文が入り交じるとどうしても階層が深くなりがちです。
今回、lambda式を使ったことで3,4階層は浅くできたと思います。
list( \ ..a
filter( \ ..b
lambda yearDir: re.match("^20[0-9]{2}$", yearDir) <= int(yearDir), os.listdir(rootPath) \ ..c
)):
os.listdir()
を元の集合(リスト)として、c
にマッチする要素だけにフィルタリング(b
)し、部分集合(リスト)として返す(a
)。
実行結果
まとめ
- 最近仕事はJavaばっかでPython久しぶりすぎたので、なんかいろいろ勉強しなおすいい機会になった
- JavaのStreamプログラミングに慣れているせいか、Pythonのlambda式のバリエーションの無さになんか腹立つ。知らないだけなのであれば、そうであってほしい
- Python階層深すぎ
- でもやっぱりちょろっとしたこと書くにはPython便利。ちゃんと勉強しよ
参考
[Python]ファイル/ディレクトリ操作
https://qiita.com/supersaiakujin/items/12451cd2b8315fe7d054
【Python入門】lambda(ラムダ式)の使い方
https://www.sejuku.net/blog/23677