#!/usr/local/bin/ruby # Resize jpeg files # $Id$ # Last modified Tue Dec 29 14:11:07 2009 on cbr # 基本的な使い方 # あるディレクトリにたくさんの大きなJPEGファイル(デジカメで撮ったままの # ファイルとか)があるときに、それらをカレントディレクトリに適当なサイズ/ # 明るさに一括変換して吐き出す。吐き出したjpgファイルのコメントエリアに # はそのファイルのタイムスタンプが埋め込まれる。 # # 【WWW用に幅400くらいにしたい】 # % rjpg -x 400 /dos/d/*jpg # 【やっぱりもとの半分のサイズで】 # % rjpg -s 0.5 -f /dos/d/*jpg (-f付けないと上書きしない) # 【縦横長い方の辺を800ピクセルに】 # % rjpg -l 400 /dos/d/*jpg # 【γ値1.5で】 # % rjpg -x 400 -g 1.5 /dos/d/*.jpg (デフォルトは1.0) # 【90度回転して】 # % rjpg -x 400 -r 90 /dos/d/dcp01234.jpg # 【中央部70%だけ残るようトリミング】 # % rjpg -x 600 -c 70% /dos/d/dcp01235.jpg $rdjpg = "rdjpgcom" wrjpg = "wrjpgcom" djpg = "djpeg" cjpg = "cjpeg" pnmcut = "pnmcut" cat = "cat" outputdir = "." resize = false quality = ENV["RJPG_q"] || "75" gamma = ENV["RJPG_g"] || "1.0" tformat = "%a %b %d %T %Y %Z" uniqfile = false uniqonly = false pnmgamma = "pnmgamma %s" scalefmt = "pnmscale -xsize %d -ysize %d" `sh -c "type pamscale"` if $?.to_i == 0 then scalefmt = "pamscale -xsize %d -ysize %d" pnmgamma = "pnmgamma -bt709tolin -gamma=%s -" end fnformat = "i%Y%m%d-%H%M-" fnsuffixfigure = 2 xtable = File.basename($0) + ".xtb" $debug = false scale = false width = false force = false noforce = false rotate = false fntitle = false Yfix = false tsname = false mkxtable = false putexif = system("/bin/sh", "-c", "which jhead >/dev/null 2>&1") cutx = nil cuty = nil landscape = nil portrait = nil usage = <<_EOU_ #{File.basename($0)} [options] JpegFiles Options are... -x WIDTH Set new jpg file's width to WIDTH -y HEIGHT Set new jpg file's height to HEIGHT -l LENGTH Set new jpg file's longer edge length to LENGTH -s SCALE Set scale (for pnmscale) to SCALE -o DIR Output directory -q N Jpeg compression quality -f Force Overwrite -n Not to Overwrite -g N Gamma correction -r ANGLE Rotate ANGLE degrees in unti-clockwise -c X,Y,W,H Cut W*H rectangle from coordinate (X,Y) -c N% Cut the N% center of photo -p FILE Print File's parent file name filled in by rjpg -pt FILE DIR Check if FILE's parent file is in DIR -rm [DIR] Show every file which has no parent in DIR(..). -rmok [DIR] Remove every file which has no parent in DIR(..). -P Create progressive JPG -i Use ImageMagick (Aprx. 4times slow) -fn Put file name in jpg comment area, instead of time -ex Put Exif data in jpg comment area (requires jhead) -ex- Do not copy exif -tf FMT Time format same as strftime(3) -u Uniq File Name according to time stamp -uf FMT (with -u)Filename format in strftime(3) (#{fnformat}) -sf N (with -u)File name counter's figure (default: 3) -xt (with -u)Create filename translation table -uo Uniquify File Name Only, no conversion _EOU_ #' def parent_file(file) if test(?s, file) then if /Original file name: (\S*)/ =~ `#{$rdjpg} #{file}` return $1 end end return false end def parent(file) if pf=parent_file(file) then puts pf exit 0 end exit 1 end def parent_test(file, dir) if pf=parent_file(file) then test(?s, File.expand_path(pf, dir)) and exit(0) or exit 1 end end def rm_orphan(dir, ok=false) dir = ".." unless dir for f in Dir.glob("*.jpg") if (pf=parent_file(f)) && !test(?s, File.expand_path(pf, dir)) then ok and File.unlink(f) printf("rm %s%s\n", f, ok ? " done." : "") end end exit 0 end while (/^-.+/ =~ ($_ = ARGV[0]) && ARGV.shift) $_= $_.dup break if ~/^--$/ while ~/^-[A-z]/ if ~/^-x$/ width = ARGV.shift.to_i elsif ~/^-y$/ height = ARGV.shift.to_i Yfix = true; elsif ~/^-l$/ length = ARGV.shift elsif ~/^-s$/ scale = ARGV.shift elsif ~/^-d$/ $debug = true elsif ~/^-o$/ outputdir = ARGV.shift elsif ~/^-q$/ quality = ARGV.shift elsif ~/^-f$/ force = true elsif ~/^-n$/ noforce = true elsif ~/^-g/ gamma = ARGV.shift elsif ~/^-r$/ rotate = ARGV.shift elsif ~/^-p$/ parent(ARGV.shift) elsif ~/^-pt$/ parent_test(ARGV.shift, ARGV.shift) elsif ~/^-rm$/ rm_orphan(ARGV.shift) elsif ~/^-rmok$/ rm_orphan(ARGV.shift, true) elsif ~/^-P$/ cjpg << " -progressive" elsif ~/^-fn$/ fntitle = true; sub!(/./, '') elsif ~/^-ex$/ putexif = true; sub!(/./, '') elsif ~/^-ex-$/ putexif = false; sub!(/../, '') elsif ~/^-i$/ #djpg = "convert - PNM:-" pnmgamma = "convert -gamma %s - -" scalefmt = "convert -geometry %dx%d - -" elsif ~/^-c$/ $_ = ARGV.shift if ~/(\d+)%/ $cut = $1 else $cut=$_ cutx, cuty = $cut.split(/,/)[0][2..3] $cut = "#{pnmcut} " + $_.split(/,/).join(" ") end elsif ~/^-u$/ uniqfile = true elsif ~/^-uf$/ uniqfile = fnformat = ARGV.shift; sub!(/u/, '') elsif ~/^-sf$/ fnsuffixfigure = ARGV.shift.to_i; sub!(/s/, '') elsif ~/^-xt$/ mkxtable = true; sub!(/x/, '') elsif ~/^-uo$/ uniqonly = uniqfile = true; sub!(/./, '') else puts "Invalid option #{$_}" print usage exit 0 end $_.sub!(/^-.(.*)/, '-\1') end end def intr () unlink(outfile) if test(?f, outfile) STDERR.puts "User break: #{outfile} deleted" exit 0; end class String if defined?("a".getbyte) nil else def getbyte(n) return self[n] end end end def charAt(handle, at) handle.seek(at, 0) return handle.read(1).getbyte(0) return handle.read(1).unpack("C")[0] end def jpgsize(file) # Return [x, y, comment] if success, else nil filesize=File.size(file) return nil if filesize < 2 # must have jpeg id (2bytes) at least open(file, "r"){|h| buf = h.read(2) comment="" # return nil if buf[0] != 0xff || buf[1] != 0xd8 return nil if buf != "\xff\xd8" seekpoint = 2 catch (:exit) { while seekpoint < filesize-4 if charAt(h, seekpoint) != 0xff then throw :exit, 0 elsif [192, 198, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].index(charAt(h, 1+seekpoint)) then # jpeg geometry area found! h.seek(5+seekpoint, 0) buf = h.read(4) x = buf[2..3].unpack("n")[0] y = buf[0..1].unpack("n")[0] throw :exit, [x, y, comment] elsif 0xFE == charAt(h, 1+seekpoint) then # JPEG comment area h.seek(2+seekpoint, 0) len = h.read(2).unpack("n")[0] comment << h.read(len-2)+" " else # Other marker # Do nothing end seekpoint += 2 h.seek(seekpoint, 0) buf = h.read(2) seekpoint += buf.unpack("n")[0] end } } end for f in ARGV if ! test(?f, f) STDERR.puts "No such file: #{f}"; next end timestamp = File.mtime(f) comment = `#{$rdjpg} #{f}`.chomp origbase = File.basename(f) if comment > "" stamp = comment puts "#{f} Comment: #{stamp}" if $debug elsif ! fntitle # stamp = &timeformat($timestamp); stamp = Time.at(timestamp).strftime(tformat) puts "#{f} Stamp: #{stamp}" if $debug else stamp = origbase end if !scale x, y = jpgsize(f) if length if x>y landscape, portrait, width, height = true, nil, length.to_i, nil else landscape, portrait, width, height = nil, true, nil, length.to_i end end end if width && !Yfix #x, y = `#{djpg} #{f}|pnmfile -`.scan(/(\d+) by (\d+)/)[0] if width > x #STDERR.puts "New width > original. Skipping #{f}" #next STDERR.puts "New width > original. Keeping original size #{x}:#{y}" resize = sprintf(scalefmt, x, y) else if cutx && cuty height = width.to_i * cuty.to_i / cutx.to_i else height = y.to_i * width.to_i / x.to_i end ##resize = "pnmscale -xsize #{width} -ysize #{height}" resize = sprintf(scalefmt, width, height) end elsif height ##x, y = `#{djpg} #{f}|pnmfile -`.scan(/(\d+) by (\d+)/)[0] if height > y #STDERR.puts "New height >= original. Skipping #{f}" #next STDERR.puts "New height > original. Keeping original size #{x}:#{y}" resize = sprintf(scalefmt, x, y) else if cutx && cuty width = height.to_i * cutx.to_i / cuty.to_i else width = x.to_i * height.to_i / y.to_i end #resize = "pnmscale -xsize #{width} -ysize #{height}" resize = sprintf(scalefmt, width, height) end elsif scale if ~/convert/ resize = sprintf("convert -geometry %d%% - -", (scale.to_i*100).round) else resize = "pnmscale #{scale}" end end # Construct file name outfile = '' fnsuffixfmt = "%0#{fnsuffixfigure}d.jpg" if uniqfile fbase = "#{outputdir}/" + Time.at(timestamp).strftime(fnformat) sc = "Original file name: #{origbase}" # stamp concats 0.upto(10**fnsuffixfigure-1) {|i| outfile = fbase + sprintf(fnsuffixfmt, i) break unless test(?f, outfile) if system("sh", "-c", "jhead #{outfile}|fgrep #{origbase} >/dev/null 2>&1") File.unlink(outfile) break end } stamp += "\n"+sc # Append Original Filename to stamp string if mkxtable open(xtable, "a"){|x| x.printf "s/%s/%s/\n", origbase, File.basename(outfile) } end if uniqonly File.link(f, outfile) print "ln #{f} #{outfile}\n" unless $quiet next end else outfile = "#{outputdir}/" + origbase end # sleep(1); if test(?f, outfile) s1, s2 = File.stat(f), File.stat(outfile) if s1.dev==s2.dev && s1.ino==s2.ino || (!force && noforce) puts "Skipping #{f}..." next end if ! force outfile.sub!(/\.jpg$/, "\.jpeg") puts "Set out file to #{outfile}" end end trap(:INT, 'intr') open(f) do |img| img.binmode if resize if /#{pnmcut}/o =~ $cut cmdline = "#{djpg} | #{$cut}" elsif $cut && $cut.to_i > 0 c = $cut.to_i ox, oy = `#{$rdjpg} -v #{f}`.scan(/(\d+)w \* (\d+)h/)[0] ox, oy = ox.to_i, oy.to_i x1, y1, w1, h1 = ox/2*(100-c)/100, oy/2*(100-c)/100, ox*c/100, oy*c/100 cmdline = "%s | %s %d %d %d %d" % [djpg, pnmcut, x1, y1, w1, h1] # cmdline = "djpeg | pnmcut 264 175.2 1232 817.6" #cmdline = "djpeg | pnmcut 200 100 800 600 " # cmdline = "djpeg" else cmdline = djpg.dup end cmdline << " | #{resize} " cmdline << "| " + sprintf(pnmgamma, gamma) if gamma.to_f != 1 if rotate cmdline << "| pnmrotate #{rotate}" end cmdline << "| #{cjpg} -q #{quality} | #{wrjpg} -c \"#{stamp}\" > #{outfile}" puts "#{cat} #{f} | #{cmdline}" open("| #{cmdline}", "w") do |out| out.binmode out.sync=true #out.print img.readlines begin out.print img.read rescue STDERR.puts "#{f}: Trailing superfluous data ignored." end end trap('PIPE', 'DEFAULT') else # resizing puts("#{wrjpg} -c \"#{stamp}\" #{f} > #{outfile}") unless $quiet open("| #{wrjpg} -c \"#{stamp}\" > #{outfile}", "w") do |out| out.binmode begin out.print img.read rescue STDERR.puts "#{f}: Trailing superfluous data ignored." end end end end trap('INT', "DEFAULT") if putexif puts "jhead -te '#{f}' #{outfile}" unless $quiet system "jhead -te '#{f}' #{outfile}" tf = "#{outputdir}/rjpg-#{$$}.tmp" File.unlink(tf) if test(?f, tf) File.rename(outfile, tf) system "#{wrjpg} -c \"#{stamp}\" #{tf} > #{outfile}" File.unlink(tf) end File.utime(timestamp, timestamp, outfile) end