Newer
Older
webtls / rjpg
@yuuji yuuji on 31 Aug 2003 8 KB Small fix for ruby 1.8.0
#!/usr/local/bin/ruby
# Resize jpeg files
# $Id$
# Last modified Sun Aug 31 17:27:47 2003 on firestorm

# 基本的な使い方
# あるディレクトリにたくさんの大きなJPEGファイル(デジカメで撮ったままの
# ファイルとか)があるときに、それらをカレントディレクトリに適当なサイズ/
# 明るさに一括変換して吐き出す。吐き出したjpgファイルのコメントエリアに
# はそのファイルのタイムスタンプが埋め込まれる。
#
# 【WWW用に幅400くらいにしたい】
#	% rjpg -x 400 /dos/d/*jpg
# 【やっぱりもとの半分のサイズで】
#	% rjpg -s 0.5 -f /dos/d/*jpg		(-f付けないと上書きしない)
# 【γ値1.5で】
#	% rjpg -x 400 -g 1.5 /dos/d/*.jpg	(デフォルトは1.2)
# 【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"
pnmgamma	= "pnmgamma %s"
pnmcut		= "pnmcut"
cat		= "cat"
outputdir	= "."
resize		= false
quality		= "75"
gamma		= "1.2"
tformat		= "%a %b %d %T %Y %Z"
uniqfile	= false
uniqonly	= false
scalefmt	= "pnmscale -xsize %d -ysize %d"
fnformat	= "i%Y%m%d-"
fnsuffixfigure	= 3
xtable		= File.basename($0) + ".xtb"

$debug		= false
scale		= false
width		= false
force		= false
rotate		= false
fntitle		= false
Yfix		= false
tsname		= false
mkxtable	= false
putexif		= false
cutx		= nil
cuty		= 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
	-s SCALE	Set scale (for pnmscale) to SCALE
	-o DIR		Output directory
	-q N		Jpeg compression quality
	-f		Force 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		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)
	-tf FMT		Time format same as strftime(3)
	-u		Uniq File Name according to time stamp
	-uf		(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_
#'

while (/^-.+/ =~ ($_ = ARGV[0]) && ARGV.shift)
  $_= $_.dup
  break if ~/^--$/
  while ~/^-[A-z]/
    if ~/^-x$/
      width = ARGV.shift
    elsif ~/^-y$/
      height = ARGV.shift
      Yfix = true;
    elsif ~/^-s$/
      scale = ARGV.shift
    elsif ~/^-d$/
      $debug = true
    elsif ~/^-o$/
      outputdir = ARGV.shift
    elsif ~/^-q$/
      quality = ARGV.shift
    elsif ~/^-f$/
      force = true
    elsif ~/^-g/
      gamma = ARGV.shift
    elsif ~/^-r/
      rotate = ARGV.shift
    elsif ~/^-p$/
      cjpg << " -progressive"
    elsif ~/^-fn$/
      fntitle = true; sub!(/./, '')
    elsif ~/^-ex$/
      putexif = true; sub!(/./, '')
    elsif ~/^-i$/
      #djpg = "convert - PNM:-"
      pnmgamma = "convert -gamma %s - -"
      scalefmt = "convert -geo %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$/
      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

def charAt(handle, at)
  handle.seek(at, 0)
  return handle.read(1)[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
    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
  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 = File.basename(f)
  end
  if width && !Yfix
    #x, y = `#{djpg} #{f}|pnmfile -`.scan(/(\d+) by (\d+)/)[0]
    x, y = jpgsize(f)
    if width == x
      scale = 1
    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]
    x, y = jpgsize(f)
    if height == y
      scale = 1
    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 -geo %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)
    0.upto(10**fnsuffixfigure-1) {|i|
      outfile = fbase + sprintf(fnsuffixfmt, i)
      break unless test(?f, outfile)
    }
    if mkxtable
      open(xtable, "a"){|x|
	x.printf "s/%s/%s/\n", File.basename(f), File.basename(outfile)
      }
    end
    if uniqonly
      File.link(f, outfile)
      print "ln #{f} #{outfile}\n" unless $quiet
      next
    end
  else
    outfile = "#{outputdir}/" + File.basename(f)
  end
  # sleep(1);
  if test(?f, outfile)
    s1, s2 = File.stat(f), File.stat(outfile)
    if s1.dev==s2.dev && s1.ino==s2.ino
      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} | " + sprintf(pnmgamma, gamma)
      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