Penguin
Note: You are viewing an old revision of this page. View the current version.

stazenc - Help Me Think of A Better Name!

I have written a bash script that from a directory containing vob files, will make an extremely good looking XviD dvdrip. I'd really appreciate it if you would try it out and let me know if there are any bugs. I've had great success with it. At the moment it only works on a PAL DVD source, but it the future this will be updated to make perfect rips from an NTSC source. Will also be updated to make rips of DVD's containing tv-eps.

Required Arguments

  • The first argument is always the path to the vob directory.
  • --title <n> or -t <n> : This tells the script which DVD title we wish to encode. (Get a list of them with the argument --list-titles) Note : n is a NUMBER. Make sure to choose the right one (look at the lengths of each title after using the '-l' option.)
  • --output <file> or -o <file> : This is the output filename. Eg : armageddon.avi

Optional Arguments

  • --max-ar-error <n> or -e <n> : This tells us the maximum AR error (percentage) to allow. This defaults at 3. I wouldn't make this value any higher than 5.

Changelog

28/12/2006
Fixed : Bug causing all files to be hidden!

27/12/2006
Added : It should now be possible to encode to XviD directly from the DVD disc, provided the DVD only has CSS encryption and
nothing too fancy :) If the Supplied dir argument is a mounted DVD (cannot be written to), then the $PWD will be used as our
working directory, otherwise the vob dir will be used as our working directory.

Added : Added some xvidenc options to set quantization type as H.263 and to make sure that qpel and gmc are not used as these
can cause problems on standalone players.

26/12/2006
Added : Title and Output Filename options are now mandatory and the script will now detect if they are missing.

Fixed : Listing .vob files now working.

25/12/2006
Added : Different range of possible resolutions for the output video, depending on whether we have a WS or FS source.

Tools Needed:

1) Mplayer & Mencoder

2) Transcode Package

3) lsdvd

4) lame v3.97

5) XviD codec (libxvidcore4)

6) mp3gain (Sorry I forgot all about this!)

7) Midentify (Put this script inside /usr/local/bin/ or somewhere else in $PATH)

#!/bin/sh
#
#This is is a wrapper around the -identify functionality.
# It is supposed to escape the output properly, so it can be easily
# used in shellscripts by 'eval'ing the output of this script.
#
# Written by Tobias Diedrich <ranma+mplayer@tdiedrich.de>
# Licensed under GNU GPL.

if [ -z "$1" ]; then
        echo "Usage: midentify <file> [<file> ...]"
        exit 1
fi

mplayer -vo null -ao null -frames 0 -identify "$@" 2>/dev/null |
        sed -ne '/^ID_/ {
                          s/[]()|&;<>`'"'"'\\!$" []/\\&/g;p
                        }'

8) ~/.mplayer/cropping.conf (Create This File)

RIGHT change_rectangle 2 +2
LEFT change_rectangle 2 -2
DOWN change_rectangle 3 +2
UP change_rectangle 3 -2
KP6 change_rectangle 0 +2
KP4 change_rectangle 0 -2
KP2 change_rectangle 1 +2
KP8 change_rectangle 1 -2
PGUP seek +60
PGDWN seek -60
q quit
ESC quit
ENTER pt_step 1 1
p pause
SPACE pause
f vo_fullscreen

So Here It Is:

#!/bin/bash

calculate_ar_error()
{
        resized_ar=$(echo "scale=10;$1/$2" | bc )
        ar_error=$(echo "scale=4; (($crop_ar - $resized_ar) / $resized_ar) * 100" | bc )
}

function best_mult16()
{
        calculate_ar_error $1 $2
        ar_error_1=$ar_error
        calculate_ar_error $1 $3
        ar_error_2=$ar_error

        echo "AR Error $1 x $2 : ${ar_error_1}%"
        echo "AR Error $1 x $3 : ${ar_error_2}%"

        if [ `echo "${ar_error_2#-} < ${ar_error_1#-}" | bc ` -eq 1 ]; then
                best_error=$ar_error_2
                return 0;
        else
                best_error=$ar_error_1
                return 1;
        fi
}

function find_best_height()
{
        #Find Closest Multiple of 16
        perfect_height=$(echo "scale=10; $1 / $crop_ar" | bc)
        round_down_mult16=$(echo "$perfect_height - ($perfect_height % 16)" | bc)
        round_down_mult16=${round_down_mult16%%.*}
        best_mult16 $1 $round_down_mult16 $(echo "$round_down_mult16 + 16" | bc)

        if [ $? -eq 0 ]; then
                best_height=$(expr $round_down_mult16 + 16)
        else
                best_height=$round_down_mult16
        fi
        echo "The Best height out of $round_down_mult16 and $(expr $round_down_mult16 + 16) is $best_height"
}

echo "Welcome to Staz's XviD Video Encoder"
echo "At the moment this can only be used to encode PAL DVD Movies to XviD"
echo "Email any bugs/fixes to ben.staz@gmail.com"
echo "-------------------------------------"

#Create Constants To Be Used In Script (Could use 'readonly foo==moo', 'declare -r foo=moo' or 'typeset -r foo=moo'
readonly CD_SIZE=716800
readonly PAL_MIN_RUNTIME_PER_CD=6000
readonly NTSC_MIN_RUNTIME_PER_CD=5220
readonly FILM_MIN_RUNTIME_PER_CD=6300

#First argument supplied is the directory containing the vobs.
if [ -d $1 ]; then
        vob_dir=$1;

        if [ "${vob_dir:0:1}" != "/" ]; then
                echo "This vob dir is relevant to the PWD"
                if [ "$vob_dir" == "." ]; then
                         vob_dir=${PWD}/
                else
                         vob_dir=${PWD}/${vob_dir#./}
                fi
        else
                echo "The vob dir is a full path."
        fi

        echo "Vob Dir : $vob_dir";
        echo "Vob Files:"

        for i in ${vob_dir}/*.[Vv][Oo][Bb] ;
        do
                echo $i;
        done;


        #Check if the supplied vob directory is a mounted DVD.
        #The 2> /dev/null part avoids the 'unary operator' message when it is not a mounted DVD dir.

        if [ `df -T $vob_dir | awk '$2 == "udf" || $2 == "iso9660" {print "0"}'` -eq 0 ]; then
                echo "This is a mounted DVD. Will use ${PWD}/ as the working directory"
                working_dir=${PWD}/
        else
                echo "Will use $vob_dir as the working directory"
                working_dir=$vob_dir
        fi 2> /dev/null
else
        echo "Error : You did not supply a valid directory!"
fi;

shift;
#Set the default arguments (If user does not specify otherwise)
max_ar_error=3


echo "-------------------------------------"
echo "         User Chosen Options"
echo "-------------------------------------"

#Following variables are used to make sure the two compulsory options (title and output) are supplied.
t=1
o=1

while test "$1" != "";
do
        case $1 in
                --list-titles|-l)
                        echo "-------------------------------------"
                        echo "         Listing Titles"
                        echo "-------------------------------------"
                        lsdvd $vob_dir 2> /dev/null
                        echo "Choose a Title by using --title <n>"
                        echo "-------------------------------------"
                        exit
                ;;
                --title|-t)
                        #TODO: WE WILL BE ABLE TO SUPPLY MULTIPLE TITLES HERE
                        t=0
                        echo "Chosen Title : $2"
                        title=$2
                        tmp=$(lsdvd -v $vob_dir 2> /dev/null )
                        length=$(echo "$tmp" | egrep -o '[0-9]{2}:[0-9]{2}:[0-9]{2}' | sed -n "${title}p")
                        ar=$(echo "$tmp" | egrep -o '[0-9]+/[0-9]+' | sed -n "${title}p")
                        echo "Title Length : $length"
                        echo "Title AR : $ar"
                        length_seconds=$(echo $length | { IFS='[:.]'; read h m s; echo "(${h##0} * 3600) + (${m##0} * 60) + ${s##0}" | bc; } )
                        echo "Length in Seconds : $length_seconds"
                        shift
                ;;
                --max-ar-error|-e)
                        echo "Max AR % Error : ${2}%"
                        max_ar_error=$2
                        shift
                ;;
                --output|-o)
                        o=0;
                        echo "Output File : $2"
                        output_file=$2
                        output_name=${2%.[Aa][Vv][Ii]}
                        shift
                ;;
                *)

                        echo "Invalid Argument : $1"
                        echo "Will print usage in the future"
                        exit 1
                ;;
        esac
        shift
done

#Check that both the title and output options have been supplied.

if [ "$t" -eq 1 -o "$o" -eq 1 ] ; then
        #Uh oh something is missing
        echo "-------------------------------------"
        echo "ERRORS!"
        if [ "$t" -eq 1 ]; then
                echo "You need To specify a Title. Usage : -t <title>"
        fi
        if [ "$o" -eq 1 ]; then
                echo "You need to specify an Output File. Usage : -o <output file>"
        fi
        echo "-------------------------------------"
        exit
fi

echo "-------------------------------------"
echo "   Source Information"
echo "-------------------------------------"

#Calculate Source Video Format/Resolution/Framerate by parsing lsdvd's output.

video_format=$(lsdvd $vob_dir -v 2> /dev/null | egrep -o '(PAL|NTSC)' | tail -1 )
echo "Video Format......: $video_format"

case $video_format in
                   PAL)
                        frame_rate=25
                        source_width=720
                        source_height=576
                        min_runtime_per_cd=$PAL_MIN_RUNTIME_PER_CD
                   ;;
                   NTSC)
                        frame_rate=29.97
                        source_width=720
                        source_height=480
                        min_runtime_per_cd=$NTSC_MIN_RUNTIME_PER_CD
                   ;;
esac

echo "Source Resolution.: $source_width x $source_height"
echo "Source FrameRate..: $frame_rate"

echo "-------------------------------------"
echo "    Crop Information"
echo "-------------------------------------"

#Calculate Cropping Values/Resolution based on parsed mplayer output.
crop_values=$(mplayer -dvd-device $vob_dir dvd://${title} -aid 128 -vf rectangle=720:576 -input conf=cropping.conf 2> /dev/null | egrep -o '[0-9]+:[0-9]+:[0-9]+:[0-9]+' | tail -1 )

echo "Crop Vals : $crop_values"

left_right_crop=$((source_width - $(echo "$crop_values" | cut -d ':' -f 1) ));
top_bottom_crop=$((source_height - $(echo "$crop_values" | cut -d ':' -f 2) ));

echo "Total Left/Right Cropping : $left_right_crop"
echo "Total Top/Bottom Cropping : $top_bottom_crop"

crop_ar=$(echo "scale=10;(($source_width - $left_right_crop) / $source_width) * ($source_height / ($source_height - $top_bottom_crop)) * ($ar)" | bc )
echo "Crop AR : $crop_ar"
found_a_res=0

if [ "$ar" == "16/9" ]; then
        for width in $(seq 672 -16 512) ;
        do
                echo "-------------------------------------"
                echo "Horizontal Resolution : $width"
                echo "-------------------------------------"
                find_best_height $width
                if [ `echo "${best_error#-} < $max_ar_error" | bc` -eq 1 ]; then
                        final_width=$width
                        final_height=$best_height
                        found_a_res=1;
                        break;
                fi
        done;
elif [ "$ar" == "4/3" ]; then
        for width in $(seq 576 -16 448) ;
        do
                echo "-------------------------------------"
                echo "Horizontal Resolution : $width"
                echo "-------------------------------------"
                find_best_height $width
                if [ `echo "${best_error#-} < $max_ar_error" | bc` -eq 1 ]; then
                        final_width=$width
                        final_height=$best_height
                        found_a_res=1;
                        break;
                fi
        done;
fi

#Check We Have Finally Came Up With a Resolution

if [ $found_a_res -eq 1 ]; then
        echo "The Final Resolution is : $final_width x $final_height"
        echo "The AR Error Is: ${best_error}%"
else
        echo "Couldn't Find A Resolution!"
        echo "Try Lowering the max AR Error %"
fi

echo "-------------------------------------"

#Work Out How Many CDS Our Encode Will Take Up

if [ "$length_seconds" -le "$min_runtime_per_cd" ]; then
        num_cds=1;
else
        num_cds=$(( ($length_seconds / $min_runtime_per_cd) + 1 ));
fi

echo "Number of CDS : $num_cds"
if [ $num_cds -eq 1 ]; then
        #Now We Rip The DVD Audio Stream To PCM .wav (Uncompressed Audio)
        mplayer -dvd-device $vob_dir dvd://${title} -aid 128 -vo null -vc dummy -mc 0 -ao pcm:file=${working_dir}${output_name}.wav
        #Use Lame to Encode to VBR MP3 (-V 3 --vbr-new seems to result in a bitrate close to 128 which I am after)
        lame -h -V 3 --vbr-new ${working_dir}${output_name}.wav ${working_dir}${output_name}.mp3

        #Time To Apply Some Gain Or Else Our Movie Is Far Too Quiet!
        #The switches will make sure that mp3gain applies the maximum gain possible WITHOUT any clipping
        mp3gain -r -k ${working_dir}${output_name}.mp3
        audio_format=mp3
else
        echo "We are ripping to AC3 Audio NOW...."
        tmp=$(mencoder -dvd-device $vob_dir dvd://${title} -aid 128 -oac copy -of rawaudio -ovc copy -mc 0 -o ${working_dir}${output_name}.ac3 2> /dev/null | grep 'audio stream: 0' | grep -o '(.*)' )
        #I DONT KNOW THE OUTPUT OF MPLAYER IF THE AUDIO STREAM IS MONO. WILL HAVE TO FIND A DVD WITH A MONO AUDIO STREAM.
        num_channels=$(echo "$tmp" | sed -e 's/(5.1)/6/' -e 's/(stereo)/2/' )
        echo "Number Of Channels : $num_channels"
        audio_format=ac3
fi

#Time To Calculate Overhead

echo "-------------------------------------"
echo        "Overhead Calculations"
echo "-------------------------------------"

if [ "$audio_format" == "mp3" ]; then
        frames_per_chunk=1
        audio_overhead=$(echo "scale=10; ($length_seconds*1000) / (24*$frames_per_chunk)" | bc)
        audio_size=$(du -k ${working_dir}${output_name}.mp3 | cut -f 1)
elif [ "$audio_format" == "ac3" ]; then
        frames_per_chunk=2
        audio_overhead=$(echo "scale=10; ($length_seconds*1000) / (32*$frames_per_chunk)" | bc)
        audio_size=$(du -k ${working_dir}${output_name}.ac3 | cut -f 1)
fi

echo "Audio Overhead : ${audio_overhead%%.*}"


if [ $num_cds -ge 3 ]; then
        #We will get an OPEN-DML .avi (with or without a legacy index?? will have to ask the mplayer devs)
        overhead_kilobytes=$(echo "scale=10; (16 * $audio_overhead) / 1024" | bc )
else
        #We will get a standard avi output.
        echo "STANDARD AVI OUTPUT"
        overhead_kilobytes=$(echo "scale=10; (24 * $audio_overhead) / 1024" | bc )
fi

echo "Total Overhead (Kilobytes) : ${overhead_kilobytes%.*}"
echo "-------------------------------------"

echo "Audio Size : $audio_size Kb's"
video_size=$(((num_cds*CD_SIZE) - audio_size - ${overhead_kilobytes%%.*} ))
echo "Video Size : $video_size kb's"

#We Now Have All The Info Needed To Start The XviD Encode.
#Note: -nosound disables processing the audio stream.

#First Pass
mencoder -dvd-device $vob_dir dvd://${title} -nosound -vf crop=${crop_values},scale=${final_width}:${final_height} -sws 9 -ovc xvid -passlogfile ${working_dir}${output_name}_2pass.log -xvidencopts pass=1:quant_type=h263:nogmc:noqpel:bitrate=-${video_size} -mc 0 -noskip -o /dev/null

pass1_filesize=$(echo "$(cat ${working_dir}${output_name}_2pass.log | awk '$6 ~ "^[0-9]" { total += $6; print $total} END { print total } ') / 1024" | bc)
echo "At a constant quantizer value of 2, the output filesize is $pass1_filesize Kb's in size"

#Here we can easily change the XviD encoding options.
xvidencopts=pass=2:quant_type=h263:min_pquant=1:min_iquant=1:min_bquant=1:nogmc:noqpel:bitrate=-${video_size}

#Second Pass:
mencoder -dvd-device $vob_dir dvd://${title} -nosound -vf crop=${crop_values},scale=${final_width}:${final_height} -sws 9 -ovc xvid -passlogfile ${working_dir}${output_name}_2pass.log -xvidencopts $xvidencopts -mc 0 -noskip -o ${working_dir}${output_name}_nosound.avi

#ITS MUXING TIME!!!

if [ "$audio_format" == "mp3" ]; then
        mencoder ${working_dir}${output_name}_nosound.avi -ovc copy -oac copy -audiofile ${working_dir}${output_name}.mp3 -mc 0 -noskip -o ${working_dir}${output_name}.avi
else
        #GET AC3 INFO AND THEN MUX
        midentify ${working_dir}${output_name}.ac3 | sed -ne '/ID_AUDIO_BITRATE/p' -ne '/ID_AUDIO_RATE/p' | head -2 | awk -F '=' '{ print $2 }' | { read audio_bitrate; read audio_rate; mencoder ${working_dir}${output_name}_nosound.avi -ovc copy -oac copy -audiofile ${working_dir}${output_name}.ac3 -audio-demuxer 20 -rawaudio format=0x2000:channels=${num_channels}:rate=${audio_rate}:bitrate=${audio_bitrate} -mc 0 -noskip -o ${working_dir}${output_name}.avi; }
fi

#If We Encoded To 2CDS+ then we will want to split the encoded video into the appropriate number of parts.

if [ "$num_cds" -gt 1 ]; then
        #Split into $num_cds parts
        avisplit -i ${working_dir}${output_name}.avi -o ${working_dir}${output_name}-cd -s 700
        counter=1;
        for i in ${working_dir}${output_name}-cd-000[0-9].avi ;
        do
                 mv $i ${working_dir}${output_name}-cd${counter}.avi;
                 counter=$((counter+1));
        done;
fi
exit

Example of Usage

  • stazenc Armageddon/ -mar 2 -t 1 -o armageddon.avi

Soon after typing this command mplayer will popup and start playing your chosen title from the DVD. You will notice the outline of a white rectangle that overlays the video. Basically what you want to do is to manipulate the rectangle so that it surrounds the video and crops out any black area around the video. Use the main arrow keys to move the entire rectangle and the numpad arrow keys to increase/decrease the width and height. What I recommend is.

  • 1) Use the main arrow keys to move the rectangle so the top right of the rectangle exactly matches the top right of the video that is being played.
  • 2) Now use the numpad arrow keys to resize the rectangle to perfectly surround the video.
  • 3) Press The Page-Up a key a few times to skip through the video and make sure your cropping values are still appropriate. My philosophy is to apply the MAX amount of cropping possible without overcropping any frames elsewhere in the video.
  • 4) Good now press escape (closes mplayer) and there is no more effort required by you! Sit back and relax.