#!/usr/bin/perl -w
# ripmake
#
# automatically configure transcode for various conversion tasks
#
# Written by Christian Vogelgsang <Vogelgsang@cs.fau.de>
# under the GNU Public License V2
#

# print hello!
print "ripmake: " . '$Revision: 1.27 $ $Date: 2002/08/27 12:14:15 $ ' . "\n";

# fetch transcode version
$tcver = `transcode 2>&1 | head -n 1`;
$tcver =~ m/v(\d+)\.(\d+)\.([^\s]+)/;
if(!defined $1) {
  print "FATAL: can't fetch transcode version!\n";
  exit(1);
}
$tcvmaj = $1;
$tcvmin = $2;
$tcvsup = $3;
print "  using transcode version: $tcvmaj.$tcvmin.$tcvsup\n";

# check version requirements: at least 0.6.x
if(($tcvmaj==0)&&($tcvmin<6)) {
  print "FATAL: please use at least version 0.6!\n";
  exit(1);
}

# --- set presets -------------------------------------------------------------

# output flavors
@flavors = ('avi','svcd','vcd');

# output codecs
%codecs = ( 'avi'  => [ 'xvid','xvidcvs','divx4' ],
	    'vcd'  => [ 'mpeg','mpeg2enc' ],
	    'svcd' => [ 'mpeg','mpeg2enc' ] );

# default codec for flavor
%def_codec = ( 'avi' => 'xvid', 'svcd' => 'mpeg2enc', 'vcd' => 'mpeg2enc' );

# map flavor to default audio rate
%def_aud_rate = ( 'avi' => 128, 'svcd' => 224, 'vcd' => 224 );

# map flavor to mux overhead bitrate (kbits/sec)
%mux_add_rate = ( 'avi' => 60, 'svcd' => 48, 'vcd' => 48 );

# how many MB reserve for target format data
%cd_reserve = ( 'avi' => 0, 'svcd' => 1, 'vcd' => 1 );

# how to scale cd size (for raw bits)
%cd_raw_scale = ( 'avi' => 1, 'svcd' => 2324/2048, 'vcd' => 2324/2048 );

# frc (frame rate codes)
@frame_rate_codes = ('illegal','23.976 NTSC (film)','24.0', # 0 1 2
		     '25.0 PAL','29.97 NTSC','30.0',        # 3 4 5
		     '50.0','59.94','60.0',                 # 6 7 8
		     '1?','5?','10?',                       # 9 10 11
		     '12?','15?');

# --- parse options -----------------------------------------------------------
# option defaults
%options = (
  'flavor' => 'avi',         # flavor for output
  'codec' => 'none',         # output tc codec
  'icodec' => 'auto',        # input tc codec

  'cd_size' => 700,          # size of CD in MB
  'cd_max' => 4,             # check this number of CDs
  'cd_force' => 0,           # force this number of CDs
  'cd_frac' => 1.0,          # fraction of cd_size used for rip
  'dvd_title' => '1',        # select a title in DVD input
  'tv_norm' => 'none',       # pick an output tv norm (otherwise use input)
  'size_force' => [0,0],     # force a specific output size
  'aud_track' => [],         # list of input source tracks to rip
  'aud_rate' => [],          # list of kbps rates for each audio track
  'aud_samp_rate' => [],     # list of sample rate for each audio track
  'aud_chan' => [],          # list of channels for each audio track
  'frame_mod' => 16,         # output frame size modulo
  'width_min' => 512,        # output frame width search begin
  'width_max' => 640,        # output frame width search end
  'asrerr_max' => 2,         # maximum accepted aspect error (in percent)
  'clip_bmodulo' => 1,       # pixel modulo of clipped border
  'clip_fmodulo' => 16,      # pixel modulo of clipped target frame
  'clip_opts' => '',         # extra options for pgmfindclip
  'scl_tol' => 7,            # pixel tolerance when scaling
  'sample_max' => 3,         # max number of sample images
  'sample_begin' => 1000,    # start frame for sample image extraction
  'sample_step' => 100,      # increment for next frame in sample image extr.
  'clip_force' => '',        # force clip parameters
  'bpp_min' => 0.19,         # minimum bits per pixel
  'bpp_max' => 0.25,         # maximum bits per pixel
  'interlaced' => 0,         # input is interlaced
  'max_vrate' => 6000,       # maximum supported video rate
  'high_quality' => 0,	     # highest quality encoder settings
  'anamorph' => -1,          # generate anamorph output (-1=autodetect)

  'debug' => 0               # set debug level
);

parse_options(\%options,\@ARGV);
check_options(\%options);
if($options{'debug'}>0) {
  dump_info("Options",\%options);
}

# --- analyse input -----------------------------------------------------------
%input = ();
$ok = probe_input(\%input,\%options);

# debug: print all internal hashes
if($options{'debug'}>0) {
  dump_info("Input info",\%input);
}
exit(1) if(!$ok);

# --- prepare output ----------------------------------------------------------
%output = ();
prepare_output(\%input,\%output,\%options);

# debug: print all internal hashes
if($options{'debug'}>0) {
  dump_info("Output info",\%output);
}

# --- generate transcode params -----------------------------------------------
generate_make(\%input,\%output,\%options);

print "ready.\n";
exit(0);

########## SUBROUTINES ########################################################

# --- print usage information -------------------------------------------------
sub usage_exit {
  my($reason) = shift;
  print <<EOF;

  Written by Christian Vogelgsang <Vogelgsang\@cs.fau.de>

Usage:

  $0 [Options] <input-file/dir> <flavor>

Common Options:

  -t <num>     Select title in DVD mode        (default: 1,-1,1)
  -a <t>[,<br>][,<sr>][,<c>]
               Pick audio track, bitrate,
               sampling rate and channels.
               Repeat for more audio tracks!

Advanced Options:

  -n <tv_norm> Set output tv norm (pal,ntsc)   (default: use input)
  -i <on/off>  Input signal is interlaced      (default: 0=off)

  -g <w>[,<h>] Force a target frame size       (default: autodetect)
  -c <num>     Force number of CDs             (default: autodetect)
  -r <num>     Force clip range
  -s <num>     Set size of a CD in MB          (default: 700)
  -S <frac>    Use only a fraction of CD size  (default: 1.0)
  -o <codec>   Choose codec for flavor
  -f <codec>   Force input codec               (default: auto)

Expert Options:

  -x var=value Set an option variable          (help: "-x help")
  -d <level>   Enable debug output

EOF
  print "supported flavors: " . join(',',@flavors) . "\n";
  if(defined($reason)) {
    print "---> ERROR: $reason\n";
  }
  exit(1);
}

# ========== options ==========================================================
# --- parse arguments ---------------------------------------------------------
# fill in an overwrite the %options hash
sub parse_options {
  my($opt)  = shift;
  my($argv) = shift;

  while($_=shift @$argv) {
    # --- option args begin with '-' ---
    if( m/^-(.)(.*)$/ ) {

      # fetch optional argument
      my($arg) = $2;
      $arg = shift @$argv if($2 eq '');

      # --- main options ---
      # t = dvd_title
      if($1 eq 't') {
	$$opt{'dvd_title'} = $arg;
      }
      # a = out_chn, out_rate
      elsif($1 eq 'a') {
	my($trk,$rate,$samp_rate,$chan) = split(/,/,$arg);
	&usage_exit("no track in -a given!") if(!defined($trk));
	# source track
	my($ref) = $$opt{'aud_track'};
	push @$ref,$trk;
	# rate
	# set rate 0 if none given - will be corrected in check_options
	$rate = 0 if(!defined($rate));
	$ref = $$opt{'aud_rate'};
	push @$ref,$rate;
	# samp_rate - will be corrected in check_options
	$samp_rate = 0 if(!defined($samp_rate));
	$ref = $$opt{'aud_samp_rate'};
	push @$ref,$samp_rate;
	# channels - will be corrected in check_options
	$chan = 0 if(!defined($chan));
	$ref = $$opt{'aud_chan'};
	push @$ref,$chan;
      }
      # --- advanced options ---
      # n = tv_norm
      elsif($1 eq 'n') {
	$$opt{'tv_norm'} = $arg;
	&usage_exit("unknown tv norm: $arg") 
	  if(($arg ne 'pal')&&($arg ne 'ntsc'));
      }
      # i = interlaced
      elsif($1 eq 'i') {
	$$opt{'interlaced'} = $arg;
	&usage_exit("wrong interlaced flag: $arg")
	  if(($arg != 0)&&($arg != 1));
      }
      # g = force geometry
      elsif($1 eq 'g') {
	my($w,$h) = split(/,/,$arg);
	&usage_exit("no width found in -g!") if(!defined($w));
	if(!defined($h)) {
	  $$opt{'size_force'} = [int($w),0];
	} else {
	  $$opt{'size_force'} = [int($w),int($h)];
	}
      }
      # c = force num cd
      elsif($1 eq 'c') {
	$$opt{'cd_force'} = $arg;
      }
      # o = out_codec
      elsif($1 eq 'o') {
	$$opt{'codec'} = $arg;
      }
      # s = cd size in MB
      elsif($1 eq 's') {
	$$opt{'cd_size'} = $arg;
      }
      # S = fraction of cd_size
      elsif($1 eq 'S') {
	$$opt{'cd_frac'} = $arg;
      }
      # r = clip range
      elsif($1 eq 'r') {
	$$opt{'clip_force'} = $arg;
      }
      # f = force input 
      elsif($1 eq 'f') {
	$$opt{'icodec'} = $arg;
      }
      # --- profi options ---
      # x = extra var
      elsif($1 eq 'x') {
	# dump help and exit
	if($arg eq 'help') {
	  dump_info("Options",\%options);
	  exit(1);
	}
	my($var,$val);
	($var,$val) = split(/=/,$arg);
	$$opt{$var} = $val;
      }
      # d = debug
      elsif($1 eq 'd') {
	$$opt{'debug'} = $arg;
      }
      # unknown!
      else {
	&usage_exit("Unknown switch: $_");
      }
    } else {
      last;
    }
  }

  # --- fixed args: fetch input and flavor ---
  my($num_arg) = scalar @$argv;
  &usage_exit("give <input> <flavor>!") if($num_arg!=1);
  # input
  my($file) = $_;
  $file =~ s,/$,,;
  $$opt{'input'} = $file;
  # flavor
  $$opt{'flavor'} = $$argv[0];
}

# --- check passed param against a list of parameters -------------------------
sub check_param {
  my($desc) = shift;
  my($par)  = shift;
  my($list) = shift;

  my($found)=0;
  foreach(@$list) {
    return 1 if($_ eq $par);
  }
  print "Unsupported $desc parameter '$par' (in " . join(',',@$list) .")!\n";
  return 0;
}

# --- check option values -----------------------------------------------------
sub check_options {
  my($opt) = shift;

  # --- check input ---
  my($input) = $$opt{'input'};
  # if its a dir then find real path
  if ( -d $input ) {
    $input = `cd "$input" && pwd`;
    chop $input;
    $$opt{'input'} = $input;
  }

  # --- check flavor ---
  my($flavor) = $$opt{'flavor'};
  exit 1 if(!check_param('output flavor',$flavor,\@flavors));

  # --- check codec for flavor ---
  if($$opt{'codec'} eq 'none') {
    # use default codec
    $$opt{'codec'} = $def_codec{$flavor};
  } else {
    # codec is given
    my($cod) = $codecs{$flavor};
    exit 1 if(!check_param('output codec',$$opt{'codec'},$cod));
  }

  # --- audio setup ---
  # add track 0 as default if none is given
  my($ref) = $$opt{'aud_track'};
  if(scalar @$ref == 0) {
    # default: add track 0 with default audio rate
    push @$ref,0;
    $ref = $$opt{'aud_rate'};
    push @$ref,$def_aud_rate{$flavor};
    $ref = $$opt{'aud_samp_rate'};
    push @$ref,44100;
    $ref = $$opt{'aud_chan'};
    push @$ref,2;
  } else {
    # replace rate 0 values with default audio rate
    $ref = $$opt{'aud_rate'};
    foreach(@$ref) {
      $_ = $def_aud_rate{$flavor} if $_ == 0;
    }
    # replace samp_rate 0 values with default
    $ref = $$opt{'aud_samp_rate'};
    foreach(@$ref) {
      $_ = 44100 if $_ == 0;
    }
    # replace channel 0 values with default
    $ref = $$opt{'aud_chan'};
    foreach(@$ref) {
      $_ = 2 if $_ == 0;
    }
  }
}

# ========== probe_input ======================================================
# fill input hash with information about the source
sub probe_input {
  my($in)   = shift;
  my($opt)  = shift;
  my($ifile) = $$opt{'input'};
  my($title) = $$opt{'dvd_title'};

  # call tcprobe
  print "Probing title $title at $ifile with tcprobe...\n";
  $_ = `tcprobe -T $title -i \"$ifile\" 2>&1`;
  my(@lines) = split /\n/;
  if(m/failed to probe/) {
    print "  probing failed!\n";
    return 0;
  }

  my($aud_total) = 0;
  my(@aud_samp_rate);
  my(@aud_info);
  my(@aud_chan);

  # ----- DVD -----------------------------------------------------------------
  if(m,DVD image/device,) {
    # tc import codec
    $$in{'codec'} = 'dvd';

    # --- title set analysis ---
    # check title set
    if(!m/title set (\d+)/) {
      print "  parse error: no title set found!\n";
      return 0;
    }
    my($ts) = $1;
    $$in{'vts_id'} = $1;

    # --- extract number of title set files ---
    # check if its a real DVD
    if(-d "$ifile/video_ts") {
      print "  WARNING: directly ripping from a DVD! (better copy first!)\n";
      $ifile = "$ifile/video_ts";
    }
    if(-d "$ifile/VIDEO_TS") {
      print "  WARNING: directly ripping from a DVD! (better copy first!)\n";
      $ifile = "$ifile/VIDEO_TS";
    }

    # read vts files
    opendir(DIR,$ifile) || die("Can't read dir '$ifile'");
    my(@files) = readdir(DIR);
    closedir(DIR);

    # extract VTS VOB files for sample extraction
    my($max) = 0;
    my(@names);
    my($last);
    my($num)=0;
    foreach(@files) {
      # is vob file
      if(m/vts_(\d+)_(\d+).vob/i) {
	# ignore menu vobs
	next if($2 == 0);
	# vob of my title set?
	if($1 == $ts) {
	  if($2 > $max) {
	    $max = $2;
	  }
	}
	$last = "$ifile/$_";
	push @names,$last;

	# check if sample max reached
	$num++;
	last if($num==$$opt{'sample_max'});
      }
    }
    $$in{'vts_num'} = $max;
    # check if we really got sample vobs
    if($max==0) {
      print "ERROR: no sample VOBs found in '$ifile'!\n";
      return 0;
    }

    # --- create sample list ---
    my(@starts);
    if(scalar @names == 1) {
      # duplicate first name
      push @names,$names[0];
      push @starts,200;
      push @starts,1000;
    } else {
      foreach(@names) {
	push @starts,32;
      }
    }
    $$in{'sample_files'} = \@names;
    $$in{'sample_start'} = \@starts;

    # --- audio query ---
    foreach(@lines) {
      if(m/\(dvd_reader.c\) (.*) (\d+)kHz (\d+)Ch/) {
	push @aud_samp_rate,$2 * 1000;
	push @aud_info,$1;
	push @aud_chan,$3;
	$aud_total++;
      }
    }
  }
  # ----- AVI -----------------------------------------------------------------
  elsif(m/RIFF data, AVI/) {
    # extract avi codec
    if(!m/codec=(\w+)/) {
      print "  parse error: no avi codec\n";
      return 0;
    }
    my($codec)=$1;

    # choose tc import on avi codec
    if(($codec eq "DIV3")||($codec eq "DIVX")) {
      $$in{'codec'} = 'divx';
    } else {
      $$in{'codec'} = 'af6';
    }

    # --- audio setup ---
    foreach(@lines) {
      if(m/\[avilib\] A: (\d+) Hz.*format=([^\s,]+).*channels=(\d+)/) {
	push @aud_samp_rate,$1;
	push @aud_info,$2;
	push @aud_chan,$3;
	$aud_total++;
      }
    }
  }
  # unknown input
  else {
    print "  probing failed (unsupported input type?):\n$_\n";
    return 0;
  }

  # ----- Common Setup --------------------------------------------------------

  # --- audio parameters ---
  $$in{'aud_total'} = $aud_total;
  $$in{'aud_samp_rate'} = \@aud_samp_rate;
  $$in{'aud_chan'} = \@aud_chan;
  $$in{'aud_info'} = \@aud_info;
  $aud_total = 0;
  foreach(@aud_samp_rate) {
    printf "  audio%u: %5u kHz  %u channel(s)  info: %s\n",
      $aud_total,$aud_samp_rate[$aud_total],$aud_chan[$aud_total],
	$aud_info[$aud_total];
    $aud_total++;
  }

  # --- video parameters ---
  # size
  if(!m/import frame size: -g (\d+)x(\d+)/) {
    print "  parse error: no frame size found!\n";
    return 0;
  }
  $$in{'size'} = [ $1,$2 ];
  # aspect
  my($asr_derived) = 0;
  if(!m/aspect ratio: (\d+):(\d+)/) {
    $$in{'aspect'} = $$in{'size'};
    $asr_derived = 1;
  } else {
    $$in{'aspect'} = [ $1,$2 ];
  }
  # frame rate
  if(!m/frame rate: -f ([\d\.]+) .* frc=(\d+)/) {
    print "  parse error: no frame rate/frc found!\n";
    return 0;
  }
  $$in{'fps'} = $1;
  $$in{'frc'} = $2;
  # frames
  if(m/V: (\d+) frames, (\d+) sec/) {
    $$in{'frames'} = $1;
    $$in{'secs'} = $2;
  } elsif(m/length: (\d+) frames, frame_time=(\d+) msec/) {
    $$in{'frames'} = $1;
    $$in{'secs'} = $1 * $2 / 1000;
  } else {
    print "  parse error: no frames, secs!\n";
    return 0;
  }

  # --- setup sample images if none is given ---
  if(!defined($$in{'sample_files'})) {
    $$in{'sample_files'} = [];
    $$in{'sample_start'} = [];
    my($off) = $$opt{'sample_begin'};
    my($files) = $$in{'sample_files'};
    my($start) = $$in{'sample_start'};
    for($i=0;$i<$$opt{'sample_max'};$i++) {
      push @$files,$ifile;
      push @$start,$off % $$in{'frames'};
      $off += $$opt{'sample_step'};
    }
  }

  # overwrite input codec?
  my($icodec) = $$opt{'icodec'};
  if($icodec ne 'auto') {
    print "  codec:  '$icodec' (forced)\n";
    $$in{'codec'} = $icodec;
  } else {
    print "  codec:  '$$in{'codec'}' (detected)\n";
  }

  # verbose
  my($size) = $$in{'size'};
  my($asr)  = $$in{'aspect'};
  $asr = $$asr[0] / $$asr[1];
  printf "  size:   %3dx%3d     aspect: %3.2g:1 %s\n",$$size[0],$$size[1],$asr,
    $asr_derived ? '(from size)':'(probed)';

  # play time
  my($secs) = $$in{'secs'};
  my($ms)   = int(($secs - int($secs)) * 1000);
  my($min)  = int($secs / 60);
  $secs -= $min * 60;
  my($hour) = int($min / 60);
  $min -= $hour * 60;
  printf "  frames: %06d    playtime: %02d:%02d:%02d.%03d\n",
    $$in{'frames'},$hour,$min,$secs,$ms;
  my($code) = $frame_rate_codes[$$in{'frc'}];
  print "  fps:    $$in{'fps'}         frc: $$in{'frc'} ($code)\n";

  return 1;
}

# --- verbose an information hash ---------------------------------------------
sub dump_info {
  my($title) = shift;
  my($in) = shift;
  print "\t--- $title ---\n";
  foreach(sort(keys(%$in))) {
    my($value) = $$in{$_};
    print "\t$_ => ";
    if( ref($value) eq 'ARRAY' ) {
      print "[".join(',',@$value)."]";
    } else {
      print $value;
    }
    print "\n";
  }
}

# ========== processing =======================================================
# --- find the clipping region of the input -----------------------------------
# adds a 'tc_clip' on success
sub find_clip {
  my($in) = shift;
  my($opt) = shift;
  my(@clip);

  print "Searching clip area...\n";

  # --- use forced clip values ---
  my($force) = $$opt{'clip_force'};
  if($force ne '') {
    print "  using forced values: $force\n";
    @clip = split(/,/,$force);
    my($len) = scalar @clip;
    if($len==1) {
      push @clip,$clip[0],$clip[0],$clip[0];
    } elsif($len==2) {
      push @clip,$clip[0],$clip[1];
    } elsif($len==3) {
      push @clip,$clip[1];
    } elsif($len!=4) {
      print "ERROR: wrong clip parameters passed!\n";
      exit(1);
    }
  }
  # --- automatic clip with pgmfindlcip ---
  else {

    # --- generate a sample gray image for each vob file ---
    print "  generating sample images with transcode...\n";
    my($files) = $$in{'sample_files'};
    my($frame) = $$in{'sample_start'};
    my($num)=0;
    my(@samples);
    my($sfile) = "sample000000.pgm";
    foreach(@$files) {
      # span a single image 'range'
      my($f) = $$frame[$num];
      my($g) = $f+1;

      # input codec - use vob mode for dvd images
      my($codec) = $$in{'codec'};
      $codec = 'vob' if ($codec eq 'dvd');

      # call transcode
      $cmd = "transcode -z -x $codec -i \"$_\" -o sample -K -y ppm -c $f-$g";
      $cmd .= " >/dev/null 2>&1";
      print "    frame #$f from '$_' ";
      print " [$cmd] " if($$opt{'debug'}>0);
      system($cmd);

      # check generated sample image
      if(!-s $sfile) {
	print "ERROR: can't create sample file '$sfile'\n";
	exit(1);
      }

      # rename to clipXX.pgm
      my($name) = sprintf("clip%02d.pgm",$num);
      rename $sfile,$name;
      print "-> $name\n";

      # store sample images in list
      push(@samples,$name);
      $num++;
    }
    # safety check
    if(scalar @samples == 0) {
      print "FATAL: no sample images found!!\n";
      exit 1;
    }

    # --- find best clip window for all sample images ---
    # requires: findclip
    my($opts) = "-b $$opt{'clip_bmodulo'} -f $$opt{'clip_fmodulo'}";
    $opts .= " $$opt{'clip_opts'}" if($$opt{'clip_opts'} ne '');
    print "  finding clip region (pgmfindclip $opts): ";
    $clip = `pgmfindclip $opts @samples`;
    $clip =~ m/(\d+),(\d+),(\d+),(\d+)/ || die("Can't find clip region!");
    $clip[0] = $1;
    $clip[1] = $2;
    $clip[2] = $3;
    $clip[3] = $4;
    print join(',',@clip) . "\n";

    # --- remove temp files --
    if($$opt{'debug'}>0) {
      print "  DEBUG: keeping sample image files.\n";
    } else {
      foreach(@samples) {
	unlink($_);
      }
    }
  }

  # check result and skip values smaller than tolerance
  my($sum)=0;
  foreach(@clip) {
    # clip too small
#    $_ = 0 if($_ <= $$opt{'clip_tol'});
    $sum += $_;
  }

  # clipping is valid
  if($sum>0) {
    # store clip parameter
    print "  final clip: " . join(',',@clip) . "\n";
    $$in{'clip'} = \@clip;

    # store clip_size
    my($size) = $$in{'size'};
    my($w,$h);
    $w = $$size[0] - ($clip[1]+$clip[3]);
    $h = $$size[1] - ($clip[0]+$clip[2]);
    $$in{'clip_size'} = [$w,$h];

    # store new clip aspect
    my($aspect) = $$in{'aspect'};
    my($a_x) = $$size[1] * $$aspect[0] * $w;
    my($a_y) = $$size[0] * $$aspect[1] * $h;
    $$in{'clip_aspect'} = [$a_x,$a_y];
  } else {
    print "  no clipping required!\n";
  }
}

# --- calc_frame_height -------------------------------------------------------
sub calc_frame_height {
  my($width,$ratio,$modulo) = @_;
  my($blocks,$height);
  my($h1,$r1,$d1,$h2,$r2,$d2);

  # calc real height with aspect
  $height = $width / $ratio;
  # round to nearest modulo value
  $blocks = int($height / $modulo);
  $h1 = $blocks * $modulo;
  $h2 = ($blocks + 1) * $modulo;
  $r1 = $width / $h1;
  $r2 = $width / $h2;
  $d1 = abs($r1-$ratio)/$ratio;
  $d2 = abs($r2-$ratio)/$ratio;
  # check both ratio errors and chose better one
  if($d1 < $d2) {
    return ($h1,$d1);
  } else {
    return ($h2,$d2);
  }
}

# --- enum_frames -------------------------------------------------------------
sub enum_frames {
  my($opt)   = shift;
  my($ratio) = shift;

  print "  enumerate suitable frame sizes...\n";
  my($w,$h,$d);
  my(@res);
  my($mod) = $$opt{'frame_mod'};
  my($max) = $$opt{'width_max'};
  my($min) = $$opt{'width_min'};
  my($asrerr) = $$opt{'asrerr_max'};

  # loop over all possible widths
  print "  from $min to $max, modulo $mod\n";
  for($w=$min;$w<=$max;$w+=$mod) {
    ($h,$d) = calc_frame_height($w,$ratio,$mod);
    # convert error into percent
    $d *= 100;
    # skip resolution if error is too large
    printf "    %dx%d  %6.4g%%",$w,$h,$d;
    if($d>$asrerr) {
      print "\n";
    } else {
      print " **\n";
      push(@res,"$w,$h");
    }
  }

  # no width found
  if(scalar @res == 0) {
    print "ERROR: No suitable resolutions found! (increase asr error?)\n";
    exit(1);
  }
  return @res;
}

# --- automatically determine size of display with square pixels --------------
sub find_output_size {
  my($in)  = shift;
  my($out) = shift;
  my($opt) = shift;

  print "Searching suitable output size for square pixels display...\n";

  # first get aspect for target
  my($aspect);
  if(defined($$in{'clip_aspect'})) {
    $aspect = $$in{'clip_aspect'};
  } else {
    $aspect = $$in{'aspect'};
  }

  # --- determine output frame size ---
  my(@nres);
  my($size) = $$opt{'size_force'};
  my($w,$h);
  $w = $$size[0];
  $h = $$size[1];
  if(($w!=0)&&($h!=0)) {
    print "  using forced size ${w}x$h\n";
    push @res,"$w,$h";
  }
  else {
    # --- enumerate possible target frames ---
    # enum possible target sizes, but smaller or equal than source
    my(@eres) = enum_frames($opt,$$aspect[0]/$$aspect[1]);

    # pick by width
    if($w!=0) {
      foreach(@eres) {
	($ew,$eh) = split(/,/);
	if($w==$ew) {
	  push @res,$_;
	  print "  picked by given width: $_\n";
	}
      }
    }
    # pick by height
    elsif($h!=0) {
      foreach(@eres) {
	($ew,$eh) = split(/,/);
	if($h==$eh) {
	  push @res,$_;
	  print "  picked by given height: $_\n";
	}
      }
    }
    # otherwise use enumerated sizes
    @res = @eres if(scalar @res == 0);
  }

  # --- find an optimal number of CDs ---
  print "Find optimal number of CDs for encoding...\n";

  my($cd_num)     = $$opt{'cd_force'};
  my($vid_rate)   = $$out{'vid_rate'};

  # force number of CDs and only a single resolution available
  if(($cd_num!=0)&&(scalar @res == 1)) {
    ($w,$h) = split(/,/,$res[0]);
    print "  using forced #CDs: $cd_num and size ${w}x$h\n";
  }
  # search
  else {
    my($bppmin)     = $$opt{'bpp_min'};
    my($bppmax)     = $$opt{'bpp_max'};
    my($frame_bits) = $$out{'frame_bits'};
    my($cd_max)     = $$opt{'cd_max'};
    my($cd_begin)   = 0;
    my($cd_end)     = $cd_max;

    # shrink search region if a cd number is forced
    if($cd_num!=0) {
      $cd_begin = $cd_end = $cd_num-1;
    }

    # init hit array for each cd
    print "  table: frame size x #CDs in bpp (avg. bits per pixel)\n";
    print "        #CD:  ";
    my(@found);
    for($cd=0;$cd<$cd_max;$cd++) {
      printf "     %02d   ",$cd+1;
      push @found,[];
    }
    print "\n";
    print "     vrates:  ";
    for($cd=0;$cd<$cd_max;$cd++) {
      printf "   %4d   ",$$vid_rate[$cd];
      push @found,[];
    }
    print "\n";

    # --- loop through all sizes ---
    my($total)=0;
    foreach(@res) {
      ($w,$h) = split /,/;
      print sprintf("    %dx%d:   ",$w,$h);
      my($size) = $w * $h;
      my($cd);
      for($cd=0;$cd<$cd_max;$cd++) {
	my($bpp) = $$frame_bits[$cd] / $size;
	print sprintf("%6.3g",$bpp);

	# check only in region
	if(($cd>=$cd_begin)&&($cd<=$cd_end)) {
	  # is in bpp interval?
	  if(($bppmin<=$bpp)&&($bpp<=$bppmax)) {
	    # bpp fits in range
	    my($tab) = $found[$cd];
	    push @$tab,"$w,$h";
	    $total++;
	    print "**  ";
	  } else {
	    print "    ";
	  }
	} else {
	  print "--  ";
	}
      }
      print "\n";
    }
    if($total==0) {
      print "ERROR: found NO suitable resolution! (use -g and/or -c)\n";
      exit(1);
    }
    print "  found $total suitable resolution(s)\n";

    # --- find lowest number of CDs and largest size ---
    for($cd=0;$cd<$cd_max;$cd++) {
      my($tab) = $found[$cd];
      my($len) = scalar @$tab;
      if($len > 0) {
	$cd_num = $cd+1;
	($w,$h) = split(/,/,$$tab[$len-1]);
	last;
      }
    }
  }

  # store all values
  my($vrate) = $$vid_rate[$cd_num-1];
  print "  using #$cd_num CD, vrate: $vrate kbps, frame size: ${w}x$h\n";
  $$out{'cd_num'} = $cd_num;
  $$out{'size'}   = [$w,$h];
  $$out{'aspect'} = $aspect;
}

# --- find_num_cd -------------------------------------------------------------
sub find_num_cd {
  my($out)  = shift;
  my($opt)  = shift;
  my($rate) = shift; # given rate
  my($vbr)  = shift; # is vbr mode?

  print "Determining number of CDs for given bitrate...\n";
  printf "  %s output vrate:   $rate kbps\n",($vbr)?'max':'fixed';

  my($cd_max) = $$opt{'cd_max'};
  my($cd_num) = $$opt{'cd_force'};
  my($vrate) = $$out{'vid_rate'};
  print "  avg. vrate per #cd: " . join(', ',@$vrate);

  # force cd number
  if($cd_num>0) {
    print "  forced -> #$cd_num\n";
    $$out{'cd_num'} = $cd_num;
  }
  # search for cd number
  else {
    my($cd);
    # vbr: pick the largest number of cds where cd rate <= max rate
    if($vbr) {
      for($cd=$cd_max;$cd>=1;$cd--) {
	if($$vrate[$cd-1]<=$rate) {
	  $$out{'cd_num'} = $cd;
	  last;
	}
      }
      $$out{'cd_num'} = 1 if($cd==0);
    }
    # cbr: pick the smalles number of cds where cd rate >= fixed rate
    else {
      for($cd=1;$cd<=$cd_max;$cd++) {
	if($$vrate[$cd-1]>=$rate) {
	  $$out{'cd_num'} = $cd;
	  last;
	}
      }
      $$out{'cd_num'} = $cd_max if($cd==$cd_max+1);
    }
    print " -> #$$out{'cd_num'}\n";
  }

  # adjust vrates
}

# --- shrink_input ------------------------------------------------------------
# enlarge the current clipping region of input by sx and sy
# center the resulting frame
sub shrink_input {
  my($in) = shift;
  my($sx) = shift;
  my($sy) = shift;

  my($size);
  if(defined($$in{'clip_size'})) {
    $size = $$in{'clip_size'};
  } else {
    $size = $$in{'size'};
  }

  # reduce size
  my($in_w) = $$size[0];
  my($in_h) = $$size[1];
  $in_w -= $sx;
  $in_h -= $sx;
	
  # modify input size and aspect
  $$in{'clip_size'}   = [$in_w,$in_h];
  $$in{'clip_aspect'} = [$in_w,$in_h];

  # now change clip
  my($x) = int($sx/2);
  my($y) = int($sy/2);
  if(defined($$in{'clip'})) {
    my($t,$l,$b,$r);
    my($clip) = $$in{'clip'};
    ($t,$l,$b,$r) = @$clip;
    $t += $y;
    $b += $sy - $y;
    $l += $x;
    $r += $sx - $x;
    $$in{'clip'}=[$t,$l,$b,$r];
  } else {
    $$in{'clip'}=[$y,$x,$sy-$y,$sx-$x];
  }
}

# --- find_frame_trafo --------------------------------------------------------
# given: input frame size, aspect and output frame size, aspect
# find: place input frame with correct aspect into output frame
#       add letterbox if necessary
sub find_frame_trafo {
  my($in)  = shift;
  my($out) = shift;
  my($opt) = shift;

  print "Finding frame transformation...\n";

  # --- fetch parameters ------------------------------------------------------
  my($in_asr,$in_size);
  # use clipped size
  if(defined($$in{'clip_aspect'})) {
    $in_asr  = $$in{'clip_aspect'};
    $in_size = $$in{'clip_size'};
  } else {
    $in_asr  = $$in{'aspect'};
    $in_size = $$in{'size'};
  }
  my($out_asr)  = $$out{'aspect'};
  my($out_size) = $$out{'size'};
  my($in_w)   = $$in_size[0];
  my($in_h)   = $$in_size[1];
  my($out_w)  = $$out_size[0];
  my($out_h)  = $$out_size[1];
  my($in_ax)  = $$in_asr[0];
  my($in_ay)  = $$in_asr[1];
  my($out_ax) = $$out_asr[0];
  my($out_ay) = $$out_asr[1];
  my($do_lb)  = $$out{'letterbox'};
  # pixel tolerance: we may shrink input in each direction to use fast rescale
  my($tol) = $$opt{'scl_tol'};

  # --- verbose parameter ---
  my($in_aspect)  = $in_ax  / $in_ay;
  my($out_aspect) = $out_ax / $out_ay;
  printf "  input:   %3dx%3d    asr: %3.2g:1\n",$in_w,$in_h,$in_aspect;
  printf "  output:  %3dx%3d    asr: %3.2g:1\n",$out_w,$out_h,$out_aspect;

  # --- adjust target height if we need aspect correction ---------------------
  my($out_rh) = $out_h;

  # if input and output aspects only differ < asrerr_max -> same aspect
  # OLD: if($in_ax * $out_ay == $in_ay * $out_ax) {
  if(abs($in_aspect-$out_aspect)<($$opt{'asrerr_max'}*0.01)) {
    print "  output height:   $out_rh (same aspect)\n";
  } elsif($do_lb) {
    # real output frame width (when assuming square pixels)
    my($out_rw) = $out_ax * $out_h / $out_ay;
    # real output frame height when assuming input aspect
    $out_rh = $in_ay * $out_rw / $in_ax;
    # round to output modulo
    my($frame_mod) = $$opt{'frame_mod'};
    $out_rh = int($out_rh / $frame_mod) * $frame_mod;
    print "  output height:   $out_rh (aspect correction)\n";
    if($out_rh>$out_h) {
      print "  WARNING: asr height > output height -> clipped\n";
      $out_rh = $out_h;
    }
  }

  # --- check scale mode ------------------------------------------------------
  # 0=nothing  1=only shrink  2=only enlarge  3=shr x enl y  4=enl x shr y
  my($diff_x) = $out_w  - $in_w;
  my($diff_y) = $out_rh - $in_h;
  my($mode);
  if(($diff_x==0)&&($diff_y==0)) {
    $mode = 0;
  } else {
    my($sign) = $diff_x * $diff_y;
    if($sign < 0) {
      # both: shrink and enlarge
      if($diff_x<0) {
	$mode=3;
      } else {
	$mode=4;
      }
    } else {
      if(($diff_x>0)||($diff_y>0)) {
	$mode=2;
      } else {
	$mode=1;
      }
    }
  }
  my(@smode) = ("same size","only shrink","only enlarge",
		"x-shrink y-enlarge","x-enlarge y-shrink");
  print "  scale mode:      $smode[$mode]\n";

  # --- find modulo for scale mode --------------------------------------------
  my($modulo);
  if($mode==0) {
    # no scaling required
    $modulo=0;
  } else {
    $modulo=1;
    # check valid modulos for fast operation
    foreach(32,16,8) {
      my($mod_x) = abs($diff_x) % $_;
      my($mod_y) = abs($diff_y) % $_;
      # accept parameter in tolerance range
      if(($mod_x<=$tol)&&($mod_y<=$tol)) {
	
	# check resulting image size
	my($new_w) = $in_w + int($diff_x / $_) * $_;
	my($new_h) = $in_h + int($diff_y / $_) * $_;
	# new image size fullfills modulo
	if(($new_w % $_ == 0)&&($new_h % $_ == 0)) {

	  # we need to change image
	  if(($mod_x>0)||($mod_y>0)) {
	    # change input image!!!
	    shrink_input($in,$mod_x,$mod_y);
	    print "  (reduced input size by $mod_x,$mod_y -> modulo $_)\n";
	    # correct differences between input and output frame size
	    $diff_x += ($diff_x>0)?$mod_x:-$mod_x;
	    $diff_y += ($diff_y>0)?$mod_y:-$mod_y;
	  }
	  $modulo = $_;
	  last;
	}
      }
    }
  }
  # verbose
  if($modulo>0) {
    print "  scale modulo:    $modulo = " . ($modulo>1?'fast':'slow')."\n";
  }

  # --- first add clip operation ----------------------------------------------
  $opt = "";
  if(defined($$in{'clip'})) {
    my($clip) = $$in{'clip'};
    $opt = "-j " . join(',',@$clip) . " ";
  }

  # --- find tc command for scale operation -----------------------------------
  # now perform scale with tc
  if($modulo==0) {
    # nothing to do
  }
  elsif($modulo==1) {
    $opt = "-Z ${out_w}x$out_rh ";
  }
  else {
    my($num_x) = int($diff_x / $modulo);
    my($num_y) = int($diff_y / $modulo);
    # shrink component
    if(($num_x<0)||($num_y<0)) {
      $opt .= "-B "
	. ($num_y<0?abs($num_y):'0') . ","
	. ($num_x<0?abs($num_x):'0') . ",$modulo ";
    }
    # enlarge component
    if(($num_x>0)||($num_y>0)) {
      $opt .= "-X "
	. ($num_y>0?$num_y:'0') . ","
	. ($num_x>0?$num_x:'0') . ",$modulo ";
    }
  }

  # --- add letterbox ---------------------------------------------------------
  my($blackbar) = $out_h - $out_rh;
  if($blackbar > 0) {
    my($top) = $blackbar/2;
    my($bot) = $blackbar - $top;
    print "  add letterbox:   top=$top, bottom=$bot\n";
    $opt .= "-Y -$top,0,-$bot,0 ";
  }

  # store final frame transformation for transcode
  print "  transcode param: $opt\n";
  $$out{'tc_trafo'} = $opt;
}

# --- find all relevant output values -----------------------------------------
sub prepare_output {
  my($in)  = shift;
  my($out) = shift;
  my($opt) = shift;
  my($flavor) = $$opt{'flavor'};

  # ----- name -----
  # get name for target
  my($name) = $$opt{'input'};
  $name =~ s,.*/([^/]+)$,$1,;
  $name =~ s,\.[^.]+$,,;
  $name = "output" if($name eq "");
  $$out{'name'} = $name;

  # ----- audio setup -----
  # check if all requested tracks are available in input
  my($aud_track)     = $$opt{'aud_track'};
  my($aud_rate)      = $$opt{'aud_rate'};
  my($aud_samp_rate) = $$opt{'aud_samp_rate'};
  my($aud_chan)      = $$opt{'aud_chan'};
  my($inp_track)     = $$in{'aud_total'};
  my($aud_num) = 0;
  foreach(@$aud_track) {
    if($_ >= $inp_track) {
      print "ERROR: requested audio track $_ is not available in input!\n";
      exit(1);
    }
    $aud_num++;
  }

  # check vcd requirements
  if($flavor eq 'vcd') {
    if($aud_num>1) {
      print "ERROR: VCDs only support a single audio track!\n";
      exit(1);
    }
  } elsif($flavor eq 'svcd') {
    if($aud_num>2) {
      print "ERROR: SVCDs only support 1 or 2 audio tracks!\n";
      exit(1);
    }
  }

  # calc total audio output rate
  my($aud_sum_rate) = 0;
  foreach(@$aud_rate) {
    $aud_sum_rate += $_;
  }
  $$out{'aud_sum_rate'} = $aud_sum_rate;

  # ----- bitrate calcing -----------------------------------------
  # --- bits on a cd ---
  # MB available on a CD
  my($cd_size) = int(($$opt{'cd_size'} - $cd_reserve{$flavor})
		     * $cd_raw_scale{$flavor} * $$opt{'cd_frac'});
  if($cd_size<1) {
    print "ERROR: no space on CD left ($cd_size MB!)\n";
    exit(1);
  }
  $$out{'cd_size'} = $cd_size;
  # Bits available on a CD
  my($cd_bits) = $cd_size * 1024 * 1024 * 8;
  $$out{'cd_bits'} = $cd_bits;
  # av rate on a CD in kbps
  my($cd_rate) = int(($cd_bits * $$in{'fps'} / ($$in{'frames'} * 1000))-0.5);
  print "CD parameters....\n";
  printf "  space for movie:    %4u MB = $cd_bits bits\n",$cd_size;
  printf "  total audio rate:   %4u kbps\n",$aud_sum_rate;
  printf "  required mux rate:  %4u kbps\n",$mux_add_rate{$flavor};

  # fixed add on to video rate
  my($fix_rate) = $aud_sum_rate + $mux_add_rate{$flavor};

  # --- video bitrate for each cd num ---
  my(@vrate,@fbits);
  my($max_vrate) = $$opt{'max_vrate'};
  for($i=0;$i<$$opt{'cd_max'};$i++) {
    $j = $i+1;
    my($vid_rate) = int(($j*$cd_rate - $fix_rate)-0.5);
    if($vid_rate<100) {
      print "ERROR: video bitrate $vid_rate kbps too low!\n";
      exit(1);
    }
    if($vid_rate>$max_vrate) {
      if($j < $$opt{'cd_max'}) {
	print "  restricting max CDs to $j because vrate already $vid_rate!\n";
	$$opt{'cd_max'} = $j;
      }
    }
    my($frame_bits) = int(($vid_rate * 1000 / $input{'fps'})-0.5);
    printf "  %u cd video rate:    %4u kbps   %6u bits per frame\n",
      $j,$vid_rate,$frame_bits;
    push @vrate,$vid_rate;
    push @fbits,$frame_bits;
  }
  $$out{'vid_rate'} = \@vrate;
  $$out{'frame_bits'} = \@fbits;

  # --- find size, aspect and num_cd -------------------------------

  # --- start with clipping ---
  # try to find a reasonable clip region
  find_clip(\%input,\%options);

  # --- frc -> tv_norm ---
  # automatic detect from frc code:
  my($frc)=$$in{'frc'};
  my(@pal) =(3);
  my(@ntsc)=(1,4);
  my($found) = 0;
  my($norm);
  foreach(@pal) {
    if($_ eq $frc) {
      $found=1;
      $norm = 'pal';
    }
  }
  foreach(@ntsc) {
    if($_ eq $frc) {
      $found=1;
      $norm = 'ntsc';
    }
  }
  # -n option is given
  if($$opt{'tv_norm'} ne 'none') {
    if($found) {
      # uh, oh conflict
      if($norm ne $$opt{'tv_norm'}) {
	printf "  WARNING: given tv norm does not support fps of input!!!\n";
      }
    }
    $norm = $$opt{'tv_norm'};
  } 
  # use autodetection
  else {
    if(!$found) {
      print "ERROR: input frame rate is not supported!\n";
      print "  use -n to force tv norm (may lead to audio/video asynch)!\n";
      exit(1);
    }
  }
  # use user specific norm
  $$out{'tv_norm'} = $norm;

  # --- anamorph encoding possible? ---
  my($anamorph) = $$opt{'anamorph'};
  if($anamorph==-1) {
    # autodetect
    my($in_asr) = $$in{'aspect'};
    $in_asr = $$in_asr[0] / $$in_asr[1];
    if($in_asr > 1.7) {
      $anamorph = 1;
    } else {
      $anamorph = 0;
    }
  }
  $$out{'anamorph'} = $anamorph;

  # --- flavor setup -----------------------------------------------
  my($tv_norm) = $$out{'tv_norm'};
  my($fixed_vrate) = 0;
  # --- svcd ---
  # set size, aspect, vrate, enable letterboxing
  if($flavor eq 'svcd') {
    # set rate (max rate from www.vcdhelp.com)
    $max_vrate = 2748 - $$out{'aud_sum_rate'};

    # size + aspect
    if($anamorph==1) {
      $$out{'aspect'} = [16,9];
    } else {
      $$out{'aspect'} = [4,3];
    }
    if($tv_norm eq 'pal') {
      $$out{'size'} = [480,576];
    } else {
      $$out{'size'} = [480,480];
    }

    # find #CDs
    find_num_cd(\%output,\%options,$max_vrate,1);
    $$out{'letterbox'} = 1;
  }
  # --- vcd ---
  # set size, aspect, vrate, enable letterboxing
  elsif($flavor eq 'vcd') {
    # set rate (fixed rate from www.vcdhelp.com)
    $max_vrate = 1150;
    $fixed_vrate = 1;

    # size + aspect
    if($anamorph==1) {
      $$out{'aspect'} = [16,9];
    } else {
      $$out{'aspect'} = [4,3];
    }
    if($tv_norm eq 'pal') {
      $$out{'size'} = [352,288];
    } else {
      $$out{'size'} = [352,240];
    }
    # find for fixed rate
    find_num_cd(\%output,\%options,$max_vrate,0);
    $$out{'letterbox'} = 1;
  }
  # avi:
  # find size with same aspect, no letterboxing
  elsif($flavor eq 'avi') {
    find_output_size(\%input,\%output,\%options);
    $$out{'letterbox'} = 0;
  }

  # ----- calc video rate per cd -----------------------------------
  print "Calculating videorate for each CD...\n";
  my(@cd_vrate);
  # in dvd mode use chaplin to split the discs into chapter sets
  if($$in{'codec'}eq'dvd') {

    # use *chaplin* on dvd input
    print "  calling chaplin...\n";
    my($title) = $$opt{'dvd_title'};
    my($xopt) = "";
    if(($flavor eq 'svcd')||($flavor eq 'vcd')) {
      my($n) = $$out{'name'};
      $xopt = sprintf("-x \"$n,%s,$n-$flavor%%d.xml,$n-$flavor%%d.mpg\"",
		      ($flavor eq 'svcd')?'svcd':'vcd2');
    }
    my($parts) = "-p $$out{'cd_num'}";
    my($cmd)="chaplin -d \"$$opt{'input'}\" -t $title $xopt $parts";
    my(@result) = `$cmd 2> /dev/null`;

    # eval chaplin output
    my(@cd_chaps);
    my($max_chap)=0;
    my($total_frames)=0;
    foreach(@result) {
      if(m/^part.*chapters (\d+)-(\d+).*frames.*\+(\d+)/) {
	push @cd_chaps,"$1-$2";
	print "  chapters $1-$2: $3 frames\n";

	# recalc vrate for chapter set
	my($frames) = $3;
	my($cd_rate) = $cd_bits * $$in{'fps'} / ($frames * 1000);
	my($vr) = int($cd_rate - $fix_rate - 0.5);
	push @cd_vrate,$vr;

	$max_chap = $2 if($2 > $max_chap);
	$total_frames += $frames;

	# recheck size
	my($trate) = $vr + $fix_rate;
	my($ncd_size) = int(0.5 + ($trate * $frames * 1000 /
	  ($$in{'fps'} * 8 * 1024 * 1024)));

	printf "    video rate:   %4u kbps\n",$vr;
	printf "    total rate:   %4u kbps\n",$trate;
	printf "    cd size:      %4u MB\n",$ncd_size;
      }
    }
    if($max_chap==0) {
      print "ERROR: call to chaplin failed!! (not installed?)\n";
      exit(1);
    }
    $$out{'cd_chaps'} = \@cd_chaps;
    $$out{'cd_maxchap'} = $max_chap;
    print "  total frames:   $total_frames ($$in{'frames'} reported)\n";
  }
  # same vrate on each disc
  else {
    my($cd);
    my($cd_num) = $$out{'cd_num'};
    for($cd=0;$cd<$cd_num;$cd++) {
      push @cd_vrate,$vrate[$cd_num-1];
    }
  }
  # check proposed vrates
  foreach(@cd_vrate) {
    if($fixed_vrate) {
      print "  forcing fixed vrate $max_vrate (was $_)\n";
      $_ = $max_vrate;
    }
    if($_ > $max_vrate) {
      print "  clamping vrate $_ -> max_vrate $max_vrate\n";
      $_ = $max_vrate;
    }
  }
  $$out{'cd_vrate'} = \@cd_vrate;

  # --- verbose - now we have in output: size, aspect, cd_num, vid_rate -------
  print "Output parameter summary:\n";

  # video parameter
  print "  tv norm: $$out{'tv_norm'}\n";
  my($size)   = $$out{'size'};
  my($aspect) = $$out{'aspect'};
  $aspect = $$aspect[0]/$$aspect[1];
  print "  cds:     $$out{'cd_num'}\n";
  printf "  size:    %dx%d    aspect: %3.2g:1",$$size[0],$$size[1],$aspect;
  print " (anamorph)" if($anamorph);
  print "\n";
  my($cd_num) = $$out{'cd_num'};
  my($vrate)  = $$out{'cd_vrate'};
  print "  video:   " . join(', ',@$vrate)." kbps\n";

  # audio parameter
  my($arate)  = $$out{'aud_sum_rate'};
  printf "  audio:  %5u kbps (total)\n",$aud_sum_rate;
  $aud_num = 0;
  foreach(@$aud_track) {
    printf "  audio%u: %5u kbps  %5u kHz  %u chan <- src #%u\n",
      $aud_num,
	$$aud_rate[$aud_num],
	  $$aud_samp_rate[$aud_num],
	    $$aud_chan[$aud_num],$_;
    $aud_num++;
  }

  # --- determine frame transformation ---
  # adds tc_trafo entry
  find_frame_trafo(\%input,\%output,\%options);
}

# ========== output ===========================================================
#
# --- generate_make -----------------------------------------------------------
#
sub generate_make {
  my($in)  = shift;
  my($out) = shift;
  my($opt) = shift;
  my($i,$j);

  print "Generating makefile...\n";

  # --- get flavor and codec ---
  my($flavor) = $$opt{'flavor'};
  my($codec)  = $$opt{'codec'};
  print "  flavor=$flavor  codec=$codec\n";

  # --- create makefile ---
  my($name) = $$out{'name'} . '-' . $flavor;
  my($mkfile) = "$name.mak";
  print "  writing rip makefile '$mkfile'\n";
  open(FH,">$mkfile") || die;
  print FH <<EOF;
# rip *makefile*: $mkfile
# generated by ripmake 
# written by Christian Vogelgsang <Vogelgsang\@cs.fau.de>

# --- config ---
EOF

  ################################ VARIABLES ##################################
  # ---- input ----
  my($icodec) = $$in{'codec'};
  my($input) = $$opt{'input'};
  print FH "# input\n";
  # a video/audio input filter
  print FH "INPUTV = -i \"$input\" -x $icodec,null\n";
  # audio only input filter
  my($iacodec) = $icodec;
  $iacodec = 'auto' if($icodec eq 'divx');
  print FH "INPUTA = -i \"$input\" -x null,$iacodec\n";
  print FH "INPUTAV= -i \"$input\" -x $icodec\n";

  # ---- dvd input extras: chapter sets ----
  my($cd_num)= $$out{'cd_num'};
  if($icodec eq 'dvd') {
    my($cd_chaps) = $$out{'cd_chaps'};
    print FH "\n# dvd\nTITLE=$$opt{'dvd_title'}\n";
    for($i=1;$i<=$cd_num;$i++) {
      my($j) = $i-1;
      print FH "CHAP$i=$$cd_chaps[$j]\n";
    }
    my($sample_chap) = $$out{'cd_maxchap'} / 2;
    $sample_chap = 1 if($sample_chap==0);
    print FH "CHAP_sample=$sample_chap\n";
  }

  # ---- video parameter ----
  my($vrate) = $$out{'vid_rate'};
  my($vkbps) = $$out{'cd_vrate'};
  my($trafo) = $$out{'tc_trafo'};
  my($range) = int($$in{'fps'}*60); # a minute sample
  print FH "\n# video param\nRANGE=0-$range\nTRAFO=$trafo\n";

  # DVD chapter mode
  if($icodec eq 'dvd') {
    for($i=1;$i<=$cd_num;$i++) {
      my($j) = $i-1;
      print FH "VRATE$i=$$vkbps[$j]\n";
    }
  } else {
    print FH "VRATE=$$vkbps[0]\n";
  }
  print FH "VRATE_sample=$$vkbps[0]\n";

  # ---- audio parameter ----
  my($aud_track) = $$opt{'aud_track'};
  my($aud_rate)= $$opt{'aud_rate'};
  my($aud_num) = scalar @$aud_track;
  my(@aud_isamp_rate);
  my(@aud_osamp_rate);
  if($aud_num>2) {
    print "WARNING: currently only 2 audio channels are supported!\n";
  }
  print FH "\n# audio param\n";
  for($i=0;$i<$aud_num;$i++) {
    print FH "ACHAN$i=$$aud_track[$i]\nARATE$i=$$aud_rate[$i]\n";
    my($iref) = $$in{'aud_samp_rate'};
    push @aud_isamp_rate,$$iref[$$aud_track[$i]];
    my($oref) = $$opt{'aud_samp_rate'};
    push @aud_osamp_rate,$$oref[$i];
  }

  # ---- output ----
  # avi
  print FH "\n# output\n";
  if($flavor eq 'avi') {
    # video only output
    print FH "OUTPUTV =-y $codec,null\n";
    # audio only output
    print FH "OUTPUTA =-y null,raw\n";
    # a/v output
    print FH "OUTPUTAV=-y $codec\n";
  }
  # svcd,vcd
  else {
    # video only output
    print FH "OUTPUTV =-y $codec,null\n";
    # audio only output
    print FH "OUTPUTA =-y null,toolame\n";
    # a/v output
    print FH "OUTPUTAV=-y $codec,toolame\n";
  }
  print FH "\n";

  # ---- codec parameters ----
  # in debug mode render only a short sample
  my($dbg) = '';
  $dbg = " -c 0-100" if($$opt{'debug'}>0);

  # audio setup
  print FH "# codec params\n";
  for($i=0;$i<$aud_num;$i++) {
    print FH "CODERA$i=-a \$(ACHAN$i) -b \$(ARATE$i)";
    if($aud_isamp_rate[$i]!=$aud_osamp_rate[$i]) {
      my($mod) = '';
      $mod = "-J resample" if ($flavor eq 'avi');
      print FH " -E $aud_osamp_rate[$i] --a52_dolby_off $mod";
    }
    print FH "$dbg\n";
  }

  # video setup
  print FH "CODERV=\$(TRAFO) -V $dbg -f $$in{'fps'},$$in{'frc'}\n\n";

  # ---- codec specific audio/video params ------------------------------------
  # pick a deinterlacer
  my($interlaced) = $$opt{'interlaced'};
  my($deinter) = '';
  $deinter = '-Jpp=lb' if($interlaced);

  # avi
  if($flavor eq 'avi') {
    my($extra)='';
    print FH "# avi\n";
    print FH "CODERV+=$deinter $extra\n" if(($deinter ne '')||($extra ne ''));
  }
  # svcd,vcd
  else {
    # video: mpeg2enc
    my($mode);
    if($codec eq 'mpeg2enc') {
      print FH "# mpeg2enc\n";
      my($extra)='';

      # add sequence headers every gop
      my($flags)="-s";

      # hq encoder settings 
      if($$opt{'high_quality'}) {
	$flags .= " -4 1 -2 1";
      }

      # auto split mode: calc overhead rate
      if($icodec ne 'dvd') {
	# calculate "overhead" rate -> audio + muxing
	my($add_rate) = $$out{'aud_sum_rate'} + $mux_add_rate{$flavor};
        $flags .= " -S $$out{'cd_size'} -B $add_rate" if($icodec ne 'dvd');
      }

      # special vcd stuff
      if($flavor eq 'vcd') {
	$mode=1;
	if($interlaced==1) {
	  $extra = $deinter; # use deinterlacer
	}
      }
      # special svcd stuff
      else { 
	$mode=5;
	if($interlaced==1) {
	  $flags = '-I1'; # encode interlaced directly
	}	
      }

      # output aspect code
      my($asr) = 2;
      $asr = 3 if($$out{'anamorph'}==1);

      # build coder options
      print FH "CODERV+=-F \"$mode,$flags\" --export_asr $asr $extra\n";
    }
    # video: mpeg - not supported very well ;)
    else {
      print FH "# mpeg\n";
      if($flavor eq 'vcd') { $mode='v'; }
      else { $mode='s'; }
      print FH "CODERV+=-F \"$mode\" --export_asr 2 $deinter\n";
    }
  }

  ######################### RULES #############################################
  print FH "\n# --- rules ---\n";

  # ----- mode flags: do_split, do_master, do_mux, do_audio0 ------------------
  # do_split - need to split files?
  my($do_split) = 0;
  $do_split = 1 if(($icodec ne 'dvd') && ($cd_num>1) && ($flavor eq 'avi'));
  # do master - call vcdimager?
  my($do_master) = 0;
  $do_master = 1 if(($flavor eq 'vcd')||($flavor eq 'svcd'));
  # do_mux - need multiplexing
  my($do_mux) = 0;
  $do_mux = 1 if($do_master || (($flavor eq 'avi')&&($aud_num>1)));
  # do_audio0 - need separate handling of first audio track
  my($do_audio0) = 0;
  $do_audio0 = $do_master;

  # ----- SAMPLE RULE ---------------------------------------------------------
  print FH "# default: render a short sample with audio track 0\n"
   . "sample: video_sample";
  for($j=0;$j<$aud_num;$j++) {
    next if(($j == 0)&&(!$do_audio0));
    print FH " audio_sample-a$j";
  }
  print FH " mux_sample" if($do_mux);
  print FH "\n\t\@echo \"--- sample ready ---\"\n";

  my($title) = '';
  $title = "-T \$(TITLE),\$(CHAP_sample)" if($icodec eq 'dvd');
  write_video_rule('_sample',$title,$flavor,$name,"-c \$(RANGE)");
  # audio rules
  for($j=0;$j<$aud_num;$j++) {
    next if(($j == 0)&&(!$do_audio0));
    write_audio_rule('_sample',$title,$flavor,$name,"-c \$(RANGE)",$j);
  }
  write_mux_rule('_sample',$flavor,$name,0,$aud_num) if($do_mux);

  # ----- MAIN RIP RULE  ------------------------------------------------------
  print FH "\n# --- main rip rule ---\n";
  print FH "rip: ";

  # ---------- PER DISC DVD MODE ----------
  if($icodec eq 'dvd') {
    # main rule: render each cd
    for($i=1;$i<=$cd_num;$i++) {
      print FH "cd$i ";
    }
    print FH "\n\t\@echo \"--- disc rip ready ---\"\n";

    # foreach cd a rule set
    for($i=1;$i<=$cd_num;$i++) {
      print FH "\n# rip cd #$i\ncd$i: video$i";
      for($j=0;$j<$aud_num;$j++) {
	next if(($j == 0)&&(!$do_audio0));
	print FH " audio$i-a$j";
      }
      print FH " mux$i" if($do_mux);
      print FH " master$i" if($do_master);
      print FH "\n";

      # video rule
      my($title) = "-T $$opt{'dvd_title'},\$(CHAP$i)";
      write_video_rule($i,$title,$flavor,$name,$dbg);

      # audio rules
      for($j=0;$j<$aud_num;$j++) {
	next if(($j == 0)&&(!$do_audio0));
	write_audio_rule($i,$title,$flavor,$name,$dbg,$j);
      }
      # mux & master
      write_mux_rule($i,$flavor,$name,0,$aud_num) if($do_mux);
      write_mastering_rule($flavor,$name,$i,1) if($do_master);
    }
  }
  # ---------- WHOLE MOVIE MODE ----------
  else {
    # main rule: video and audio, mux, split, master
    print FH "video";
    for($j=0;$j<$aud_num;$j++) {
      next if(($j == 0)&&(!$do_audio0));
      print FH " audio-a$j";
    }
    print FH " mux" if($do_mux);
    print FH " split" if($do_split);
    if($do_master) {
      if($cd_num>1) {
	my($i);
	for($i=1;$i<=$cd_num;$i++) {
	  print FH " master$i";
	}
      } else {
	print FH " master";
      }
    }
    print FH "\n\t\@echo \"--- movie rip ready ---\"\n";

    # video rule
    my($title) = '';
    $title = "-T $$opt{'dvd_title'}" if($icodec eq 'dvd');
    write_video_rule('',$title,$flavor,$name,$dbg);

    # audio rule
    for($j=0;$j<$aud_num;$j++) {
      next if(($j == 0)&&(!$do_audio0));
      write_audio_rule('',$title,$flavor,$name,$dbg,$j);
    }

    # mux rule
    write_mux_rule('',$flavor,$name,$cd_num>1,$aud_num) if($do_mux);

    # --- split avi ---
    if($do_split) {
      print FH "\nsplit:\n";
      print FH "\tavisplit -i \"$name-a0.avi\" -s $$out{'cd_size'}"
	."\\\n\t\t-o \"$name\"";
      print FH "\n";
    }

    # --- mastering ---
    if($do_master) {
      my($i);
      if($cd_num>1) {
	for($i=1;$i<=$cd_num;$i++) {
	  write_mastering_rule($flavor,$name,$i,0);
	}
      } else {
	write_mastering_rule($flavor,$name,'',0);
      }
    }
  }
  close(FH);
}

# --- write a rule for generating a movie with first audio track --------------
# num,title_cmd,flavor
sub write_video_rule {
  my($id) = shift;
  my($title) = shift;
  my($flavor) = shift;
  my($name) = shift;
  my($extra) = shift;
  print FH "\nvideo$id: ";

  $name .= $id;

  # ----- avi - this rule also does first audio track
  if($flavor eq 'avi') {
    # do two pass!
    print FH "video${id}_pass1 video${id}_pass2\n\n";

    # --- pass1 : video analyse + audio 0 scale
    print FH "video${id}_pass1:\n\t\@echo \"--- video $id pass 1 ---\"\n";
    print FH "\ttranscode $title \$(INPUTAV) \$(OUTPUTV)\\\n"
      . "\t\t\$(CODERV) \$(CODERA0)\\\n"
      . "\t\t-J astat=\"${name}-a0.scl\"\\\n"
      . "\t\t-w \$(VRATE$id) $extra\\\n"
      . "\t\t-R \"1,${name}-divx.log,${name}-pcm.log\"\n";

    # --- pass2 : video render + audio 0 render
    print FH "\nvideo${id}_pass2:\n\t\@echo \"--- video $id pass 2 ---\"\n";
    print FH "\ttranscode $title \$(INPUTAV) \$(OUTPUTAV)\\\n"
      . "\t\t\$(CODERV) \$(CODERA0)\\\n"
      . "\t\t-o \"$name-a0.avi\" -w \$(VRATE$id) $extra\\\n"
      . "\t\t-R \"2,${name}-divx.log,${name}-pcm.log\"\\\n"
      . "\t\t-s `cat \"${name}-a0.scl\"`\n";
  }
  # ----- svcd,vcd
  else {
    print FH "\n\t\@echo \"--- video $id ---\"\n";
    print FH "\ttranscode $title \$(INPUTAV) \$(OUTPUTV)\\\n"
      . "\t\t\$(CODERV) \$(CODERA0)\\\n"
      . "\t\t-o \"$name\"\\\n"
      . "\t\t-w \$(VRATE$id) $extra\\\n"
      . "\t\t-J astat=\"${name}-a0.scl\"\n";
  }
}

# --- write audio track generation rule ---------------------------------------
# num,title_cmd,flavor,name
sub write_audio_rule {
  my($id) = shift;
  my($title_cmd) = shift;
  my($flavor) = shift;
  my($name) = shift;
  my($extra) = shift;
  my($chan) = shift;

  $name .= $id;

  # first fetch the audio scale value if this is not the first track
  if($chan>0) {
    print FH "\naudio$id-a${chan}_scl:\n"
      . "\t\@echo \"--- audio $id ch$chan scale value ---\"\n"
	. "\ttranscode $title_cmd \$(INPUTA) \\\n"
	  . "\t\t\$(CODERA$chan) $extra\\\n"
	    . "\t\t-J astat=\"$name-a$chan.scl\"\n";
  }

  print FH "\naudio$id-a$chan:";
  print FH " audio$id-a${chan}_scl" if($chan>0);
  print FH "\n\t\@echo \"--- audio $id ch$chan ---\"\n";

  # ----- avi
  if($flavor eq 'avi') {
    # we handled track 0 already in video!
    if($chan > 0) {
      print FH "\ttranscode $title_cmd \$(INPUTA) -g 0x0 -y raw -u 50\\\n"
	. "\t\t\$(CODERA$chan) $extra\\\n"
	  . "\t\t-o $name-a$chan.avi\\\n"
	    . "\t\t-s `cat \"$name-a$chan.scl\"`\n";
    }
  }
  # ----- svcd,vcd
  else {
    # svcd,vcd
    print FH "\ttranscode $title_cmd \$(INPUTA) \$(OUTPUTA)\\\n"
      . "\t\t\$(CODERA$chan) $extra\\\n"
      . "\t\t-m \"$name-a$chan\"\\\n"
      . "\t\t-s `cat \"$name-a$chan.scl\"`\n";
  }
}

# --- write muxer rule --------------------------------------------------------
sub write_mux_rule {
  my($id) = shift;
  my($flavor) = shift;
  my($name) = shift;
  my($need_split) = shift;
  my($aud_num) = shift;

  $name .= $id;

  print FH "\nmux$id:\n\t\@echo \"--- mux $id ---\"\n";
  # ----- avi
  if($flavor eq 'avi') {
    if($aud_num>1) {
      if($aud_num>2) {
	print "WARNING: More than 2 audio tracks not supported in AVI!!";
      }
      # mux 2 tracks
      print FH "\tavimerge -i \"$name-a0.avi\"\\\n"
	. "\t\t-p \"$name-a1.avi\"\\\n"
	. "\t\t-o \"$name.avi\"\n";
    }
  } 
  # ----- svcd,vcd
  else {
    # svcd,vcd
    my($mode) = '1';
    $mode = '4' if($flavor eq 'svcd');
    my($ext) = 'm1v';
    $ext = 'm2v' if($flavor eq 'svcd');
    my($out) = $name;
    $out = "$name\%d" if($need_split);

    print FH "\tmplex -f $mode\\\n\t\t-o \"$out.mpg\"\\\n"
      . "\t\t\"$name.$ext\"\\\n";
    my($i);
    for($i=0;$i<$aud_num;$i++) {
      print FH "\t\t\"$name-a$i.mp2\"";
      print FH "\\" if ($i<$aud_num-1);
      print FH "\n";
    }
  }
}

# --- write master rule -------------------------------------------------------
sub write_mastering_rule {
  my($flavor) = shift;
  my($name)   = shift;
  my($id)     = shift;
  my($xb)     = shift;

  # part numbers starting from 1!
  my($out) = "-c \"$name$id.cue\"\\\n\t\t -b \"$name$id.bin\"";

  print FH "\nmaster$id:\n\t\@echo \"--- master $id ---\"\n";
  # currently only svcds can use xml...
  if($flavor eq 'svcd') {
    if($xb) {
      print FH "\tvcdxbuild $out\\\n\t\t \"${name}$id.xml\"\n";
    } else {
      my($opts) = "-t svcd --update-scan-offsets";
      print FH "\tvcdimager $opts $out\\\n\t\t \"$name$id.mpg\"\n";
    }
  } else {
    print FH "\tvcdimager $out\\\n\t\t \"$name$id.mpg\"\n";
  }
}
