豪鬼メモ

一瞬千撃

写真をレンズ毎にフォルダ分けするスクリプト

魚眼レンズBCL-0980は現像時に倍率色収差の補正をかけると調子がいいという話を以前に書いたが、ちょっと手間がかかる。SDカード内の全ての写真がBCL-0980で撮ったものならば、「select all」して「export」して「batch processing file」を選択するだけで一括処理できるが、問題は、複数レンズを切り替えて使った場合である。魚眼レンズだけつけて出かけるということは少ないので、多くの場合にこれがあてはまる。魚眼レンズで撮った写真には補正用の「batch processing file」を指定して現像するが、それ以外のものではデフォルトのパラメータで現像したい。しかし、それらを自動分類する方法がないのだ。写真の並び替えで「レンズ毎」という項目があればいいのだが、ない。よって、一枚ずつ写真の構図やメタデータを見て、どれとどれには補正が必要なのかを判断する手間がかかる。何百枚も撮った日には拷問のようである。
f:id:fridaynight:20151129134353j:plain
f:id:fridaynight:20151129143516j:plain


そこで、その作業を自動化するスクリプトを書いてみた。一つ以上の写真のファイルを指定すると、レンズ名がついたサブフォルダにコピーして保存してくれる。RubyとexiftoolがインストールしてあるMacLinuxで動くはず。

#! /usr/bin/ruby
# -*- coding: utf-8 -*-

require 'fileutils'

C_exif_cmd = 'exiftool'
C_out_dir = '.'

# main routine
def main()
  Encoding.default_external = 'UTF-8'
  input_paths = []
  out_dir = C_out_dir
  i = 0
  while i < ARGV.size
    arg = ARGV[i]
    if arg.start_with?('-')
      case arg
      when '-outdir'
        i += 1
        out_dir = ARGV[i]
      else
        raise("unknown option: #{arg}")
      end
    else
      input_paths.push(arg)
    end
    i += 1
  end
  print("Exporting #{input_paths.size} files.\n")
  print("\n")
  STDOUT.flush
  if not Dir::exist?(out_dir)
    Dir::mkdir(out_dir)
  end
  input_paths.each { |input_path|
    if not File::exist?(input_path)
      print("#{input_path} is missing\n")
    end
    lens = check_lens(input_path)
    lens_dir = "#{out_dir}/#{lens}"
    if not Dir::exist?(lens_dir)
      Dir::mkdir(lens_dir)
    end
    input_name = input_path.gsub(/.*\//, '')
    lens_path = "#{lens_dir}/#{input_name}"
    print("Copying #{input_path} as #{lens_path}\n")
    FileUtils::cp(input_path, lens_path)
  }
  print("\n")
  print("Done\n")
  STDOUT.flush
  0
end

# return the lens info
def check_lens(input_path)
  cmd = "#{C_exif_cmd}"
  cmd += " -t"
  cmd += " -s"
  cmd += " \"#{input_path}\""
  meta = {}
  result = `#{cmd} 2>/dev/null`
  result.force_encoding('ASCII-8BIT')
  lines = result.split("\n", -1)
  lines.each { |line|
    begin
      line = line.encode('UTF-8')
    rescue
      next
    end
    fields = line.chomp.split("\t", 2)
    next if fields.size < 2
    key = fields[0].strip
    value = fields[1].gsub(/\s+/, ' ').strip
    next if value.empty?
    meta[key] = value
  }
  lens_info = get_meta_value(meta, 'LensType') ||
    get_meta_value(meta, 'LensModel') || 
    get_meta_value(meta, 'LensInfo') || 
    get_meta_value(meta, 'LensID') || 'None'
  lens_info = 'unknown' if lens_info == nil or lens_info == 'None'
  lens_info = lens_info.gsub(/[\s*$]/, '_')
  lens_info = lens_info.downcase
  lens_info
end

# get the value of a specified key
def get_meta_value(meta, key)
  value = meta[key]
  return nil if value == nil or value.empty?
  value
end

# execute the main routine
exit(main())

上記をclassifyphotoという名前で保存して実行パーミッションをつけてコマンドサーチパスのどこかに置いたなら、ファイル名を引数に指定してそのコマンドを実行する。

$ classifyphoto *.ORF

すると、カレントディレクトリの下に、olympus_m.zuiko_17mmとかolympus_m.zuiko_25mmとかいうレンズ名のフォルダができる。BCL-0980の場合にはレンズ情報がEXIFから取れないので、unknownというフォルダになる。あとは、フォルダ毎にOlympus Viewerやその他の現像ソフトにインポートして、レンズ毎の設定を施しつつ一括現像すればよい。exiftoolが対応しているファイルフォーマットは全て分類可能なので、DNGやARWやCR2やNEFなど各社のRAWファイルも扱えるし、JPEGTIFFなどの現像済みファイルも扱える。

これで、気兼ねなくBCL-0980を使える環境が整った。魚眼面白写真ばかり見ると脳が疲れるので、20枚の中に1枚くらい混ぜる感じがいいのかもしれん。その21枚のキープ写真を得るために100枚くらい撮影したりするので、やっぱ自動化は大事だ。
f:id:fridaynight:20151129115724j:plain
f:id:fridaynight:20151129125711j:plain
f:id:fridaynight:20151129152036j:plain

M.Zuiko 25mm F1.8によるおまけ。普通のパースペクティブの写真はほっとする。下の子が補助輪なし自転車練習を始めた。まだ乗りこなせていないが、ちょっとだいぶバランスは取れてきているので、集中的に練習すればいけるかな。下の子にかかりっきりになると上の子が邪魔してくるので、下の子だけを連れていく日を作らねばなるまい。
f:id:fridaynight:20151129155352j:plain
f:id:fridaynight:20151129121303j:plain
f:id:fridaynight:20151129113905j:plain