1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

ことのはじまり

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

MacOS Xの写真ライブラリ

まず、写真たちはそもそもどこにいるのかというと、特に何もしていなければ
ホームディレクトリ > ピクチャ にいます。
この写真ライブラリ.photoslibraryが写真たちになるのだが、これはFinder上で見るとフォルダではなく、ダブルクリックすると写真アプリが起動してしまいます。
この中身を見るためには、右クリック > パッケージの内容を表示すると見れます。
(ターミナルなら普通にcdで入れる)
スクリーンショット 2018-08-28 21.52.30.png
なにやらたくさんのフォルダがあります。まあサムネやら検索DBやら、写真アプリを快適に利用するための様々なデータが保存されているようです。

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

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

やりたいこと

ということで、前置きがかなり長くなってしまいましたが、今回やりたいことはこちらです。

  • > > xxx.PNG の階層に短縮して、写真アプリを経由せずとも写真たちを閲覧したい
  • (できれば)Googleフォトにも放り込んで、クラウド経由でも見たい

Pythonで簡単なPG書いた

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

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

強いて挙げるとすれば、ポイントは2つ。

①正規表現

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

②lambda式

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

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)。

実行結果

おぉー。期待通り。
スクリーンショット 2018-09-01 0.10.00.png

まとめ

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

参考

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

1
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?