#!/usr/bin/env perl # devede_mencoder - Wrapper that runs main "mencoder" executable # License: BSD-style (for this file only) # Revision: 090130 #--------------------------------------------------------------------- # overview #--------------------------------------------------------------------- # "devede_mencoder" is a wrapper for "mencoder" that's designed speci- # fically for use by "devede". This wrapper fixes various audio-video # synchronization problems that occur with the standard version of # "devede". # Note: This program uses "ffmpeg", "mplayer", "mplex", and "sox" (in # addition to "mencoder") to handle various steps. #--------------------------------------------------------------------- # module setup #--------------------------------------------------------------------- require 5.8.1; use strict; use Carp; use warnings; # Trap warnings $SIG {__WARN__} = sub { die @_; }; #--------------------------------------------------------------------- # basic constants #--------------------------------------------------------------------- use constant ZERO => 0; # Zero use constant ONE => 1; # One use constant FALSE => 0; # Boolean FALSE use constant TRUE => 1; # Boolean TRUE #--------------------------------------------------------------------- # misc. tables #--------------------------------------------------------------------- # @ExternalPrograms lists the names (without directory components) of # some of the external programs used by this program. This array is # used by code which verifies that the external programs are avail- # able. my @ExternalPrograms = qw (ffmpeg mencoder mplayer mplex sox); #--------------------------------------------------------------------- # support routines #--------------------------------------------------------------------- # This routine verifies that the external programs required by this # program are accessible via PATH. # If a required external program appears to be missing, this routine # prints an error message and terminates the current program. Other- # wise, this routine simply returns. #--------------------------------------------------------------------- sub check_programs { my $str; # Scratch for my $program (sort @ExternalPrograms) { my $found = FALSE; my $PATH = $ENV {PATH}; $PATH = "" unless defined $PATH; for my $dir (split (/:+/, $PATH)) { next unless -d $dir; $dir =~ s@([^/])\z@$1/@; $str = "$dir$program"; next unless (-f $str) && (-r $str) && (-x $str); $found = TRUE; last; } die "Error: Required program not found via PATH: $program\n" unless $found; } undef; } #--------------------------------------------------------------------- # This routine checks to see if "devede_mencoder" was invoked by a re- # cognized "devede" video-file creation operation. If so, this routine # tries to perform the requested operation using a modified approach. # The modified approach involves multiple steps, and it isn't guaran- # teed to work correctly, but it improves the chances that the output # file produced by the specified operation will contain synchronized # audio and video. After the alternate steps are completed, this rou- # tine terminates "devede_mencoder". # If "devede_mencoder" wasn't invoked by a recognized "devede" video- # file creation operation, this routine simply returns. #--------------------------------------------------------------------- sub devede_kludge { my $idx1 = -1; my $idx2 = -1; my $idx3 = -1; my $TestFlag1 = FALSE; my $TestFlag2 = FALSE; my $TestFlag3 = FALSE; my $TestFlag4 = FALSE; my $TestFlag5 = FALSE; my $TestFlag6 = FALSE; my @VIDEXTPAT = qw (asf avi flv m2v m4v mov mp4 mpe?g qtw? rm vob wmv); my $VIDEXTPAT = join '|', @VIDEXTPAT; for my $ii (ZERO..$#ARGV) { my $arg = $ARGV [$ii]; my $jj = $ii + ONE; my $nexta = $ARGV [$jj]; $nexta = "" unless defined $nexta; $idx1 = $jj if ($arg eq '-of' ) && ($nexta eq 'mpeg' ); $idx2 = $jj if ($arg eq '-o' ) && ($nexta =~ m@\.mpg\z@ ); if (($ii != $idx2) && ($arg =~ m@\.($VIDEXTPAT)\z@i)) { return if $idx3 >= ZERO; $idx3 = $ii; } $TestFlag1 = TRUE if $arg =~ m@^vcodec=mpeg2video\b@; $TestFlag2 = TRUE if ($arg eq '-oac' ) && ($nexta eq 'lavc' ); $TestFlag3 = TRUE if ($arg eq '-ovc' ) && ($nexta eq 'lavc' ); $TestFlag4 = TRUE if ($arg eq '-ofps' ) && ($nexta eq '24000/1001' ); $TestFlag5 = TRUE if ($arg eq '-mpegopts' ) && ($nexta =~ m@[=:]dvd\b@ ); $TestFlag6 = TRUE if ($arg eq '-vf' ) && ($nexta =~ m@\bharddup\b@ ); } return if ($idx1 < ZERO) || ($idx2 < ZERO) || ($idx3 < ZERO); return unless $TestFlag1 && $TestFlag2 && $TestFlag3 && $TestFlag4 && $TestFlag5 && $TestFlag6; $ARGV [$idx1] = 'rawvideo'; push (@ARGV, '-nosound'); my $TEMPWAV = "/var/tmp/temp$>-$$.wav"; my $TEMPVID = "/var/tmp/temp$>-$$.m2v"; my $TEMPSOX = "/var/tmp/tempsox$>-$$.wav"; my $TEMPAC3 = "/var/tmp/temp$>-$$.ac3"; my $INPFILE = $ARGV [$idx3]; $ENV {TEMPWAV} = $TEMPWAV; $ENV {TEMPVID} = $TEMPVID; $ENV {TEMPSOX} = $TEMPSOX; $ENV {TEMPAC3} = $TEMPAC3; $ENV {INPFILE} = $INPFILE; my $OFNAME = $ARGV [$idx2]; $ARGV [$idx2] = $TEMPVID; system << 'END'; # 'END' must be single-quoted here mplayer -ao pcm:file="$TEMPWAV" -novideo -vc null -vo null "$INPFILE" # If the audio track is 8-bit *or* mono, this code converts it to 16- # bit stereo. This bypasses some "ffmpeg"-related problems. X=`file $TEMPWAV` N1=`echo $X | grep ", mono" | wc -l` N2=`echo $X | grep ", 8 bit" | wc -l` if [ \( $N1 != 0 \) -o \( $N2 != 0 \) ]; then echo Converting audio to 16-bit stereo format sox -V "$TEMPWAV" -c 2 -2 "$TEMPSOX" mv "$TEMPSOX" "$TEMPWAV" || exit 1 fi ffmpeg -i "$TEMPWAV" -ab 224000 -ar 48000 -ac 2 -acodec ac3 \ -y "$TEMPAC3" END system 'mencoder', @ARGV; system << "END"; # "END" must be double-quoted here mplex -f 8 -o "$OFNAME" "$TEMPVID" "$TEMPAC3" rm -f "$TEMPVID" "$TEMPAC3" "$TEMPWAV" "$TEMPSOX" END exit ZERO; } #--------------------------------------------------------------------- # main routine #--------------------------------------------------------------------- sub Main { &check_programs(); # Check external programs &devede_kludge(); # See comments preceding subroutine exec 'mencoder', @ARGV; } #--------------------------------------------------------------------- # main program #--------------------------------------------------------------------- &Main(); # Call the main routine exit ONE; # Should't be reached