写真取り込みスクリプト
デジカメから写真を取り込むスクリプトを書いてみた。アルバム系ソフトと違って「さっき撮った写真」とか「昨日の午後に撮ったあれらの写真」とか、そういう感じで指定して取り込める。デフォルトでは1時間以内に撮影された一連の写真群を1つのイベントとみなす。
Rubyの勉強と実用を兼ねている。書く前は実用性は高くないかとも思っていたけど、実際に使ってみると結構自分的には便利なスクリプトになった。
- find、fileutilsなどを使ったファイルの扱いの基本
- 基本的なクラスの定義の仕方
- optparseライブラリの使い方。オプションぐらいでいろいろ大げさだなと思ったけど、使ってみると非常に便利だった
- 定数を大文字で定義するらしいこと
- コロンなんとかはシンボル(Lispにもシンボルってあったっけ?)
- ハッシュのキーにシンボルを使うほうがベターらしいこと(ほんとかな)
- Range.newを使った範囲オブジェクトの生成
- 範囲オブジェクトが、実は文字列でも有効なこと
- 例外処理の基本的な構文を何となく使ってみた
- 目が慣れると、Arrayにはpushよりも<<のほうが手軽に書けて、後から見ても分かりやすいということ
- strftimeを使った時刻オブジェクトのテキスト整形
- RDocの基本とツールの使い方
#!/usr/bin/ruby require "find" require "fileutils" require "optparse" =begin = 写真取り込みスクリプト (1) 指定ディレクトリ以下のRAWとJPEGファイルをflistに (2) タイムスタンプ逆順にソートしてslistに (3) slistの隣同士を見て一定時間以内の撮影なら同一イベントとみなして同一IDを割り 当てる (4) ID判別で直近イベントと思われる写真のリストをCamera.latestで返す (5) ID判別でn回前のイベントと思われる写真のリストをCamera.event(n)で返す * デフォルトでは直近から5回前までのイベントの写真枚数と時間を表示 * -cオプションでカレントディレクトリに取り込み = 今後やりたい =end PHOTO_DIR = "/media/disk/DCIM/" HOURS = 1.0 # 同一イベントの撮影とみなすシャッター間隔 # default options Options = { :photo_dir => PHOTO_DIR, :hours => HOURS, :copy => false, :events => (1..5), :verbose => false, } class Camera def initialize(dir=PHOTO_DIR) @dir = dir @flist = [] # [fullpath, mtime] @slist = [] # time sorted flist [fullpath, mtime, event_id] make_list time_sort end def make_list Find.find(@dir){|f| if (File.ftype(f) == "file") && (f =~ /.*jpg/i) || (f =~ /.*nef/i) @flist.push [f,File.mtime(f)] end } rescue => ex print ex.message, "\n" exit end def time_sort @slist = @flist.sort {|x,y| x[1] <=> y[1] }.reverse t = @slist[0][1] @count = 1 @slist.each {|file| if (((t - file[1]).to_f / 3600) > Options[:hours]) @count += 1 end file.push(@count) t = file[1] } end def event(num) @event = [] slist.each {|file| if (file[2] == num) # event_id @event.push file end } return @event end def latest return self.event(1) end protected :make_list, :time_sort attr_reader :flist, :slist end # parse options def parse_option opts = OptionParser.new opts.on("-c", "--copy", "copy the photos, instead of just showing"){|val| Options[:copy] = true} opts.on("-t [duration]", "--time", "set the duration hours of event"){|val| Options[:hours] = val.to_f} opts.on("-e [number expression]", "--event", "set the events to show/import"){|val| numbers = [] if val =~ /[^0-9\,\-]/ puts "Error! allowed expressions for -e option are: 1,2,5-7..." exit end val.split(",").each{|exp| if exp =~ /(\d)-(\d)/ Range.new($1,$2).each{|n| # This works, not sure if it's okay. numbers << n.to_i # Range object generates a sequence of string nums... } else numbers << exp.to_i end } Options[:events] = numbers } opts.on("-l", "--latest", "set the event to the latest"){|val| Options[:events] = [1] } opts.on("-v", "--verbose", "verbose mode"){|val| Options[:verbose] = true } opts.parse!(ARGV) end # main parse_option nikon = Camera.new if Options[:verbose] print "Duration of each event is set to #{Options[:hours]} hour(s)\n\n" end if Options[:copy] Options[:events].each {|n| nikon.event(n).each {|photo| FileUtils.cp(photo[0],".", {:verbose => Options[:verbose]}) } } else Options[:events].each {|n| number = nikon.event(n).size print "Event",n, "(#{number}): " print nikon.event(n)[number-1][1].strftime("%m/%d %H:%M - ") print nikon.event(n)[0][1].strftime("%m/%d %H:%M") print "\n" } end