Newer
Older
webtls / rjpg
@yuuji yuuji on 30 Dec 2009 11 KB Revise comment docs.
#!/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