RAWやJPGファイルを撮影日時に応じたフォルダに移動するRubyスクリプト

なんだかんだで8年近くカメラをやっている私ですが、昔と今で異なる現像ソフトを使っています。そして取り込み時のフォルダ名のルールを変えてしまったため、フォルダ名が滅茶苦茶になってきました。具体的には、Nikon ViewNX で取り込んでいた頃は3桁の連番（`001/, 002/, ...`）に保存していたのですが、Adobe Lightroom を使うようになってからは YYYYMMDD (`20160101/, 20160102/, ...`) という規則で取り込んでいます。

いい加減気持ち悪くなってきたので、日付毎に写真をフォルダ分けする Ruby スクリプトを書きました。これを使うと、写真の Exif 情報から日時を取得し、その日時に対応したフォルダに写真を移動できます。例えば2016年1月21日に撮影された写真なら `20160121` というフォルダに移動します。

以下、スクリプトと使い方です。


検証環境
----
* Windows 10 64bit (Build 10240)
* ruby 2.2.3p173 (2015-08-18 revision 51636)


導入手順
----
1. 何はともあれ Ruby を実行できるようにしておく。
    * Ruby 実行環境がない人は以下の記事を参考に作ってください。
      * [Rubyダウンロード及びインストール - Rubyインストール](http://www.rubylife.jp/install/install/index1.html)
1. `gem install thor` で Thor gem をインストール。
1. 後述のスクリプトを適当な名前 (仮に `organize_photos.rb` とする) で保存する。
1. スクリプトと同じディレクトリに [Exiv2 - Image metadata library and tools](http://www.exiv2.org/download.html) からダウンロードした "Windows executable" のバイナリを保存する。
  * RAW や JPEG 画像の Exif データから撮影日時を抽出するために利用。
  * [exifr](https://github.com/remvee/exifr) という gem の方が手軽ですが、これは JPEG と TIFF しか扱えないので NG でした。
1. `ruby organize_photos.rb mv {移動元フォルダ} {移動先フォルダ}` で移動を実行。

ここまででファイル構成が以下のようになっていれば OK です。

```bash
$ ls .
exiv2.exe          libexpat.dll        organize_photos.rb
```

スクリプト
----
```rb
require 'date'
require 'fileutils'
require 'thor'

class OrgnizePhoto < Thor
  desc 'move SRC_DIR dest_DIR', 'Move files in SRC_DIR to dest_DIR'
  option :extensions, default: 'nef,dng,jpg,jpeg'
  option :date_format, default: '%Y%m%d'
  option :force, type: :boolean, default: true
  option :noop, type: :boolean, default: false
  def mv(src_dir, dest_dir)
    get_files(src_dir, options[:extensions]).each do |src|
      date = extract_date(src, options[:date_format])
      dest = "#{dest_dir}/#{date}/" + src.sub(src_dir, '')

      opts = { force: options[:force], noop: options[:noop] }
      move src, dest, opts
    end
  end

  private

  def get_files(dir, extensions)
    Dir.glob("#{dir}/*.{#{extensions}}")
  end

  def extract_date(file, date_format)
    exif = `exiv2.exe -q -Pnv #{file}`
    if /DateTimeOriginal\s+(\d{4}:\d{2}:\d{2})\s+(\d{2}:\d{2}:\d{2})/ =~ exif
      DateTime.parse("#{$1.tr(':', '-')} #{$2}").strftime(date_format)
    else
      false
    end
  end

  def move(src, dest, options)
    dest_dir = File.dirname(dest)
    begin
      unless Dir.exist?(dest_dir)
        FileUtils.mkdir_p dest_dir
        puts "Created #{dest_dir}"
      end

      FileUtils.move(src, dest, options)
      puts "#{src} -> #{dest}"
    rescue Exception => e
      p "#{src} -> #{dest} ... #{e.inspect}"
    end
  end
end

OrgnizePhoto.start(ARGV)

```

実行例
----
```bash
$ ruby -Eutf-8 organize_photos.rb mv --noop f:/Pictures/001/ f:/Pictures/

f:/Pictures/001/RIMG0408.jpg -> f:/Pictures/20080707/RIMG0408.jpg
f:/Pictures/001/RIMG1817.jpg -> f:/Pictures/20080907/RIMG1817.jpg
f:/Pictures/001/RIMG1828.jpg -> f:/Pictures/20080908/RIMG1828.jpg
f:/Pictures/001/RIMG1840.jpg -> f:/Pictures/20080909/RIMG1840.jpg
```

補足
----
* 私が Nikon および Ricoh ユーザーなので、拡張子は `.nef, .dng, .jpg, .jpeg` ぐらいしか対応してません。
  * 他のメーカーのカメラをお使いの方は、拡張子を実行時のオプション `--extensions` に追加してください。
  * スクリプト内の `option :extensions, default: 'nef,dng,jpg,jpeg'` の中に直接追加しても OK。
* オプション `--noop` を付けた場合は、ファイルの移動先が表示されるだけで実際に移動は発生しません。
* かなり急いで書いたので、変なバグがあるかもしれません。気づいた方はコメントか編集リクエストをください。
* 誰か僕に [D5](http://www.nikon-image.com/products/slr/lineup/d5/) か [D500](http://www.nikon-image.com/products/slr/lineup/d500/) を買ってください。


参考にした記事
----
* [Snip2Code - Simple ruby script that renames all .jpg/.mov/.nef files in the given folder to a name containing the exif creation timestamp (for jpg/nef) and modification time (for mov)](https://www.snip2code.com/Snippet/912445/Simple-ruby-script-that-renames-all--jpg)