ripmake-1.39/0000755000175000017500000000000010121313353014522 5ustar marillatmarillat00000000000000ripmake-1.39/ripmake-1.390000644000175000017500000032254710121313323016477 0ustar marillatmarillat00000000000000#!/usr/bin/perl -w # ripmake # # automatically configure transcode for various conversion tasks # # Written by Christian Vogelgsang # under the GNU Public License V2 # # --- setup presets ----------------------------------------------------------- # output flavors @flavors = ('avi','ogm','mkv','svcd','vcd','dvd'); # default codec for output flavor %def_vcodec = ( 'avi' => 'xvid', 'ogm' => 'xvid', 'mkv' => 'xvid', 'svcd' => 'mpeg2enc', 'vcd' => 'mpeg2enc', 'dvd' => 'mpeg2enc'); %def_acodec = ( 'avi' => 'raw', 'ogm' => 'ogg', 'mkv' => 'ogg', 'svcd' => 'toolame', 'vcd' => 'toolame', 'dvd' => 'toolame'); # map flavor to default audio rate (kbps) %def_aud_rate = ( 'avi' => 128, 'ogm' => 128, 'mkv' => 128, 'svcd' => 224, 'vcd' => 224, 'dvd' => 224 ); # map flavor to default audio sampling rate (Hz) %def_aud_samp_rate = ( 'avi' => 44100, 'ogm' => 44100, 'mkv' => 44100, 'svcd' => 44100, 'vcd' => 44100, 'dvd' => 48000 ); # map flavor to mux overhead bitrate (kbps) %mux_add_rate = ( 'avi' => 60, 'ogm' => 10, 'mkv' => 10, 'svcd' => 2, 'vcd' => 2, 'dvd' => 2); # how many MB reserve for target format data %cd_reserve = ( 'avi' => 0, 'ogm' => 0, 'mkv' => 0, 'svcd' => 1, 'vcd' => 1, 'dvd' => 0 ); # how to scale user specified cd size to get raw bits %cd_raw_scale = ( 'avi' => 1, 'ogm' => 1, 'mkv' => 1, 'svcd' => 2324/2048, 'vcd' => 2324/2048, 'dvd' => 1); # default cd size in MB %def_cd_size = ( 'avi' => 700, 'ogm' => 700, 'mkv' => 700, 'svcd' => 700, 'vcd' => 700, 'dvd' => 4450 ); # frc (frame rate codes) (cloned from transcode) @frame_rate_codes = ('illegal','23.976 NTSC','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?'); # --- define default options -------------------------------------------------- %options = ( # --- input/output param --- 'flavor' => 'avi', # flavor for output 'ovcodec' => 'auto', # output tc video codec 'oacodec' => 'auto', # output tc audio codec 'ivcodec' => 'auto', # input tc video codec 'iacodec' => 'auto', # input tc audio codec 'tc_vopt' => '', # add transcode options to video processing 'tc_aopt' => '', # add transcode options to audio processing 'ovc_opt' => '', # options for the video output codec (passed via -F) # --- output cd setup --- 'cd_size' => 0, # size of CD/DVD 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 # --- output audio setup --- '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 'aud_vbr_qual' => [], # list of vbr quality (0=no vbr, 1..9) # --- output subtitle setup --- 'sub_render' => 1, # render subtitles directly into the video stream 'sub_track' => [], # list of input subtitle tracks to rip # --- misc input flags --- 'dvd_title' => '1', # select a title in DVD input 'tv_norm' => 'auto', # pick an output tv norm (otherwise autodetect) 'interlaced' => 0, # input is interlaced 'modavi_divx' => 'ffmpeg', # use this module for AVI/divx import 'modavi_other'=> 'ffmpeg', # use this module for AVI/non-divx import 'split_avi' => 0, # split input avi for multi cd rips 'in_frc' => -1, # set input frameratecode (-1=autodetect) 'in_asr' => [0,0], # force input aspect ratio # --- misc output flags --- 'anamorph' => 0, # generate anamorph output (-1=autodetect) 'high_quality' => 0, # highest quality encoder settings 'scl_tol' => 7, # pixel tolerance when scaling 'max_vrate' => 6000, # maximum supported video rate 'low_space' => 0, # conserve space while ripping (deletes intermed.) # --- frame size determination --- 'asrerr_max' => 2.5, # maximum accepted aspect error (in percent) 'size_force' => [0,0], # force a specific output size 'frame_mod' => 16, # output frame size modulo 'width_range' => [512,640],# output frame width search range 'bpp_range' => [0.30,0.65],# minimum bits per pixel # --- sample image generation --- 'sample_max' => 5, # max number of sample images 'sample_chapframe' => 16, # use this frame as the sample image of a chapter 'sample_begin' => 100, # start frame for sample image extraction 'sample_step' => 100, # increment for next frame in sample image extr. 'sample_recycle' => 1, # do not regenerate or delete sample images # --- input clipping --- 'clip_force' => '', # force clip parameters 'clip_user' => 0, # user interactive clipping 'clip_thres' => [80,100], # clipping threshold for pgmfindclip 'clip_tstep' => [10,10], # stepping for threshold change 'clip_bmodulo' => [1,1], # pixel modulo of clipped border 'clip_fmodulo' => [16,16], # pixel modulo of clipped target frame 'clip_offset' => [0,0], # pixel offset for search 'clip_extra' => '', # extra options for pgmfindclip # --- MPEG --- 'mpeg_mplex' => 'mplex', # multiplexer for MPEG (mplex or tcmplex) 'chap_menu' => 0, # create chapter menus for VCD/SVCD (>0 = entries) # --- VCD params (MPEG 1, 4:3) --- 'vcd_conform' => 1, # follow the VCD standard. otherwise use options below: 'vcd_res' => [352,240,288], # screen resolution [w,h_ntsc,h_pal] 'vcd_max_kbps' => 1374, # maximum supported bitrate for whole stream 'vcd_min_vbr_quant' => 0, # minimum vbr quantizer (or 0=cbr, 2..31) 'vcd_can_anamorph' => 0, # supports anamoprh 16:9 encoding 'vcd_aud_max_tracks' => 1, # maximum supported audio channels 'vcd_aud_rates' => [224], # list of allowed audio kbps (first: default) 'vcd_video_buf' => 0, # video buffer for multiplexing (0=default) # --- SVCD params (MPEG 2, 4:3 or 16:9) --- 'svcd_conform' => 1, # follow the SVCD "standard". otherwise use options below: 'svcd_res' => [480,480,576],# screen resolution [w,h_ntsc,h_pal] 'svcd_max_kbps' => 2748, # maximum supported bitrate for whole stream 'svcd_min_vbr_quant' => 4, # minimum vbr quantizer (or 0=cbr, 2..31) 'svcd_can_anamorph' => 1, # supports anamoprh 16:9 encoding 'svcd_aud_max_tracks' => 2, # maximum supported audio channels 'svcd_aud_rates' => [192,224], # list of allowed audio kbps (first: default) 'svcd_video_buf' => 0, # video buffer for multiplexing (0=default) # --- DVD params (MPEG 1/2, 4:3 or 16:9) --- 'dvd_res' => [720,480,576], # screen resolution 'dvd_max_kbps' => 9800, # max full stream rate 'dvd_min_vbr_quant' => 4, 'dvd_can_anamorph' => 1, 'dvd_aud_max_tracks' => 8, 'dvd_aud_rates' => [192, 112, 128, 160, 224, 256, 320], 'dvd_video_buf' => 0, # --- debug/control --- 'debug_level' => 0, # set debug level 'probe_only' => 0 # probe only and then exit ); # ========== MAIN CODE ======================================================== $ripmake ="ripmake: ".'$Revision: 1.39 $ $Date: 2004/09/12 18:13:53 $ '."\n"; # ----- adjust options ----- # try to read ~/.ripmakerc my($rc_file) = "$ENV{'HOME'}/.ripmakerc"; if(-e $rc_file) { exit(1) if(!read_option_file(\%options,$rc_file)); } # parse command line options exit(1) if(!parse_options(\%options,\@ARGV)); # set project name set_project(\%options); # ----- open log file ----- $debug_level = $options{'debug_level'}; my($log_file) = "$options{'project'}.log"; open(my $log_handle,">$log_file") || die "Can't open log file: $log_file"; print_log($ripmake); # ----- check options ----- exit(1) if(!check_options(\%options)); print_log(" debug mode: level $debug_level\n") if($debug_level>0); dump_info("[5]--> Options",\%options) if($debug_level>=5); # ----- check tool versions ----- exit(1) if(!check_tools(\%options)); print_log("\n"); # ----- set clean LANG, otherwise input probing will fail (e.g. on RH) ----- $LANG = $ENV{'LANG'} || ""; if ($LANG ne "C") { $ENV{'LANG'} = "C"; } # ----- analyse input ----- %input = (); $ok = probe_input(\%input,\%options); dump_info("[5]--> Input info",\%input) if($debug_level>=5); exit(1) if(!$ok); print_log("\n"); exit 0 if($options{'probe_only'}); # ----- prepare output ----- %output = (); $ok = prepare_output(\%input,\%output,\%options); dump_info("[5]--> Output info",\%output) if($debug_level>=5); exit(1) if(!$ok); print_log("\n"); # ----- generate ripping makefile ----- exit(1) if(!generate_makefile(\%input,\%output,\%options)); # ----- close and rename log to project name ----- print_log("ready.\n"); close $log_handle; exit(0); ########## SUBROUTINES ######################################################## # --- print_log(@strings) --- # replacement for print command, outputs to log file, too # returns: - sub print_log { print @_; print $log_handle @_ if(defined $log_handle); } # --- run_cmd($cmd,$merge_stderr) --- # run external command and receive output # returns: stdout of command (if "merge_stderr" is set then stderr is added) # or "undef" if command failed sub run_cmd { my($cmd) = shift; my($merge_stderr) = shift; # use a temporary file for stderr logging my($stderr_log); $stderr_log = "/tmp/log.$$" if(!$merge_stderr); # open pipe to command and read output my($all) = "$cmd 2>&1 |"; $all = "$cmd 2>$stderr_log |" if(defined($stderr_log)); my($ok) = open(CFH,$all); my($result); if($ok) { $result = join('',); close(CFH); } # debug message [1] print_log("\n[1]--> external command: '$all' ($ok)\n") if($debug_level>=1); # log all output [2] if(!$ok || ($debug_level>=2)) { # print stdout if(defined($result)) { print_log("[2]--> output stdout begin:\n"); print_log($result); print_log("[2]--> output stdout end.\n"); } # print stderr if(defined($stderr_log) && -s $stderr_log) { print_log("[2]--> output stderr begin:\n"); if(open(LOG2,$stderr_log)) { print_log(join('',)); close LOG2; } print_log("[2]--> output stderr end.\n"); } } # remove stderr log unlink($stderr_log) if(defined($stderr_log) && -e $stderr_log); # return undef or output if(!$ok) { return undef; } else { return $result; } } # --- dump_info($title,$hash_ref) --- # verbose an information hash # returns: - sub dump_info { my($title) = shift; my($in) = shift; print_log("$title begin:\n"); foreach(sort(keys(%$in))) { my($value) = $$in{$_}; print_log(" $_ = "); if( ref($value) eq 'ARRAY' ) { print_log("[".join(',',@$value)."]"); } else { print_log($value); } print_log("\n"); } print_log("$title end.\n"); } # --- check_tools($opt) --- # check if all external programs are available in the correct version # returns: 0=error, 1=ok sub check_tools { my($opt) = @_; # fetch transcode version my($tcver) = run_cmd("transcode 2>&1 | head -n 1",0); $tcver =~ m/v(\d+)\.(\d+)\.(\d+)/; if(!defined $1) { print_log("FATAL: can't fetch transcode version!\n"); return 0; } my($tcvmaj) = $1; my($tcvmin) = $2; my($tcvmic) = $3; print_log(" transcode($tcvmaj.$tcvmin.$tcvmic),"); # check version requirements: at least 0.6.x if(($tcvmaj==0)&&($tcvmin<6)&&($tcvmic<2)) { print_log("\nFATAL: please use at least transcode version 0.6!\n"); return 0; } # check availability of pgmfindclip and chaplin my($has_pgmfindclip) = defined(run_cmd("pgmfindclip -h",1))?1:0; my($has_chaplin) = defined(run_cmd("chaplin -h",1))?1:0; my($has_mpglen) = defined(run_cmd("mpglen -h",1))?1:0; print_log( " pgmfindclip(".($has_pgmfindclip?'found':'MISSING') ."), chaplin(".($has_chaplin?'found':'MISSING') ."), mpglen(".($has_mpglen?'found':'MISSING') .")\n"); if(!($has_pgmfindclip && $has_chaplin && $has_mpglen)) { print_log("FATAL: external tool missing!\n"); return 0; } # check for display if interactive clipping is on if($$opt{'clip_user'}==1) { my($has_display) = defined(run_cmd("display -h",1))?1:0; print_log(" display: ".($has_display?'found':'MISSING')."\n"); if(!$has_display) { print_log("FATAL: external tool missing!\n"); return 0; } } return 1; } # ============================================================================= # ========== options ========================================================== # ============================================================================= # --- print_usage() ----------------------------------------------------------------- # print command help # returns: - sub print_usage { my($reason) = shift; print < under the GNU Public License V2 print_usage: $0 [Options] Common Options: -t Select title in DVD mode (default: 1,-1,1) -a [,
][,][,] Pick audio track num or language, (e.g. -a en bitrate, sampling rate and channels. -a 0,224 Repeat for more audio tracks! -a 0,128v Add 'v[mode]' to
for vbr encoding. -a 1,192v4) -s Add subtitle track (e.g. -s de -s 2) -p Probe input only (default: 0=off) Advanced Options: -n Set output tv standard (fps) (none,pal,ntsc,ntscfilm) (default: autodetect) -i Input signal is interlaced (default: 0=off) -g [,] Force a target frame size (default: autodetect) -c Force number of CDs (default: autodetect) -r Force clip range -u Do interactive clipping (default: 0=off) -C Set size of a CD/DVD in MB (default: 700=cd/4450=dvd) -S Use only a fraction of CD size (default: 1.0) -o [/][,] Choose codec for flavor (default: derived) -f [,] Force input codec (default: auto) Expert Options: -x var=value Set an option variable (help: "-x help") -X Read config options from file -d Enable debug output EOF print "supported flavors: " . join(',',@flavors) . "\n"; if(defined($reason)) { print "---> ERROR: $reason\n"; } } # --- parse_options($opt_ref,$argv_ref) --------------------------------------- # fill in and overwrite the %options hash with command line options # returns: 0=error, 1=ok 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 = audio setup elsif($1 eq 'a') { my($trk,$rate,$samp_rate,$chan) = split(/,/,$arg); if(!defined($trk)) { print_usage("no track in -a given!"); return 0; } # source track my($ref) = $$opt{'aud_track'}; push @$ref,$trk; # rate # set rate 0 if none given - will be corrected in check_options my($vbr); if(!defined($rate)) { $rate = 0; $vbr = 0; } else { if($rate =~ m/^(\d+)v(\d+)?$/) { $rate = $1; $vbr = defined($2)?$2:5; } else { $vbr = 0; } } $ref = $$opt{'aud_rate'}; push @$ref,$rate; $ref = $$opt{'aud_vbr_qual'}; push @$ref,$vbr; # 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; } # s = subtitle elsif($1 eq 's') { my($trk) = $arg; my($ref) = $$opt{'sub_track'}; push @$ref,$trk; } # --- advanced options --- # n = tv_norm elsif($1 eq 'n') { $$opt{'tv_norm'} = $arg; if(($arg ne 'none')&&($arg ne 'pal')&&($arg ne 'ntsc')&&($arg ne 'ntscfilm')) { print_usage("unknown tv norm: $arg"); return 0; } } # i = interlaced elsif($1 eq 'i') { $$opt{'interlaced'} = $arg; if(($arg != 0)&&($arg != 1)) { print_usage("wrong interlaced flag: $arg"); return 0; } } # g = force geometry elsif($1 eq 'g') { my($w,$h) = split(/,/,$arg); if(!defined($w)) { print_usage("no width found in -g!"); return 0; } 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') { my($ovcodec,$oacodec) = split(/,/,$arg); if(defined($ovcodec)) { my($ovc,$ovc_opt) = split(/\//,$ovcodec); $$opt{'ovcodec'} = $ovc if(defined($ovc)); $$opt{'ovc_opt'} = $ovc_opt if(defined($ovc_opt)); } $$opt{'oacodec'} = $oacodec if(defined $oacodec); } # C = cd size in MB elsif($1 eq 'C') { $$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; } # u = interactive clipping elsif($1 eq 'u') { $$opt{'clip_user'} = $arg; &print_usage_exit("wrong user clipping flag: $arg") if(($arg != 0)&&($arg != 1)); } # f = force input elsif($1 eq 'f') { my($ivcodec,$iacodec) = split(/,/,$arg); $$opt{'ivcodec'} = $ivcodec if(defined $ivcodec); $$opt{'iacodec'} = $iacodec if(defined $iacodec); } # --- profi options --- # x = extra var elsif($1 eq 'x') { # dump help if($arg eq 'help') { dump_info("Options",\%options); return 0; } # parse option if(!parse_option_line($opt,$arg,"command line")) { return 0; } } # X = read config file elsif($1 eq 'X') { if(!read_option_file($opt,$arg)) { print_log("ERROR: reading config from '$arg'\n"); return 0; } } # d = debug elsif($1 eq 'd') { $$opt{'debug_level'} = $arg; } # p = probe only elsif($1 eq 'p') { $$opt{'probe_only'} = 1; } # unknown! else { print_usage("Unknown switch: $_"); return 0; } } else { last; } } # --- fixed args: fetch input and flavor --- # input my($file) = $_; if(!defined($file)) { print_usage("give !"); return 0; } $file =~ s,/$,,; $$opt{'input'} = $file; if(!$$opt{'probe_only'}) { my($num_arg) = scalar @$argv; if($num_arg!=1) { print_usage("give !"); return 0; } # flavor $$opt{'flavor'} = $$argv[0]; } return 1; # all went well } # --- read_option_file($$opt,$file) ------------------------------------------- # parse option values from a file # returns: 1=ok, 0=failed sub read_option_file { my($opt,$file) = @_; open my($fh),$file or return 0; my($line) = 0; while(<$fh>) { $line++; # normalize input line s/^\s+//; s/\s*=\s*/=/; # skip comment or empty lines next if(m/^#/ || m/^$/); chop; # really parse option return 0 if(!parse_option_line($opt,$_,"$file:$line")); } close $fh; return 1; } # --- parse_option_line($$opt,$line,$from) ------------------------------------ # parse an option value # returns: 1=ok, 0=failed sub parse_option_line { my($opt,$line,$from) = @_; my($var,$val) = split(/=/,$line); if(!defined($var) || !defined($val)) { print_log "$from: Syntax error in '$line'\n"; return 0; } if(!defined($$opt{$var})) { print_log "$from: Unknown option: '$var' (see -x help)\n"; return 0; } my($res); eval "\$res = $val"; if(ref($res) ne ref($$opt{$var})) { print_log "$from: Wrong type of value for '$var': $val\n"; return 0; } # asign value $$opt{$var} = $res; return 1; } # --- check_param($desc,$par,$list) ------------------------------------------- # check passed param if it is found in a list of parameters # returns: - 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; } # --- set_project($opt) ------------------------------------------------------- # set project name in options # returns: - sub set_project { my($opt) = shift; # --- set project --- # fetch input and normalize my($name) = $$opt{'input'}; $name =~ s,.*/([^/]+)$,$1,; $name =~ s,\.[^.]+$,,; $name =~ s,\s+,_,g; $name = "output" if($name eq ""); $$opt{'project'} = $name . '-' . $$opt{'flavor'}; } # --- check_options($opt_ref) ------------------------------------------------- # perform some checks on the current state of the option hash # returns: 0=error, 1=ok sub check_options { my($opt) = shift; # --- check input --- my($input) = $$opt{'input'}; # if its a dir then find real path if ( -d $input ) { $input = run_cmd("cd \"$input\" && pwd",0); chop $input; $$opt{'input'} = $input; } # --- check flavor --- my($flavor) = $$opt{'flavor'}; if(!check_param('output flavor',$flavor,\@flavors)) { return 0; } # --- check cd_size --- if($$opt{'cd_size'}==0) { $$opt{'cd_size'} = $def_cd_size{$flavor}; } # --- 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_vbr_qual'}; push @$ref,0; $ref = $$opt{'aud_samp_rate'}; push @$ref,$def_aud_samp_rate{$flavor}; $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) { $_ = $def_aud_samp_rate{$flavor} if $_ == 0; } # replace channel 0 values with default $ref = $$opt{'aud_chan'}; foreach(@$ref) { $_ = 2 if $_ == 0; } } $$opt{'aud_num'} = scalar @$ref; # --- subtitle setup --- my($sref) = $$opt{'sub_track'}; $$opt{'sub_num'} = scalar @$sref; return 1; } # ============================================================================= # ========== probe_input($inp_ref,$opt_ref) =================================== # ============================================================================= # fill input hash with information about the source # returns: ok=1, error=0 sub probe_input { my($in) = shift; my($opt) = shift; # fetch source and optional title my($ifile) = $$opt{'input'}; my($title) = $$opt{'dvd_title'}; # call tcprobe print_log("Probing '$ifile' (title $title) with 'tcprobe'...\n"); my($cmd) = "tcprobe -T $title -i \"$ifile\""; $_ = run_cmd($cmd,1); my(@lines) = split(/\n/); if(m/fail/) { print_log("ERROR: probing with tcprobe failed!\n"); return 0; } my($aud_total) = 0; my(@aud_samp_rate); my(@aud_info); my(@aud_chan); my($sub_total) = 0; my(@sub_type); # ----- DVD ----------------------------------------------------------------- if(m,DVD image/device,) { # tc import codecs $$in{'vcodec'} = 'dvd'; $$in{'acodec'} = 'dvd'; # --- chapter analysis --- if(!m/(\d+) chapter\(s\)/) { print_log(" parse error: no chapters found!\n"); return 0; } my($chaps) = $1; $$in{'chapters'} = $1; # start time for each chapter my(@chap_start); foreach(@lines) { if(m/Chapter \d+.*(\d\d:\d\d:\d\d\.\d\d\d)/) { push @chap_start,$1; } } $$in{'chap_start'} = \@chap_start; # --- find chapters for sample image extraction --- # sample chapters max my($max) = $$opt{'sample_max'}; # build sample list my(@offset); my(@chap); my($i); # too few chapters -> search in first chapter only if($chaps<$max) { my($off) = $$opt{'sample_begin'}; for($i=0;$i<$max;$i++) { push @chap,1; # always in first chapter push @offset,$off; $off += $$opt{'sample_step'}; } } else { # clamp max range my($off) = $$opt{'sample_chapframe'}; $max = $chaps-1 if($max > ($chaps-1)); for($i=0;$i<$max;$i++) { push @offset,$off; # make sure to skip first (partial) gop push @chap,$i+2; # chapter number (starting with 2) } } $$in{'sample_chaps'} = \@chap; $$in{'sample_offsets'} = \@offset; # --- audio/subtitle query --- foreach(@lines) { if(m/\(dvd_reader.c\) (.*) (\d+)kHz (\d+)Ch/) { push @aud_samp_rate,$2 * 1000; push @aud_chan,$3; my($info) = $1; $info =~ s/\s+/_/g; push @aud_info,$info; $aud_total++; } if(m/\(dvd_reader.c\) subtitle \d+=<(\w+)>/) { push @sub_type,$1; $sub_total++; } } } # ----- AVI ----------------------------------------------------------------- elsif(m/RIFF data, AVI/) { # extract avi codec if(!m/codec=(\w+)/) { print_log(" parse error: no avi codec\n"); return 0; } my($codec)=$1; # --- video codec --- # is it a DIVX file? if(($codec eq "DIV3")|| ($codec eq "DIV4")|| ($codec eq "DIV5")|| ($codec eq "DIVX")|| ($codec eq "divx")|| ($codec eq "DX50")) { $$in{'vcodec'} = $$opt{'modavi_divx'}; } elsif($codec eq "XVID") { $$in{'vcodec'} = "xvid"; } else { $$in{'vcodec'} = $$opt{'modavi_other'}; } } # ---- MPEG streams --------------------------------------------------------- elsif(m/MPEG|CDXA/) { # --- set codec --- $$in{'vcodec'} = "mpeg2"; $$in{'acodec'} = "mp3"; # we use mpglen for exact MPEG frame count print_log(" calling 'mpglen' to determine frame count... (this may take a while!)\n"); my($frames) = run_cmd("mpglen -f \"$ifile\"",0); $$in{'frames'} = $frames; } # unknown input else { print_log("ERROR: no known input codec found!\n"); return 0; } # ----- Common Setup -------------------------------------------------------- # --- parse generic audio output of transcode --- if($aud_total == 0) { my($acodec) = 'auto'; foreach(@lines) { if(m/audio track:.*-e (\d+),\d+,(\d+).*-n (\S+)/) { push @aud_samp_rate,$1; push @aud_info,$3; push @aud_chan,$2; $aud_total++; # pick right codec (assume all channels are the same) # PCM if($3 eq '0x01') { $acodec = 'avi'; } # MP3 elsif($3 eq '0x55') { $acodec = 'mp3'; } # MP2 elsif($3 eq '0x50') { $acodec = 'mp2'; } # AC3 elsif($3 eq '0x2000') { $acodec = 'ac3'; } # WMA elsif($3 eq '0x161') { $acodec = 'mplayer'; } } } $$in{'acodec'} = $acodec; } # --- 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; print_log(" audio tracks:\n"); foreach(@aud_samp_rate) { print_log(" ") if($aud_total % 2 == 0); print_log(sprintf "%02u=%2ukHz,%1uch(%s) ", $aud_total, int($aud_samp_rate[$aud_total]/1000), $aud_chan[$aud_total], $aud_info[$aud_total]); $aud_total++; print_log("\n") if($aud_total % 2 == 0); } print_log("\n") if($aud_total % 2 != 0); # --- subtitles --- $$in{'sub_total'} = $sub_total; $$in{'sub_type'} = \@sub_type; if($sub_total > 0) { print_log(" subtitles:\n"); $sub_total = 0; foreach(@sub_type) { print_log(" ") if($sub_total % 8 == 0); print_log(sprintf("%02u=%s ",$sub_total,$_)); $sub_total++; print_log("\n") if($sub_total % 8 == 0); } print_log("\n") if($sub_total % 8 != 0); } # --- video parameters --- # size if(!m/import frame size: -g (\d+)x(\d+)/) { print_log(" parse error: no frame size found!\n"); return 0; } $$in{'size'} = [ $1,$2 ]; # aspect my($asr_derived) = 0; my($asr_forced) = 0; if($$opt{'in_asr'}[0]!=0) { $$in{'aspect'} = $$opt{'in_asr'}; $asr_forced = 1; } elsif(!m/aspect ratio: (\d+):(\d+)/) { $$in{'aspect'} = $$in{'size'}; $asr_derived = 1; } else { $$in{'aspect'} = [ $1,$2 ]; } # frame rate if($$opt{'in_frc'} != -1) { # force frame rate $$in{'fps'} = $frame_rate_codes[$$opt{'in_frc'}]; $$in{'frc'} = $$opt{'in_frc'}; } else { if(!m/frame rate: -f ([\d\.]+) .* frc=(\d+)/) { print_log(" parse error: no frame rate/frc found!\n"); return 0; } $$in{'fps'} = $1; $$in{'frc'} = $2; } # frames and duration in secs if(!defined($$in{'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_log(" parse error: no frames, secs!\n"); return 0; } } if(!defined($$in{'secs'})) { $$in{'secs'} = $$in{'frames'} / $$in{'fps'}; } # --- setup sample images if none is given --- if(!defined($$in{'sample_offsets'})) { $$in{'sample_offsets'} = []; $$in{'sample_chaps'} = []; my($off) = $$opt{'sample_begin'}; my($chap)= $$in{'sample_chaps'}; my($offs) = $$in{'sample_offsets'}; for($i=0;$i<$$opt{'sample_max'};$i++) { push @$chap,1; # dummy chapter push @$offs,$off % $$in{'frames'}; $off += $$opt{'sample_step'}; } } # overwrite input codec? my($ivcodec) = $$opt{'ivcodec'}; if($ivcodec ne 'auto') { print_log(" vcodec: '$ivcodec' (forced)\n"); $$in{'vcodec'} = $ivcodec; } else { print_log(" vcodec: '$$in{'vcodec'}' (detected)\n"); } my($iacodec) = $$opt{'iacodec'}; if($iacodec ne 'auto') { print_log(" acodec: '$iacodec' (forced)\n"); $$in{'acodec'} = $iacodec; } else { print_log(" acodec: '$$in{'acodec'}' (detected)\n"); } # verbose my($size) = $$in{'size'}; my($asr) = $$in{'aspect'}; $asr = $$asr[0] / $$asr[1]; print_log(sprintf " size: %3dx%3d aspect: %3.2g:1 %s\n", $$size[0],$$size[1],$asr, $asr_forced ? '(forced)' : $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; print_log(sprintf " frames: %06d playtime: %02d:%02d:%02d.%03d\n", $$in{'frames'},$hour,$min,$secs,$ms); my($code) = $frame_rate_codes[$$in{'frc'}]; print_log(sprintf(" fps: %-14s frc: %s ($code)\n",$$in{'fps'},$$in{'frc'})); if($$in{'frames'} < 1000) { print_log("WARNING: movie is very short!! (maybe you selected the wrong title?)\n"); } # --- do we need input clipping? --- # try to find a reasonable clip region return 1 if($$opt{'probe_only'}); return find_clip(\%input,\%options) } # --- do_user_clip ------------------------------------------------------------ # adjust clipping with user feedback # returns: - sub do_user_clip { my($opt,$clip,$samples) = @_; print " interactive clipping (enter '?' for help):\n"; my(@bsamples) = map {/(.*)(\.pgm)/ && "$1-m$2" } @$samples; # init clip parameters my(@thres) = @{$$opt{'clip_thres'}}; my(@step) = @{$$opt{'clip_tstep'}}; my(@bmod) = @{$$opt{'clip_bmodulo'}}; my(@fmod) = @{$$opt{'clip_fmodulo'}}; my(@offset)= @{$$opt{'clip_offset'}}; my($extra) = $$opt{'clip_extra'}; while(1) { # now call pgmfindclip my($opts) = "-b $bmod[0],$bmod[1] -f $fmod[0],$fmod[1] "; $opts .= "-t $thres[0],$thres[1] -o $offset[0],$offset[1] -w "; $opts .= $extra if($extra ne ''); print_log(" searching clip region (pgmfindclip $opts): "); my($cmd) = "pgmfindclip $opts @$samples"; $_ = run_cmd($cmd,0); if(!m/(\d+),(\d+),(\d+),(\d+)/) { print_log("\nFATAL: call to pgmfindclip failed???\n"); } else { @$clip = ($1,$2,$3,$4); print_log("current clip: " . join(',',@$clip) . "\n"); } # show result with ImageMagick's display print "[displaying]\r"; run_cmd("display ".join(' ',@bsamples),0); # ----- handle user input ----- my($quit) = 0; my($stay) = 1; while($stay) { $stay = 0; print "[enter cmd]> "; $_ = ; # 'q' quit if(m/^q$/) { $quit = 1; } # 'x' adjust x threshold elsif(m/^x\s*([+-=])(\d*)$/) { my($inc) = ($2 eq '')?$step[0]:$2; if($1 eq '+') { $thres[0] += $inc; } elsif($1 eq '-') { $thres[0] -= $inc; } else { $thres[0] = $inc; } } # 'y' adjust y threshold elsif(m/^y\s*([+-=])(\d*)$/) { my($inc) = ($2 eq '')?$step[1]:$2; if($1 eq '+') { $thres[1] += $inc; } elsif($1 eq '-') { $thres[1] -= $inc; } else { $thres[1] = $inc; } } # 'b' border modulo elsif(m/^b\s*(\d+),(\d+)/) { $bmod[0] = $1; $bmod[1] = $2; } # 'f' border modulo elsif(m/^f\s*(\d+),(\d+)/) { $fmod[0] = $1; $fmod[1] = $2; } # 'o' offset elsif(m/^o\s*(\d+),(\d+)/) { $offset[0] = $1; $offset[1] = $2; } # 'e' extra options elsif(m/^e\s*(.*)/) { $extra = $1; } # else help else { print "x <+-=>[val] change x threshold, e.g. x+ x-10 x=20\n"; print "y <+-=>[val] change y threshold\n"; print "b , change border modulo\n"; print "f , change frame modulo\n"; print "o , change search offset\n"; print "e add extra options to pgmfindclip\n"; print "q quit interactive clipping\n"; $stay = 1; } } last if($quit); } foreach(@bsamples) { unlink($_); } print_log(" user clip result: " . join(',',@$clip) . "\n"); } # --- find_clip($inp_ref,$opt_ref) -------------------------------------------- # adds a 'tc_clip' on success # returns: 0=failed 1=ok sub find_clip { my($in) = shift; my($opt) = shift; # the clip area my(@clip); print_log("Searching clipping values for input frames...\n"); # --- use forced clip values --- my($force) = $$opt{'clip_force'}; if($force ne '') { print_log(" 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_log("ERROR: wrong clip parameters passed!\n"); return 0; } } # --- automatic clip with pgmfindclip --- else { # --- generate a sample gray image for each vob file --- my($chaps) = $$in{'sample_chaps'}; my($offsets) = $$in{'sample_offsets'}; my($num); my(@samples); my($sfile) = "sample000000.pgm"; # the file that transcode generates my($numchap) = scalar @$chaps; print_log(" generating $numchap sample images with transcode...\n"); for($num=0;$num<$numchap;$num++) { # span a single image 'range' my($f) = $$offsets[$num]; my($g) = $f+1; # input param my($ivcodec) = $$in{'vcodec'}; my($input) = "-x $ivcodec,null -i \"$$opt{'input'}\""; my($chap) = $$chaps[$num]; $input .= " -T $$opt{'dvd_title'},$chap" if($ivcodec eq 'dvd'); # output name (project name without flavor) my($name) = $$opt{'project'}; $name =~ s/-[^-]+$//; my($pgm_name) = sprintf("$name-SAMPLE-%02d.pgm",$num); # report frame if($ivcodec eq 'dvd') { print_log(sprintf(" chapter %02d/frame %03d: $pgm_name",$chap,$f)); } else { print_log(sprintf(" frame %05d: $pgm_name",$f)); } # call transcode only if file is missing or reuse mode is off if((!-s $pgm_name) || ($$opt{'sample_recycle'}==0)) { # call transcode $cmd = "transcode $input -o sample -y ppm -c $f-$g -z -K"; my($log) = run_cmd($cmd,0); # check generated sample image if(!-s $sfile) { print_log("\nERROR: can't create sample file '$sfile'\n"); return 0; } # rename to clipXX.pgm rename $sfile,"$pgm_name"; print_log(" (extracted)\n"); } else { print_log(" (recycled)\n"); } # store sample images in list push(@samples,"$pgm_name"); } # safety check if(scalar @samples == 0) { print_log("FATAL: no sample images found!!\n"); return 0; } # --- find best clip window for all sample images --- if($$opt{'clip_user'} == 1) { # perform interactive clipping do_user_clip($opt,\@clip,\@samples); } else { # pgmfindclip parameters my($thres) = $$opt{'clip_thres'}; my($bmod) = $$opt{'clip_bmodulo'}; my($fmod) = $$opt{'clip_fmodulo'}; my($offset)= $$opt{'clip_offset'}; my($extra) = $$opt{'clip_extra'}; # now call pgmfindclip my($opts) = "-b $$bmod[0],$$bmod[1] -f $$fmod[0],$$fmod[1] "; $opts .= "-t $$thres[0],$$thres[1] -o $$offset[0],$$offset[1] "; $opts .= $extra if($extra ne ''); print_log(" finding clip region (pgmfindclip $opts): "); my($cmd) = "pgmfindclip $opts @samples"; $_ = run_cmd($cmd,0); if(!m/(\d+),(\d+),(\d+),(\d+)/) { print_log("\nFATAL: call to pgmfindclip failed???\n"); return 0; } @clip = ($1,$2,$3,$4); print_log(join(',',@clip) . "\n"); } # --- remove temp files if not in debug mode -- if($debug_level>0) { print_log(" DEBUG: keeping sample image files.\n"); } else { if($$opt{'sample_recycle'}==0) { foreach(@samples) { unlink($_); } } } } # all zero? my($sum)=0; foreach(@clip) { $sum += $_; } # no, some clipping is required if($sum>0) { # store clip parameter print_log(" 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_log(" no clipping required!\n"); } return 1; } # ============================================================================= # ========== prepare_output($inp_ref,$out_ref,$opt_ref) ======================= # ============================================================================= # fill in all required values in the output hash # return: 0=error, 1=ok sub prepare_output { my($in) = shift; my($out) = shift; my($opt) = shift; # fetch output flavor my($flavor) = $$opt{'flavor'}; # ----- find output codecs ---- my($ovcodec) = $$opt{'ovcodec'}; my($oacodec) = $$opt{'oacodec'}; $ovcodec = $def_vcodec{$flavor} if($ovcodec eq 'auto'); $oacodec = $def_acodec{$flavor} if($oacodec eq 'auto'); $$out{'vcodec'} = $ovcodec; $$out{'acodec'} = $oacodec; $$out{'vc_opt'} = $$opt{'ovc_opt'}; # ----- setup output tv_norm (out fps, out frc) ----- return 0 if(!prepare_tv_norm($in,$out,$opt)); # ----- check and prepare output audio setup ----- return 0 if(!prepare_audio($in,$out,$opt)); # ----- check and prepare subtitles ----- return 0 if(!prepare_subs($in,$out,$opt)); # ----- calc vrates for range of cds (given the audio rate) ----- return 0 if(!calc_vrates_per_num_cd($in,$out,$opt)); # ----- flavor setup ----- # -- vcd,svcd,dvd -- if(($flavor eq 'vcd')||($flavor eq 'svcd')||($flavor eq 'dvd')) { return 0 if(!prepare_vcd_svcd_dvd($in,$out,$opt)); } # -- avi/ogm/mkv -- # given: aspect # find: size else { return 0 if(!find_output_size($in,$out,$opt)); # set common options $$out{'can_letterbox'} = 0; $$out{'anamorph'} = 0; $$out{'pulldown'} = 0; $$out{'vbr'} = 1; $$out{'max_vrate'} = $$opt{'max_vrate'}; } # ----- determine vrate for each cd ----- if($$in{'vcodec'} eq 'dvd') { return 0 if(!recalc_vrate_for_chapter_sets($in,$out,$opt)); } else { calc_ranges_for_each_cd($in,$out); } # ----- clamp video rates ----- my($cd_vrates) = $$out{'cd_vrate'}; $max_vrate = $$out{'max_vrate'}; foreach(@$cd_vrates) { if(!$$out{'vbr'}) { print_log(" forcing fixed vrate $max_vrate (was $_)\n"); $_ = $max_vrate; } if($_ > $max_vrate) { print_log(" clamping vrate $_ -> $max_vrate\n"); $_ = $max_vrate; } } # --- verbose video output parameter ---------------------------------------- print_log("\nDerived video output:\n"); # video parameter print_log(" tv norm: '$$out{'tv_norm'}'\n"); print_log(sprintf " fps: %5.3f frc: %d\n",$$out{'fps'},$$out{'frc'}); print_log(" pulldown: " .($$out{'pulldown'}?'yes':'no ') ." anamorph: ".($$out{'anamorph'}?'yes':'no')."\n"); my($size) = $$out{'size'}; my($aspect) = $$out{'aspect'}; $aspect = $$aspect[0]/$$aspect[1]; print_log(sprintf " size: %dx%d aspect: %3.2g:1\n", $$size[0],$$size[1],$aspect); my($cd_num) = $$out{'cd_num'}; my($vrate) = $$out{'cd_vrate'}; print_log(" vrate: " . ($$out{'vbr'}?'vbr ':'cbr '). join(', ',@$vrate)." kbps\n"); # --- determine frame transformation --- # adds tc_trafo entry find_frame_trafo($in,$out,$opt); return 1; } # ----- prepare_vcd_svcd_dvd -------------------------------------------------- # setup output parameters suitable for vcd/svcd encoding # returns: 1=ok 0=error sub prepare_vcd_svcd_dvd { my($in) = shift; my($out) = shift; my($opt) = shift; # --- anamorph encoding suitable? --- my($anamorph) = $$opt{'anamorph'}; if($anamorph==-1) { # autodetect: yes if asr at least 16:9 my($in_asr) = $$in{'aspect'}; $in_asr = $$in_asr[0] / $$in_asr[1]; if($in_asr > 1.7) { $anamorph = 1; } else { $anamorph = 0; } } # --- setup flavor restrictions --------------------------------------------- my($tv_norm) = $$out{'tv_norm'}; my($vbr) = 0; my($max_vrate) = $$opt{'max_vrate'}; # --- need a valid tv norm! --- if($tv_norm eq 'none') { print "ERROR: tv norm 'none' not supported for VCD/SVCD/DVD flavor!\n"; return 0; } # ----- dvd ----- if($$opt{'flavor'} eq 'dvd') { $vbr = ($$opt{'dvd_min_vbr_quant'}>0)?1:0; $max_vrate = $$opt{'dvd_max_kbps'} - $$opt{'aud_sum_rate'}; # aspect? anamorph? if($anamorph && $$opt{'dvd_can_anamorph'}) { $$out{'aspect'} = [16,9]; $$out{'anamorph'} = 1; } else { $$out{'aspect'} = [4,3]; $$out{'anamorph'} = 0; } # frame size? my($res) = $$opt{'dvd_res'}; $$out{'size'} = [$$res[0],$$res[$tv_norm eq 'pal' ? 2:1]]; # -- 3:2 pulldown flag is required for NTSC film -- $$out{'pulldown'} = ($$out{'frc'} == 1)?1:0; # ntscfilm pulldown } # ----- svcd ----- elsif($$opt{'flavor'} eq 'svcd') { # -- follow the standard if($$opt{'svcd_conform'}==1) { # vbr, max vrate $vbr = 1; $max_vrate = 2748 - $$out{'aud_sum_rate'}; # 4:3 no anamorph $$out{'aspect'} = [4,3]; $$out{'anamorph'} = 0; # fixed size if($tv_norm eq 'pal') { $$out{'size'} = [480,576]; } else { $$out{'size'} = [480,480]; } } # -- XSVCD else { # vbr? vrate? $vbr = ($$opt{'svcd_min_vbr_quant'}>0)?1:0; $max_vrate = $$opt{'svcd_max_kbps'} - $$out{'aud_sum_rate'}; # aspect? anamorph? if($anamorph && $$opt{'svcd_can_anamorph'}) { $$out{'aspect'} = [16,9]; $$out{'anamorph'} = 1; } else { $$out{'aspect'} = [4,3]; $$out{'anamorph'} = 0; } # frame size? my($res) = $$opt{'svcd_res'}; $$out{'size'} = [$$res[0],$$res[$tv_norm eq 'pal' ? 2:1]]; } # -- 3:2 pulldown flag is required for NTSC film -- $$out{'pulldown'} = ($$out{'frc'} == 1)?1:0; # ntscfilm pulldown } # ----- vcd ----- else { # -- follow the standard if($$opt{'vcd_conform'}==1) { # cbr, fixed rate $vbr = 0; $max_vrate = 1150; # fixed 4:3 no anamorph $$out{'aspect'} = [4,3]; $$out{'anamorph'} = 0; # fixed frame size if($tv_norm eq 'pal') { $$out{'size'} = [352,288]; } else { $$out{'size'} = [352,240]; } } else { # vbr? vrate? $vbr = ($$opt{'vcd_min_vbr_quant'}>0)?1:0; $max_vrate = $$opt{'vcd_max_kbps'} - $$out{'aud_sum_rate'}; # aspect? anamorph? if($anamorph && $$opt{'vcd_can_anamorph'}) { $$out{'aspect'} = [16,9]; $$out{'anamorph'} = 1; } else { $$out{'aspect'} = [4,3]; $$out{'anamorph'} = 0; } # frame size? my($res) = $$opt{'vcd_res'}; $$out{'size'} = [$$res[0],$$res[$tv_norm eq 'pal' ? 2:1]]; } # no 3:2 pulldown for MPEG1 available $$out{'pulldown'} = 0; } # ----- now determine #no cds ----- find_num_cd($out,$opt,$max_vrate,$vbr); # ----- VCD/SVCD/DVD can use letterboxing ----- $$out{'can_letterbox'} = 1; $$out{'vbr'} = $vbr; $$out{'max_vrate'} = $max_vrate; return 1; } # ------ calc_ranges_for_each_cd(out) ------------------------------------ # create the cd_vrate array and fill in the same value for each cd # returns: - sub calc_ranges_for_each_cd { my($in) = shift; my($out) = shift; my($cd_num) = $$out{'cd_num'}; my($vrate) = $$out{'vid_rate'}; my(@cd_vrate); # copy given vrate[cd_num] to all entries foreach(0 .. $cd_num-1) { push @cd_vrate,$$vrate[$cd_num-1]; } $$out{'cd_vrate'} = \@cd_vrate; # calc cd_ranges my($total) = $$in{'frames'}; # frame rate conversion? if($$in{'fps'} != $$out{'fps'}) { $total = int($total * $$out{'fps'} / $$in{'fps'}); } my(@cd_ranges); my($part) = $total / $cd_num; foreach(0 .. $cd_num-2) { $cd_ranges[$_] = sprintf("%06u-%06u",$_ * $part,($_+1)*$part); } $cd_ranges[$cd_num-1] = sprintf("%06u-%06u",($cd_num-1)*$part,$total); # verbose print_log("Split movie into frame ranges:\n"); foreach(1 .. $cd_num) { print_log(" cd$_: range $cd_ranges[$_-1]\n"); } $$out{'cd_ranges'} = \@cd_ranges; } # ----- recalc_vrate_for_chapter_sets($in,$out,$opt) -------------------------- # when ripping a dvd then each cd holds a region of chapters # here we adjust the vrates for each cd so that the chapter range fits # returns: 0=error, 1=ok sub recalc_vrate_for_chapter_sets { my($in) = shift; my($out) = shift; my($opt) = shift; print_log("Recalculating videorate for each DVD chapter set... (chaplin)\n"); my($title) = $$opt{'dvd_title'}; my($fps) = $$out{'fps'}; # additional SVCD/VCD options for chaplin: XML + menus my($prj) = $$opt{'project'}; my($xopt) = "-y $prj-pal.yuv"; # always create yuv palette my($flavor) = $$opt{'flavor'}; my($chap_menu) = $$opt{'chap_menu'}; if(($flavor eq 'svcd')||($flavor eq 'vcd')) { # XML creation $xopt .= sprintf(" -x \"$prj,%s,$prj-CD%%d.xml,$prj-CD%%d.mpg,$prj-CD%%d-menu%%d.mpg\"", ($flavor eq 'svcd')?'svcd':'vcd'); if($chap_menu>0) { # create chapter menus $xopt .= " -m $chap_menu -g $prj-menu.txt"; print_log(" (creating chapter menu in '$prj-menu.txt' - please edit)\n"); } } # pass requested number of chapter sets and call chaplin my($parts) = "-p $$out{'cd_num'}"; my($cmd)="chaplin -d \"$$opt{'input'}\" -t $title -f $fps $xopt $parts"; my($log) = run_cmd($cmd,0); my(@result) = split(/\n/,$log); # if creating menus then fetch the required menu MPG files if($chap_menu>0) { foreach(@result) { if(m/added menu '([^']+)/) { push @menu_mpgs,$1; } } $$out{'menu_mpgs'} = \@menu_mpgs; } # eval chaplin output my(@cd_chaps); my($max_chap)=0; my($total_frames)=0; my(@cd_vrate); foreach(@result) { if(m/^part.*chapters (\d+)-(\d+).*frames.*\+(\d+)/) { push @cd_chaps,"$1-$2"; print_log(" chapters $1-$2: $3 frames\n"); # recalc vrate for chapter set my($frames) = $3; my($cd_rate) = $$out{'cd_bits'} * $$out{'fps'} / ($frames * 1000); my($vr) = int($cd_rate - $$out{'novideo_rate'} - 0.5); push @cd_vrate,$vr; $max_chap = $2 if($2 > $max_chap); $total_frames += $frames; # recheck size my($trate) = $vr + $$out{'novideo_rate'}; my($ncd_size) = int(0.5 + ($trate * $frames * 1000 / ($$out{'fps'} * 8 * 1024 * 1024))); print_log(sprintf " video rate: %4u kbps\n",$vr); print_log(sprintf " total rate: %4u kbps\n",$trate); print_log(sprintf " cd size: %4u MB\n",$ncd_size); } } if($max_chap==0) { print_log("FATAL: no chapters found in output of chaplin???\n"); return 0; } $$out{'cd_chaps'} = \@cd_chaps; $$out{'cd_vrate'} = \@cd_vrate; print_log(" total frames: $total_frames ($$in{'frames'} reported)\n"); return 1; } # --- prepare_audio($inp_ref,$out_ref,$opt_ref) ------------------------------- # setup and check audio relevant components for output # returns: 0=error, 1=ok sub prepare_audio { my($in) = shift; my($out) = shift; my($opt) = shift; # fetch output flavor my($flavor) = $$opt{'flavor'}; # ----- audio setup ----- # check if all requested tracks are available in input my($aud_track) = $$opt{'aud_track'}; my($aud_num) = $$opt{'aud_num'}; my($aud_rate) = $$opt{'aud_rate'}; my($aud_samp_rate) = $$opt{'aud_samp_rate'}; my($aud_chan) = $$opt{'aud_chan'}; my($inp_info) = $$in{'aud_info'}; my($inp_total) = $$in{'aud_total'}; # map audio language name to track number foreach(@$aud_track) { # check if language is given if(/^[a-zA-z]+$/) { my($lang) = $_; my($track) = -1; # try to find audio track for language foreach(0 .. $inp_total-1) { if($$inp_info[$_] =~ m/_${lang}_/i) { $track = $_; last; } } if($track == -1) { print_log "FATAL: Could not find audio track for language '$lang'\n"; return 0; } $_ = $track; } } foreach(@$aud_track) { if($_ >= $inp_total) { print_log("ERROR: requested audio track $_ is not available in input!\n"); return 0; } } # check vcd requirements if($flavor eq 'vcd') { # VCD standard if($$opt{'vcd_conform'}==1) { # maximum number of audio tracks if($aud_num>1) { print_log("ERROR: VCDs only supports one track!\n"); return 0; } # only 224kbps if($$aud_rate[0]!=224) { print_log("ERROR: VCDs only support 224 kbps audio!\n"); return 0; } } # XVCD else { # maximum number of audio tracks if($aud_num>$$opt{'vcd_aud_max_tracks'}) { print_log("ERROR: VCDs only support $$opt{'vcd_aud_max'} track(s)!\n"); return 0; } # supported audio bitrate? my($rates) = $$opt{'vcd_aud_rates'}; foreach(@$aud_rate) { my($rate) = $_; my($found) = 0; foreach(@$rates) { if($_ == $rate) { $found = 1; last; } } if($found == 0) { print_log("ERROR: audio rate $rate kbps no supported for VCDs\n"); return 0; } } } } # check svcd requirements elsif($flavor eq 'svcd') { # standard SVCD if($$opt{'svcd_conform'}==1) { # at most 2 tracks if($aud_num>2) { print_log("ERROR: SVCDs only support 1 or 2 track(s)!\n"); return 0; } # check bitrate foreach(@$aud_rate) { my($rate) = $_; if(($rate<32)||($rate>384)) { print_log("ERROR: SVCDs only support bitrate in [32;384]!\n"); return 0; } } } # XSVCD else { # maximum number of audio tracks if($aud_num>$$opt{'svcd_aud_max_tracks'}) { print_log("ERROR: SVCDs only support $$opt{'svcd_aud_max'} track(s)!\n"); return 0; } # supported audio bitrate? my($rates) = $$opt{'svcd_aud_rates'}; foreach(@$aud_rate) { my($rate) = $_; my($found) = 0; foreach(@$rates) { if($_ == $rate) { $found = 1; last; } } if($found == 0) { print_log("ERROR: audio rate $rate kbps no supported for SVCDs\n"); return 0; } } } } # check dvd requirements elsif($flavor eq 'dvd') { # maximum number of audio tracks if($aud_num>$$opt{'dvd_aud_max_tracks'}) { print_log("ERROR: DVDs only support $$opt{'dvd_aud_max'} track(s)!\n"); return 0; } # supported audio bitrate? my($rates) = $$opt{'dvd_aud_rates'}; foreach(@$aud_rate) { my($rate) = $_; my($found) = 0; foreach(@$rates) { if($_ == $rate) { $found = 1; last; } } if($found == 0) { print_log("ERROR: audio rate $rate kbps no supported for DVDs\n"); return 0; } } } # calc total audio output rate my($aud_sum_rate) = 0; foreach(@$aud_rate) { $aud_sum_rate += $_; } $$out{'aud_sum_rate'} = $aud_sum_rate; # give audio output summary print_log("Requested audio output:\n"); $aud_num = 0; foreach(@$aud_track) { print_log(sprintf " audio%u: %5u kbps %5u kHz %u chan [src=#%u:%s]\n", $aud_num, $$aud_rate[$aud_num], $$aud_samp_rate[$aud_num], $$aud_chan[$aud_num], $_,$$inp_info[$_]); $aud_num++; } print_log(sprintf " total: %5u kbps\n",$aud_sum_rate); return 1; } # --- ($inp_ref,$out_ref,$opt_ref) -------------------------- # setup and check subtitles for output # returns: 0=error, 1=ok sub prepare_subs { my($in) = shift; my($out) = shift; my($opt) = shift; # fetch output flavor my($flavor) = $$opt{'flavor'}; # check if all requested tracks are available in input my($sub_track) = $$opt{'sub_track'}; my($sub_num) = $$opt{'sub_num'}; my($inp_type) = $$in{'sub_type'}; my($inp_total) = $$in{'sub_total'}; # nothing to do return 1 if($sub_num == 0); # map audio language name to track number foreach(@$sub_track) { # check if language is given if(/^[a-zA-z]+$/) { my($lang) = $_; my($track) = -1; # try to find subtitle track for language foreach(0 .. $inp_total-1) { if($$inp_type[$_] =~ m/${lang}/i) { $track = $_; last; } } if($track == -1) { print_log "FATAL: Could not find subtitle track for language '$lang'\n"; return 0; } $_ = $track; } } foreach(@$sub_track) { if($_ >= $inp_total) { print_log("ERROR: requested subtitle track $_ is not available in input!\n"); return 0; } } # we can render only one sub title if(($$opt{'sub_render'})&&($sub_num > 1)) { print_log("ERROR: you can only render a single subtitle!!\n"); return 0; } # check s/vcd requirements if(($flavor eq 'vcd')||($flavor eq 'svcd')) { if($sub_num > 1) { print_log("ERROR: only a single subtitle supported for S/VCD!\n"); return 0; } if(!$$opt{'sub_render'}) { print_log("ERROR: S/VCD need rendered subtitles!\n"); return 0; } } # give subtitle summary print_log("Subtitle setup:\n"); $sub_num = 0; foreach(@$sub_track) { my($src_trk) = $$sub_track[$sub_num]; print_log(sprintf(" sub%u: %s [src=#%u]\n", $sub_num,$$inp_type[$src_trk],$src_trk)); $sub_num++; } return 1; } # --- calc_vrates_per_num_cd($inp,$out,$opt) ---------------------------------- # calc max vrate available on one, two or more discs if audio+mux rate is given # returns: 0=error, 1=ok sub calc_vrates_per_num_cd { my($in) = shift; my($out) = shift; my($opt) = shift; # fetch output flavor my($flavor) = $$opt{'flavor'}; print_log("Calculating video output rate for CD sets...\n"); # --- 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_log("ERROR: no space on CD left ($cd_size MB!)\n"); return 0; } $$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 * $$out{'fps'} / ($$in{'frames'} * 1000))-0.5); print_log(sprintf " space for movie: %4u MB = $cd_bits bits\n",$cd_size); print_log(sprintf " total audio rate: %4u kbps\n",$$out{'aud_sum_rate'}); print_log(sprintf " required mux rate: %4u kbps\n",$mux_add_rate{$flavor}); # fixed add on to video rate my($novideo_rate) = $$out{'aud_sum_rate'} + $mux_add_rate{$flavor}; $$out{'novideo_rate'} = $novideo_rate; # --- 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 - $novideo_rate)-0.5); if($vid_rate<100) { print_log("ERROR: video bitrate $vid_rate kbps too low!\n"); return 0; } if($vid_rate>$max_vrate) { if($j < $$opt{'cd_max'}) { print_log(" [restricting max CDs to $j; vrate already $vid_rate!]\n"); $$opt{'cd_max'} = $j; # correct force cd $$opt{'cd_force'} = $j if($$opt{'cd_force'} > $j); } } my($frame_bits) = int(($vid_rate * 1000 / $$out{'fps'})-0.5); print_log(sprintf " %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; return 1; } # --- prepare_tv_norm($in,$out,$opt) ------------------------------------------ # setup output tv norm. either given or derive from input frc # returns: 0=error, 1=ok sub prepare_tv_norm { my($in) = shift; my($out) = shift; my($opt) = shift; my($tv_norm,$fps,$frc); # --- determine tv_norm, fps and frc for output --- $tv_norm = $$opt{'tv_norm'}; if($tv_norm eq 'auto') { # automatic detect from input frc code: my($frc)=$$in{'frc'}; if($frc == 3) { $tv_norm = 'pal'; } elsif($frc == 4) { $tv_norm = 'ntsc'; } elsif($frc == 1) { $tv_norm = 'ntscfilm'; } else { $tv_norm = 'none'; } } # --- now set output fps and frc according to norm --- if($tv_norm eq 'pal') { $fps = 25.0; $frc = 3; } elsif($tv_norm eq 'ntsc') { $fps = 29.97; $frc = 4; } elsif($tv_norm eq 'ntscfilm') { $fps = 23.976; $frc = 1; } else { $fps = $$in{'fps'}; $frc = $$in{'frc'}; } # set output $$out{'tv_norm'} = $tv_norm; $$out{'fps'} = $fps; $$out{'frc'} = $frc; return 1; } # --- calc_frame_height($width,$height,$ratio,$modulo) ------------------------ # calculate a frame size that respects the given modulo (AVI export) # and has the smallest ratio error # returns: ($new_width,$new_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($opt_ref,$ratio) -------------------------------------------- # generate a list of possible output frame sizes (AVI export) # returns: array of "$w,$h" or empty sub enum_frames { my($opt) = shift; my($ratio) = shift; my($w,$h,$d); my(@res); my($mod) = $$opt{'frame_mod'}; my($range) = $$opt{'width_range'}; my($min) = $$range[0]; my($max) = $$range[1]; my($asrerr) = $$opt{'asrerr_max'}; # loop over all possible widths print_log(" enumerating framesize from $min to $max, modulo $mod\n"); print_log(" pick entries with aspect ratio error <= $asrerr%\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 print_log(sprintf " %dx%d: %4.2g %%",$w,$h,$d); if($d>$asrerr) { print_log(" too large\n"); } else { print_log(" OK\n"); push(@res,"$w,$h"); } } # no width found if(scalar @res == 0) { print_log("ERROR: No suitable resolutions found! (increase asr error?)\n"); } return @res; } # --- find_output_size($inp_ref,$out_ref,$opt_ref) ---------------------------- # automatically determine size of display with square pixels (AVI export) # returns: 0=failed, 1=ok sub find_output_size { my($in) = shift; my($out) = shift; my($opt) = shift; print_log("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_log(" 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]); return 0 if(scalar(@eres)==0); # pick by width if($w!=0) { foreach(@eres) { ($ew,$eh) = split(/,/); if($w==$ew) { push @res,$_; print_log(" picked by given width: $_\n"); } } } # pick by height elsif($h!=0) { foreach(@eres) { ($ew,$eh) = split(/,/); if($h==$eh) { push @res,$_; print_log(" picked by given height: $_\n"); } } } # otherwise use enumerated sizes @res = @eres if(scalar @res == 0); } # --- find an optimal number of CDs --- print_log("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_log(" using forced #CDs: $cd_num and frame size ${w}x$h\n"); } # search else { my($bpprange) = $$opt{'bpp_range'}; my($bppmin) = $$bpprange[0]; my($bppmax) = $$bpprange[1]; 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_log(" listing frame size x #CDs in bpp (avg. bits per pixel)\n"); print_log(" pick entries with bpp in [$bppmin;$bppmax]\n"); print_log(" #CD: "); my(@found); for($cd=0;$cd<$cd_max;$cd++) { print_log(sprintf " %02d ",$cd+1); push @found,[]; } print_log("\n"); print_log(" vrates: "); for($cd=0;$cd<$cd_max;$cd++) { print_log(sprintf " %4d ",$$vid_rate[$cd]); push @found,[]; } print_log("\n"); # --- loop through all sizes --- my($total)=0; foreach(@res) { ($w,$h) = split /,/; print_log(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_log(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_log("** "); } else { print_log(" "); } } else { print_log("-- "); } } print_log("\n"); } if($total==0) { print_log("ERROR: found NO suitable resolution! (use -g and/or -c)\n"); return 0; } 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_log(" 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; return 1; } # --- find_num_cd($out_ref,$opt_ref,$rate,$is_vbr) ----------------------------- # find a suitable number of CDs by judging the comparing the resulting vrate # with the given value (used for VCD/SVCD/DVD) # returns: - sub find_num_cd { my($out) = shift; my($opt) = shift; my($rate) = shift; # given rate my($vbr) = shift; # is vbr mode? print_log("Determining number of CDs for given bitrate...\n"); print_log(sprintf " %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_log(" avg. vrate per #cd: ".join(', ',@$vrate) ." kbps\n -> will use #cd: "); # force cd number if($cd_num>0) { print_log("$cd_num (forced)\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_log("$$out{'cd_num'} (= $$vrate[$$out{'cd_num'}-1] kbps)\n"); } } # --- shrink_input($inp_ref,$shrink_x,$shrink_y) ------------------------------ # enlarge the current clipping region of input by sx and sy # and center the resulting frame # returns: - 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($inp_ref,$out_ref,$opt_ref) ---------------------------- # given: input frame size, aspect and output frame size, aspect # find: place input frame with correct aspect into output frame # add letterbox if necessary # returns: - sub find_frame_trafo { my($in) = shift; my($out) = shift; my($opt) = shift; print_log("Input->output 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($can_lb) = $$out{'can_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; print_log(sprintf " input: %3dx%3d asr: %3.2g:1\n", $in_w,$in_h,$in_aspect); print_log(sprintf " 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_log(" output height: $out_rh (same aspect)\n"); } elsif($can_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_log(" output height: $out_rh (aspect correction)\n"); if($out_rh>$out_h) { print_log(" 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_log(" 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_log(" (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_log(" 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_log(" add letterbox: top=$top, bottom=$bot\n"); $opt .= "-Y -$top,0,-$bot,0 "; } # store final frame transformation for transcode print_log(" transcode param: $opt\n"); $$out{'tc_trafo'} = $opt; } # ============================================================================= # ========== generate_makefile($inp_ref,$out_ref,$opt_ref) ==================== # ============================================================================= # returns: 0=ok, 1=error sub generate_makefile { my($in) = shift; my($out) = shift; my($opt) = shift; my($i,$j); print_log("Generating makefile...\n"); # --- create makefile --- my($project)= $$opt{'project'}; my($mkfile) = "$project.mak"; print_log(" file: '$mkfile'\n"); if(!open(FH,">$mkfile")) { print_log("ERROR: opening makefile $mkfile!\n"); return 0; } # --- write banner --- print FH "# rip *makefile*: $mkfile\n"; print FH "# generated by $ripmake\n"; print FH "# --- config ---\n"; # --- output make variables --- print_log(" emitting make variables...\n"); emit_var_input($in,$out,$opt); emit_var_output($out,$opt); emit_var_acodec($in,$out,$opt); emit_var_vcodec($in,$out,$opt); emit_var_subs($in,$out,$opt); # --- begin rules --- $project = "\$(TARGET)"; my($vcodec) = $$out{'vcodec'}; my($vc_opt) = $$out{'vc_opt'}; my($acodec) = $$out{'acodec'}; my($flavor) = $$opt{'flavor'}; print_log(" flavor: '$flavor'\n vcodec: '$vcodec' options: '$vc_opt'\n acodec: '$acodec'\n"); # --- determine stages for makefile --- my(%make); setup_make($in,$out,$opt,\%make); dump_info("[5]--> Make",\%make) if($debug_level>=5); # --- rules --- print_log(" writing rules...\n"); print_log(" global: all clean\n"); print FH "\n# ----- MAIN RULES -----\n" . "all: sample\n" . "clean: clean-sample clean-rip\n"; emit_sample_rules(\%make); emit_rip_rules(\%make); # --- close file and ready -- close(FH); return 1; } # ========== VARIABLE STUFF =================================================== # --- emit_var_input($in,$out,$opt) ------------------------------------------- # emit input specific variables in makefile # returns: - sub emit_var_input { my($in) = shift; my($out) = shift; my($opt) = shift; # ---- input ---- print FH "# generic input parameters\n"; my($source) = $$opt{'input'}; $source =~ s/ /\\ /g; print FH "SOURCE =$source\n"; print FH "IVCODEC=$$in{'vcodec'}\n"; print FH "IACODEC=$$in{'acodec'}\n"; print FH "INPUTV =-x \$(IVCODEC),null\n"; print FH "INPUTA =-x null,\$(IACODEC)\n"; print FH "INPUTAV=-x \$(IVCODEC),\$(IACODEC)\n"; # ---- input ranges and sources for each cd ---- print FH "\n# input ranges and sources for each target cd\n"; my($range) = int($$out{'fps'}*60); # a minute sample my($cd_num) = $$out{'cd_num'}; print FH "RANGE_SAMPLE=0-$range\n"; # dvd mode - direct select with chapters if($$in{'vcodec'} eq 'dvd') { print FH "TITLE=$$opt{'dvd_title'}\n"; print FH "ANGLE=1\n"; # DVD sample my($sample_chap) = int($$in{'chapters'} / 2); $sample_chap = 1 if($sample_chap==0); print FH "CHAP_SAMPLE=$sample_chap\n"; # DVD chapter range for each CD my($cd_chaps) = $$out{'cd_chaps'}; foreach(1..$cd_num) { my($j) = $_-1; print FH "CHAP_CD$_=$$cd_chaps[$j]\n"; } # sources print FH "SRC_SAMPLE=\$(SOURCE)\n"; foreach(1..$cd_num) { print FH "SRC_CD$_=\$(SOURCE)\n"; } # construct transcode selector print FH "TSEL_SAMPLE=-i \$(SRC_SAMPLE)" . " -T \$(TITLE),\$(CHAP_SAMPLE),\$(ANGLE)" . " -c \$(RANGE_SAMPLE)\n"; foreach(1..$cd_num) { print FH "TSEL_CD$_=-i \$(SRC_CD$_)" . " -T \$(TITLE),\$(CHAP_CD$_),\$(ANGLE)\n"; } } # avi input mode else { # ranges my($cd_ranges) = $$out{'cd_ranges'}; foreach(1..$cd_num) { print FH "RANGE_CD$_=$$cd_ranges[$_-1]\n"; } if($cd_num>1) { # sources my($mid) = int($cd_num/2)+1; if($$opt{'split_avi'}) { print FH "SRC_SAMPLE=\$(TARGET)-SRC-CD$mid.avi\n"; } else { print FH "SRC_SAMPLE=\$(SOURCE)\n"; } foreach(1..$cd_num) { if($$opt{'split_avi'}) { print FH "SRC_CD$_=\$(TARGET)-SRC-CD$_.avi\n"; } else { print FH "SRC_CD$_=\$(SOURCE)\n"; } } } else { # single cd source/selectors print FH "SRC_SAMPLE=\$(SOURCE)\n" . "SRC_CD1=\$(SOURCE)\n"; } # selectors print FH "TSEL_SAMPLE=-i \$(SRC_SAMPLE) -c \$(RANGE_SAMPLE)\n"; foreach(1..$cd_num) { if($$opt{'split_avi'}) { print FH "TSEL_CD$_=-i \$(SRC_CD$_)\n"; } else { print FH "TSEL_CD$_=-i \$(SRC_CD$_) -c \$(RANGE_CD$_)\n"; } } } } # --- emit_var_output($out,$opt) ---------------------------------------------- # emit output specific variables in makefile # returns: - sub emit_var_output { my($out) = shift; my($opt) = shift; # ---- video parameter ---- print FH "\n# video param\n"; print FH "TRAFO=$$out{'tc_trafo'}\n"; my($vkbps) = $$out{'cd_vrate'}; my($cd_num) = $$out{'cd_num'}; foreach(1..$cd_num) { my($j) = $_-1; my($sum) = $$vkbps[$j] + $$out{'aud_sum_rate'}; print FH "VRATE_CD$_=$$vkbps[$j]\n"; print FH "SRATE_CD$_=$sum\n"; } my($sum) = $$vkbps[0] + $$out{'aud_sum_rate'}; print FH "VRATE_SAMPLE=$$vkbps[0]\n"; print FH "SRATE_SAMPLE=$sum\n"; # ---- audio parameter ---- my($aud_track) = $$opt{'aud_track'}; my($aud_rate) = $$opt{'aud_rate'}; my($aud_num) = $$opt{'aud_num'}; my($aud_vbr) = $$opt{'aud_vbr_qual'}; print FH "\n# audio param\n"; for($i=0;$i<$aud_num;$i++) { print FH "ATRACK_T$i=$$aud_track[$i]\n"; print FH "ARATE_T$i=$$aud_rate[$i]\n"; print FH "AVBR_T$i=" . (($$aud_vbr[$i]>0)?1:0) . "\n"; print FH "AVBR_QUAL_T$i=" . (($$aud_vbr[$i]>0)?$$aud_vbr[$i]:5) . "\n"; } # ---- output ---- print FH "\n# output\n"; print FH "TARGET =$$opt{'project'}\n"; print FH "OVCODEC =$$out{'vcodec'}\n"; print FH "OACODEC =$$out{'acodec'}\n"; print FH "OUTPUTV =-y \$(OVCODEC),null\n"; print FH "OUTPUTA =-y null,\$(OACODEC)\n"; print FH "OUTPUTAV=-y \$(OVCODEC),\$(OACODEC)\n"; # ----- output extensions ----- print FH "\n# output extensions\n"; # --- video extension my($flavor) = $$opt{'flavor'}; if($flavor eq 'vcd') { print FH "OVC_EXT=.m1v\n"; } elsif(($flavor eq 'svcd')||($flavor eq 'dvd')){ print FH "OVC_EXT=.m2v\n"; } else { print FH "OVC_EXT=-v.avi\n"; } # --- audio extension my($oac) = $$out{'acodec'}; if($oac eq 'mp2enc') { print FH "OAC_EXT=.mpa\n"; } elsif($oac eq 'toolame') { print FH "OAC_EXT=.mp2\n"; } elsif($oac eq 'raw') { print FH "OAC_EXT=.avi\n"; } elsif($oac eq 'ogg') { print FH "OAC_EXT=.ogg\n"; } # --- mux extension if(($flavor eq 'svcd')||($flavor eq 'vcd')) { print FH "MUX_EXT=.mpg\n"; print FH "BIN_EXT=.bin\n"; print FH "CUE_EXT=.cue\n"; } elsif($flavor eq 'dvd') { print FH "MUX_EXT=.mpg\n"; print FH "IMG_EXT=.img\n"; } elsif($flavor eq 'avi') { print FH "MUX_EXT=.avi\n"; } elsif($flavor eq 'ogm') { print FH "MUX_EXT=.ogm\n"; } elsif($flavor eq 'mkv') { print FH "MUX_EXT=.mkv\n"; } # --- filters --- print FH "\n# user transcode options for video/audio\n"; if($$opt{'tc_vopt'} ne '') { print FH "V_OPT=$$opt{'tc_vopt'}\n"; } else { print FH "# use V_OPT to add video options\n"; } if($$opt{'tx_aopt'} ne '') { print FH "A_OPT=$$opt{'tc_aopt'}\n"; } else { print FH "# use A_OPT to add audio options\n"; } # --- menu mpgs --- my($menu_mpgs) = $$out{'menu_mpgs'}; if(defined($menu_mpgs)) { print FH "\n# mpeg files for menus\n" . "MENU_MPGS=" . join(' ',@$menu_mpgs) . "\n" . "GENMENU_OPTS=-c\n"; } } # --- emit_var_acodec($in,$out,$opt) ------------------------------------------ # setup parameter make variables for audio codec # returns: - sub emit_var_acodec { my($in) = shift; my($out) = shift; my($opt) = shift; # fetch input and output sample rate my(@aud_isamp_rate); my(@aud_osamp_rate); my(@aud_isamp_chan); my($aud_track) = $$opt{'aud_track'}; my($aud_num) = $$opt{'aud_num'}; for($i=0;$i<$aud_num;$i++) { 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]; my($icr) = $$in{'aud_chan'}; push @aud_isamp_chan,$$icr[$$aud_track[$i]]; } # audio codec params print FH "\n# acodec params\n"; for($i=0;$i<$aud_num;$i++) { print FH "# channel $i\n"; print FH "CODERA_T$i=-a \$(ATRACK_T$i) " . " -b \$(ARATE_T$i),\$(AVBR_T$i),\$(AVBR_QUAL_T$i)"; if($aud_isamp_rate[$i]!=$aud_osamp_rate[$i]) { my($mod) = ''; $mod = "-J resample" if ($$out{'acodec'} ne 'toolame'); print FH " -E $aud_osamp_rate[$i] --a52_dolby_off $mod"; } print FH "\n"; # audio scaling options if($aud_isamp_chan[$i]!=1) { print FH "# auto scaling\n"; print FH "SCL_T${i}_SAMPLE=\$(TARGET)-SAMPLE-a$i.scl\n"; print FH "SCL_T${i}_SAMPLE_CREATE=-J astat=\$(SCL_T${i}_SAMPLE)\n"; print FH "SCL_T${i}_SAMPLE_USE=-s `cat \$(SCL_T${i}_SAMPLE)`\n"; foreach(1..$$out{'cd_num'}) { print FH "SCL_T${i}_CD$_=\$(TARGET)-CD$_-a$i.scl\n"; print FH "SCL_T${i}_CD${_}_CREATE=-J astat=\$(SCL_T${i}_CD$_)\n"; print FH "SCL_T${i}_CD${_}_USE=-s `cat \$(SCL_T${i}_CD$_)`\n"; } } } } # --- emit_var_vcodec($in,$out,$opt) ------------------------------------------ # setup parameter make variables for video codec # returns: - sub emit_var_vcodec { my($in) = shift; my($out) = shift; my($opt) = shift; # generic video setup print FH "\n# vcodec params\n"; print FH "IN_FPS=$$in{'fps'}\n"; print FH "IN_FRC=$$in{'frc'}\n"; print FH "OUT_FPS=$$out{'fps'}\n"; print FH "OUT_FRC=$$out{'frc'}\n"; print FH "CODERV=\$(TRAFO) -V -f \$(IN_FPS),\$(IN_FRC) --export_fps \$(OUT_FPS),\$(OUT_FRC)\n"; print FH "CODERV+= --pulldown\n" if($$out{'pulldown'}==1); print FH "ifneq \"\$(IN_FRC)\" \"\$(OUT_FRC)\"\n" . " CODERV+= -Jfps\n" . "endif\n"; print FH "CODERV+= -F \"$$out{'vc_opt'}\"\n" if($$out{'vc_opt'} ne ''); # ---- codec specific audio/video params ----- # pick a deinterlacer my($interlaced) = $$opt{'interlaced'}; my($deinter) = ''; $deinter = '-I 3' if($interlaced); # avi my($flavor) = $$opt{'flavor'}; if(($flavor eq 'avi')||($flavor eq 'ogm')||($flavor eq 'mkv')) { my($extra)=''; if($deinter ne '') { print FH "# avi output\n"; print FH "CODERV+=$deinter\n"; } } # svcd,vcd else { # output aspect code my($asr) = 2; $asr = 3 if($$out{'anamorph'}==1); # ----- mpeg2enc encoder ----- my($mode); if($$out{'vcodec'} eq 'mpeg2enc') { print FH "# mpeg2enc\n"; my($extra)=''; # add sequence headers every gop # WORKAROUND: forbid splitting my($flags)="-s -S 9999"; # hq encoder settings if($$opt{'high_quality'}) { $flags .= " -4 1 -2 1"; } # special vcd stuff if($flavor eq 'vcd') { if($$opt{'vcd_conform'}==1) { $mode=1 } else { # XVCD $mode=2; if($$opt{'vcd_min_vbr_quant'}>0) { $flags .= " -q $$opt{'vcd_min_vbr_quant'}"; } if($$opt{'vcd_video_buf'}>0) { $flags .= " -V $$opt{'vcd_video_buf'}"; } } if($interlaced==1) { $extra .= $deinter; # use deinterlacer } } # special svcd stuff elsif($flavor eq 'svcd') { if($$opt{'svcd_conform'}==1) { # HACK: use mode 5 here, too otherwise bitrate selection won't work! $mode=5; } else { # XSVCD $mode=5; if($$opt{'svcd_min_vbr_quant'}>0) { $flags .= " -q $$opt{'svcd_min_vbr_quant'}"; } if($$opt{'svcd_video_buf'}>0) { $flags .= " -V $$opt{'svcd_video_buf'}"; } } if($interlaced==1) { $flags = '-I1'; # encode interlaced directly } } # special DVD stuff elsif($flavor eq 'dvd') { # DVD MPEG 2 for dvdauthor $mode = 8; if($$opt{'dvd_min_vbr_quant'}>0) { $flags .= " -q $$opt{'dvd_min_vbr_quant'}"; } if($$opt{'dvd_video_buf'}>0) { $flags .= " -V $$opt{'dvd_video_buf'}"; } if($interlaced==1) { $flags = '-I1'; # encode interlaced directly } } # build coder options print FH "CODERV+=-F \"$mode,$flags\" --export_asr $asr $extra\n"; } # ----- bbmpeg - 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 $asr $deinter\n"; } } } # ========== RULE STUFF ======================================================= # --- setup_make($in,$out,$opt,$make) ----------------------------------------- # build $make hash with values required for makefile generation # returns: - sub setup_make { my($in) = shift; my($out) = shift; my($opt) = shift; my($mak) = shift; my($flavor) = $$opt{'flavor'}; # does flavor need mastering my($do_master) = (($flavor eq 'vcd')||($flavor eq 'svcd')||($flavor eq 'dvd')); # if the input is not dvd then split it up my($do_split) = (($$in{'vcodec'} ne 'dvd')&&($$out{'cd_num'}>1))? $$opt{'split_avi'}:0; # set values $$mak{'do_master'} = $do_master; $$mak{'do_split'} = $do_split; # derive values for makefile $$mak{'aud_num'} = $$opt{'aud_num'}; $$mak{'cd_num'} = $$out{'cd_num'}; $$mak{'flavor'} = $$opt{'flavor'}; $$mak{'cd_ranges'} = $$out{'cd_ranges'} if(defined($$out{'cd_ranges'})); $$mak{'xml'} = ($$in{'vcodec'} eq 'dvd')?1:0; $$mak{'menu'} = $$opt{'chap_menu'}; $$mak{'slim'} = $$opt{'low_space'}; # vcd/svcd/dvd setup $$mak{'vcd_std'} = $$opt{'vcd_conform'}; $$mak{'vcd_vbr'} = ($$opt{'vcd_min_vbr_quant'}>0)?1:0; $$mak{'vcd_buf'} = $$opt{'vcd_video_buf'}; $$mak{'svcd_std'} = $$opt{'svcd_conform'}; $$mak{'svcd_vbr'} = ($$opt{'svcd_min_vbr_quant'}>0)?1:0; $$mak{'svcd_buf'} = $$opt{'svcd_video_buf'}; $$mak{'dvd_vbr'} = ($$opt{'dvd_min_vbr_quant'}>0)?1:0; $$mak{'dvd_buf'} = $$opt{'dvd_video_buf'}; $$mak{'mpeg_mplex'}= $$opt{'mpeg_mplex'}; # subs setup $$mak{'sub_num'} = $$opt{'sub_num'}; $$mak{'sub_dvd'} = $$in{'vcodec'} eq 'dvd'; } # --- get_video_name($mak,$tgt) ----------------------------------------------- # return the name of the generated video file for a target sub get_video_name { my($mak) = shift; my($tgt) = shift; return "\$(TARGET)-$tgt\$(OVC_EXT)"; } # --- get_audio_name($mak,$tgt,$chan) ----------------------------------------- # return the name of the generated audio file for a target sub get_audio_name { my($mak) = shift; my($tgt) = shift; my($chan) = shift; return "\$(TARGET)-$tgt-a$chan\$(OAC_EXT)"; } # --- get_all_audio_names($mak,$tgt) ------------------------------------------ # return all generated audio names sub get_all_audio_names { my($mak) = shift; my($tgt) = shift; my($j); my(@names); for($j=0;$j<$$mak{'aud_num'};$j++) { my($name) = get_audio_name($mak,$tgt,$j); push @names,$name if(defined($name)); } return join(' ',@names); } # --- get_mux_name($mak,$tgt) ------------------------------------------------- # return the filename after multiplexing sub get_mux_name { my($mak) = shift; my($tgt) = shift; return "\$(TARGET)-$tgt\$(MUX_EXT)"; } # --- get_master_name($mak,$tgt) ---------------------------------------------- # return the filenames generated after mastering sub get_master_name { my($mak) = shift; my($tgt) = shift; if($$mak{'flavor'} eq 'dvd') { return "\$(TARGET)-$tgt\$(IMG_EXT)"; } else { return "\$(TARGET)-$tgt\$(BIN_EXT) \$(TARGET)-$tgt\$(CUE_EXT)"; } } # --- get_final_name ---------------------------------------------------------- # returns: filenames of rip result separated by spaces sub get_final_name { my($mak) = shift; my($tgt) = shift; my($master) = shift; my($names); # return the filename after mastering if($master) { $names = get_master_name($mak,$tgt); return $names if(defined($names)); } # return the filename after muxing $names = get_mux_name($mak,$tgt); return $names; } # --- emit_sample_rules($mak) ------------------------------------------------- sub emit_sample_rules { my($mak) = shift; # get the resulting name my($final) = get_final_name($mak,"SAMPLE",0); print_log(" sample: sample clean-sample\n"); # have subtitles? my($have_subs) = ($$mak{'sub_num'}>0); my($sub_rules) = ($have_subs)?"sub-SAMPLE":""; # write sample rule line print FH "\n# ========== SAMPLE ==========\n"; print FH "# render a one minute sample of the movie\n"; print FH "sample: $sub_rules mux-SAMPLE\n"; print FH "\t\@echo \"--- sample ready! ---\"\n"; print FH "\t\@ls -l \"$final\"\n"; print FH "\t\@echo $final\n"; # cleanup rule print FH "\nclean-sample: " . "clean-video-SAMPLE clean-audio-SAMPLE clean-mux-SAMPLE\n"; # write video rule write_video_rule($mak,"SAMPLE"); write_audio_rules($mak,"SAMPLE"); write_mux_rule($mak,"SAMPLE"); write_sub_rule($mak,"SAMPLE") if($have_subs); } # --- emit_rip_rules($mak) ---------------------------------------------------- # returns: - sub emit_rip_rules { my($mak) = shift; # have subtitles? my($have_subs) = ($$mak{'sub_num'}>0); # have menu? my($have_menu) = ($$mak{'menu'}>0); # get result names my($cd_num) = $$mak{'cd_num'}; my(@names); my(@rips); my(@crips); # add (global9 menu creation my($global) = ""; my($cglobal) = ""; if($have_menu) { $global = "menu"; $cglobal = "clean-menu"; } # now add rules for each CD foreach(1..$cd_num) { push @rips,"rip-CD$_"; push @crips,"clean-rip-CD$_"; push @names,get_final_name($mak,"CD$_",$$mak{'do_master'}); } my($rips) = join(' ',@rips); my($crips) = join(' ',@crips); my($names) = join(' ',@names); # main rule print_log(" rip: rip $rips clean-rip $crips\n"); print FH "\n# ========== RIP RULES ==========\n" ."\nrip: $global $rips\n" . "\t\@echo \"--- rip ready! ---\"\n" . "\t\@ls -l $names\n" . "\t\@echo \"$names\"\n" ."\nclean-rip: $cglobal $crips\n"; # CD rip rules foreach(1..$cd_num) { my($j) = $_ - 1; my($subs) = ($have_subs)?"sub-CD$_":""; my($csubs)= ($have_subs)?"clean-sub-CD$_":""; if($$mak{'do_master'}) { print FH "\n$rips[$j]: $subs master-CD$_\n" . "\n$crips[$j]: $csubs clean-video-CD$_ clean-audio-CD$_ clean-mux-CD$_ clean-master-CD$_\n"; } else { print FH "\n$rips[$j]: $subs mux-CD$_\n" . "\n$crips[$j]: $csubs clean-video-CD$_ clean-audio-CD$_ clean-mux-CD$_\n"; } } # split source avi if requested write_split_rule($mak) if($$mak{'do_split'}); # write menu generation rule write_menu_rule($mak) if($have_menu); # write rules for each cd foreach(1..$cd_num) { write_video_rule($mak,"CD$_"); write_audio_rules($mak,"CD$_"); write_mux_rule($mak,"CD$_"); write_mastering_rule($mak,"CD$_") if($$mak{'do_master'}); write_sub_rule($mak,"CD$_") if($have_subs); } } # --- write_split_rule($mak,$tgt) --------------------------------------------- # split an input AVI file into CD chunks # returns: - sub write_split_rule { my($mak) = shift; my($cd_num) = $$mak{'cd_num'}; my($cd_ranges) = $$mak{'cd_ranges'}; # create names of splitted AVIs my(@names); foreach(1..$cd_num) { push @names,"\$(TARGET)-SRC-CD$_.avi"; } # split input AVI with avisplit # NOTE: unfortunately this does not work always !! print FH "\n# ----- splitting AVI source -----\n"; foreach(1..$cd_num) { print FH "\nsplit-SRC-CD$_ $names[$_-1]: \$(INPUT)\n" . "\t\@echo \"--- split-SRC-CD$_ ---\"\n" . "\tavisplit -s 0 -t \$(RANGE_CD$_) \\\n" . "\t\t-i \$(SOURCE) \\\n" . "\t\t-o temp\n" . "\t\tmv temp-0000 $names[$_-1]\n"; } } # --- write_video_rule($mak,$tgt) --------------------------------------------- # write a rule for generating a video stream # and the audio scale value for the first channel # returns: - sub write_video_rule { my($mak) = shift; my($tgt) = shift; # target: SAMPLE, CD1, CD2, MAIN ... my($name) = get_video_name($mak,$tgt); my($flavor) = $$mak{'flavor'}; my($log) = ""; print FH "\n# ----- video rules of $tgt -----\n" . "\nvideo-$tgt: $name\n"; # command for subtitle integration my($subcmd) = "\$(RENDER_SUBS)"; # ----- avi,ogm,mkv - render video into an avi container if(($flavor eq 'avi')||($flavor eq 'ogm')||($flavor eq 'mkv')) { # the 2 pass log file $log = "\$(TARGET)-$tgt-v.log"; # --- pass1 : video analyse + audio 0 scale print FH "\nvideo-pass1-$tgt $log: \$(SRC_$tgt)\n"; print FH "\t\@echo \"--- video-pass1-$tgt ---\"\n"; print FH "\ttranscode \$(TSEL_$tgt) \$(INPUTAV) \$(OUTPUTV) \\\n" . "\t\t\$(CODERV) \\\n" . "\t\t\$(CODERA_T0) \\\n" . "\t\t-w \$(VRATE_$tgt) \$(V_OPT) \\\n" . "\t\t-R 1,$log \\\n" . "\t\t\$(SCL_T0_${tgt}_CREATE) \\\n" . "\t\t$subcmd\n"; # --- pass2 : video render + audio 0 render print FH "\nvideo-pass2-$tgt $name: $log \$(SRC_$tgt) \$(SCL_T0_$tgt)\n"; print FH "\t\@echo \"--- video-pass2-$tgt ---\"\n"; print FH "\ttranscode \$(TSEL_$tgt) \$(INPUTV) \$(OUTPUTV) \\\n" . "\t\t\$(CODERV) \\\n" . "\t\t-o $name -w \$(VRATE_$tgt) \$(V_OPT) \\\n" . "\t\t-R 2,$log \\\n" . "\t\t$subcmd\n"; } # ----- svcd,vcd,dvd else { print FH "\n$name: \$(SRC_$tgt)\n"; print FH "\t\@echo \"--- video-$tgt ---\"\n"; print FH "\ttranscode \$(TSEL_$tgt) \$(INPUTAV) \$(OUTPUTV) \\\n" . "\t\t\$(CODERV) \\\n" . "\t\t\$(CODERA_T0) \\\n" . "\t\t-o \$(TARGET)-$tgt\\\n" . "\t\t-w \$(VRATE_$tgt) \$(V_OPT) \\\n" . "\t\t\$(SCL_T0_${tgt}_CREATE) \\\n" . "\t\t$subcmd\n"; } # cleanup rule print FH "\nclean-video-$tgt:\n" . "\t-rm -f $name $log\n"; } # --- write_audio_rules($mak,$addon) ------------------------------------------ # write scale calc and audio render rules for all requested channels # returns: - sub write_audio_rules { my($mak) = shift; my($tgt) = shift; # target: SAMPLE, CD1, CD2, MAIN... my($flavor) = $$mak{'flavor'}; # main audio rule print FH "\n# ----- audio rules for $tgt -----\n" . "\naudio-$tgt: " . join(' ',map { "audio$_-$tgt" } (0.. $$mak{'aud_num'}-1)) . "\n"; # cleanup rule print FH "\nclean-audio-$tgt: " . join(' ',map { "clean-audio$_-$tgt" } (0.. $$mak{'aud_num'}-1)) . "\n"; my($j); # channel loop for($j=0;$j<$$mak{'aud_num'};$j++) { my($scl) = "\$(TARGET)-$tgt-a$j.scl"; my($audio) = get_audio_name($mak,$tgt,$j); # main channel audio rule print FH "\naudio$j-$tgt: $audio\n"; # first fetch the audio scale value print FH "\n$scl: \$(SRC_$tgt)\n" . "\t\@echo \"--- audio$j-scl-$tgt ---\"\n" . "\ttranscode \$(TSEL_$tgt) \$(INPUTA) \\\n" . "\t\t\$(CODERA_T$j) \$(A_OPT) \\\n" . "\t\t\$(SCL_T${j}_${tgt}_CREATE)\n"; # render audio track print FH "\n$audio: \$(SCL_T${j}_$tgt) \$(SRC_$tgt)\n" . "\t\@echo \"--- audio$j-$tgt ---\"\n"; # audio render commands # WORKAROUND for raw+avi container + mp2/mpa-no-extension workaround print FH "ifeq (\$(OACODEC),raw)\n" . "\ttranscode \$(TSEL_$tgt) \$(INPUTA) -y raw \\\n" . "\t\t\$(CODERA_T$j) \$(A_OPT) \\\n" . "\t\t\$(SCL_T${j}_${tgt}_USE) \\\n" . "\t\t-g 0x0 -o $audio\n" . "else\n" . "\ttranscode \$(TSEL_$tgt) \$(INPUTA) \$(OUTPUTA) \\\n" . "\t\t\$(CODERA_T$j) \$(A_OPT) \\\n" . "\t\t\$(SCL_T${j}_${tgt}_USE) \\\n" . "\t\t\-m `echo $audio | sed -e 's/\\.mp.\$\$//'`\n" . "endif\n"; # cleanup rule print FH "\nclean-audio$j-$tgt:\n" . "\t-rm -f $scl $audio\n"; } } # --- write_mux_rule($mak,$tgt) ----------------------------------------------- # multiplexing # returns: - sub write_mux_rule { my($mak) = shift; my($tgt) = shift; my($flavor) = $$mak{'flavor'}; my($aud_num) = $$mak{'aud_num'}; my($name) = get_mux_name($mak,"$tgt"); my($video) = get_video_name($mak,$tgt); my($audio) = get_all_audio_names($mak,"$tgt"); print FH "\n# ----- multiplexing rule of $tgt -----\n"; # conserve space in slim mode if($$mak{'slim'}) { print FH "\nmux-$tgt: $name clean-video-$tgt clean-audio-$tgt\n" . "\n$name: video-$tgt audio-$tgt\n"; } else { print FH "\nmux-$tgt: $name\n" . "\n$name: $video $audio\n"; } print FH "\t\@echo \"--- mux-$tgt ---\"\n"; # ----- avi if($flavor eq 'avi') { # mux all audio tracks by pairwise avimerge calls my($a); my($input) = $video; for($a=0;$a<$aud_num;$a++) { my($output); if($a == $aud_num-1) { $output = $name; } else { $output = "\$(TARGET)-$tgt-m" . $a . ".avi"; } my($afile) = "\$(TARGET)-$tgt-a" . $a . ".avi"; print FH "\tavimerge -i $input \\\n" . "\t\t -p $afile \\\n" . "\t\t -o $output\n"; print FH "\trm $input\n" if($input ne $video); $input = $output; } } # ----- ogm elsif($flavor eq 'ogm') { print FH "\togmmerge -o $name \\\n" . "\t\t$video \\\n" . "\t\t$audio\n"; } # ----- mkv elsif($flavor eq 'mkv') { print FH "\tmkvmerge -o $name \\\n" . "\t\t$video \\\n" . "\t\t$audio\n"; } # ----- svcd,vcd,dvd else { if($$mak{'mpeg_mplex'} eq 'mplex') { # --- mplex support --- my($extra) = ''; my($mode) = 0; # dvd if($flavor eq 'dvd') { $mode = 8; # MPEG2 DVD $extra = "-r \$(SRATE_$tgt)"; $extra .= ' -V' if($$mak{'dvd_vbr'}); $extra .= " -b $$mak{'dvd_buf'}" if($$mak{'dvd_buf'}>0); } # svcd elsif($flavor eq 'svcd') { if($$mak{'svcd_std'}==1) { $mode = 4; # standard SVCD } else { $mode = 5; # user mode SVCD $extra = "-r \$(SRATE_$tgt)"; $extra .= ' -V' if($$mak{'svcd_vbr'}); $extra .= " -b $$mak{'svcd_buf'}" if($$mak{'svcd_buf'}>0); } } # vcd else { if($$mak{'vcd_std'}==1) { $mode = 1; # standard VCD } else { $mode = 2; # user mode vcd $extra = "-r \$(SRATE_$tgt)"; $extra .= ' -V' if($$mak{'vcd_vbr'}); $extra .= " -b $$mak{'vcd_buf'}" if($$mak{'vcd_buf'}>0); } } print FH "\tmplex -f $mode $extra\\\n" . "\t\t-o $name \\\n" . "\t\t$video\\\n" . "\t\t$audio\n"; } else { # --- tcmplex support --- my(@auds) = split(/\s+/,$audio); my($mode); if($flavor eq 'svcd') { $mode = "-m s"; } else { $mode = "-m 1"; } my($aud) = ''; $aud .= "-p $auds[0] " if(defined($auds[0])); $aud .= "-s $auds[1]" if(defined($auds[1])); print FH "\ttcmplex $mode\\\n" . "\t\t-o $name\\\n" . "\t\t-i $video\\\n" . "\t\t$aud\n"; } } # cleanup rule print FH "\nclean-mux-$tgt:\n" . "\t-rm -f $name\n"; } # --- write_mastering_rule($mak,$tgt) ----------------------------------------- # build SVCD/VCD disc images # returns: - sub write_mastering_rule { my($mak) = shift; my($tgt) = shift; my($mux) = get_mux_name($mak,$tgt); my($master) = get_master_name($mak,$tgt); my($flavor) = $$mak{'flavor'}; # part numbers starting from 1! my($out) = "-c \$(TARGET)-$tgt.cue \\\n\t\t -b \$(TARGET)-$tgt.bin"; print FH "\n# ----- mastering rule of $tgt -----\n"; # conserver space in slim mode if($$mak{'slim'}) { print FH "\nmaster-$tgt: $master clean-mux-$tgt\n" . "\n$master: mux-$tgt\n" } else { print FH "\nmaster-$tgt: $master\n" . "\n$master: $mux\n"; } print FH "\t\@echo \"--- master-$tgt ---\"\n"; # --- dvd --- my($dvddir); if($flavor eq 'dvd') { $dvddir = "\$(TARGET)-$tgt.imgdir"; print FH "\tdvdauthor -o $dvddir $mux\n"; print FH "\tdvdauthor -T -o $dvddir\n"; print FH "\tmkisofs -dvd-video -o $master $dvddir\n"; } # --- vcd+svcd --- else { # XML is used for DVD input (created by chaplin) if($$mak{'xml'}) { print FH "\tvcdxbuild $out \\\n\t\t \$(TARGET)-$tgt.xml\n"; } else { my($opts) = ""; if($$mak{'flavor'} eq 'svcd') { $opts .= "-t svcd --update-scan-offsets"; } print FH "\tvcdimager $opts $out \\\n\t\t$mux\n"; } } # cleanup rule print FH "\nclean-master-$tgt:\n" . "\t-rm -f $master\n"; if($dvddir ne '') { print FH "\trm -rf $dvddir\n"; } } # --- write_menu_rule($mak) --------------------------------------------------- # create chapter menus with a global call to chaplin-genmenu # returns: - sub write_menu_rule { my($mak) = shift; print FH "\n# ----- menu creation -----\n"; print FH "\nmenu: \$(MENU_MPGS)\n" . "\n\$(MENU_MPGS): \$(TARGET)-menu.txt\n" . "\t\@echo \"--- chapter menu creation ---\"\n" . "\tchaplin-genmenu \$(GENMENU_OPTS) \$(TARGET)-menu.txt\n"; print FH "\nclean-menu:\n" . "\trm -f \$(MENU_MPGS)\n"; } # # ===== subtitle stuff ======================================================= # # --- emit_var_subs ---------------------------------------------------------- sub emit_var_subs { my($in) = shift; my($out) = shift; my($opt) = shift; # ---- subtitle parameter ---- my($sub_track) = $$opt{'sub_track'}; my($sub_num) = $$opt{'sub_num'}; return if($sub_num == 0); print FH "\n# --- subtitle params ---\n"; # ----- DVD subs ----- if($$in{'vcodec'} eq 'dvd') { print FH "# dvd sub channels\n"; my($num) = 0; foreach(@$sub_track) { print FH sprintf("SUBID_T%d = %u\n",$num,$_); print FH sprintf("SUBCHN_T%d = 0x%x\n",$num,$_ + 0x20); $num++; } print FH "# dvd chapter begin\n"; my($chap_start) = $$in{'chap_start'}; $num = 1; foreach(@$chap_start) { print FH sprintf("CHAP%d_START = %s\n",$num,$_); $num++; } # DVD burn in subs - currently only one track print FH "# burn-in subtitles\n"; print FH "RENDER_SUBS=-J extsub=\"\$(SUBID_T0)\"\n"; } } # --- write_sub_rule($mak,$tgt) ------------------------------------------ sub write_sub_rule { my($mak) = shift; my($tgt) = shift; my($sub_num) = $$mak{'sub_num'}; my($sub_dvd) = $$mak{'sub_dvd'}; return if($sub_num==0); # common rule for all subs: process each subtitle print FH "\n# ----- subtitle rules for $tgt -----\n"; print FH "\nsub-$tgt: "; foreach(0 .. $sub_num-1) { print FH "sub$_-$tgt "; } print FH "\n\nclean-sub-$tgt: "; foreach(0 .. $sub_num-1) { print FH "clean-sub$_-$tgt "; } print FH "\n"; # ----- gen DVD subs ----- if($sub_dvd) { # now for each sub title foreach(0 .. $sub_num-1) { # we use a working directory for each subtitle my($dir) = "\$(TARGET)-$tgt.sub$_"; my($ppml) = "$dir/sub.ppml"; my($srtx) = "$dir/sub.srtx"; # the subtitle generation rule print FH "\n# --- subtitle #$_ for $tgt ---\n"; print FH "\nsub$_-$tgt: #$ppml\n"; print FH "\n$srtx: $dir\n" . "\ttccat -i \$(SRC_$tgt) -T \$(TITLE),\$(CHAP_$tgt),\$(ANGLE) | \\\n" . "\ttcextract -x ps1 -t vob -a \$(SUBCHN_T$_) | \\\n" . "\tsubtitle2pgm -c 255,0,128,128 -o $dir/sub\n" . "\tfor a in $dir/*.pgm ; do b=`echo \$\$a | sed -e 's/pgm/ppm/'`; convert \$\$a \$\$b ; done\n"; print FH "\n$dir:\n\tmkdir $dir\n"; print FH "\n$ppml: $srtx\n" . "\tsrtx2ppml -o \$(CHAP\$(CHAP_$tgt)_START) -f \$(OUT_FPS) < $srtx > $ppml\n"; # clean up rule print FH "\nclean-sub$_-$tgt:\n" . "#\trm -rf $dir\n"; } } }