The Series Ripper Script Project aims to automate the ripping of DVDs with an emphasis on accomodating multiple DVDs from a series. SRS is actually a handful of scripts each dedicated to a particular aspect of the process.

The scripts are currently being overhauled after a very long hiatus. The original focus put too much emphasis on making a console UI. The new focus is to have a handful of helper scripts that do more-specifc tasks.

The SRS scripts depend on:

mplayer
For audio stream dumping
mencoder
For video encoding and subtitle dumping
x264
For video encoding
udisks
For optical drive monitoring and mounting
sountouch
For audio syncing
bc
For audio sync calculations

Optionally, the scripts can use the following:

dvdbackup
For making local copies of DVDs. (So the script doesn’t have to wait for the user to change disks.)
GNU time
For process completion information
beep
For audible notifications (next DVD, project complete)
flac
For WAV compression

So far we have:

~/.srs-conf
( project-location )/.srs-conf-[ project-name ]

Sets and configures global options.

Status

Complete in that the options I want pre-defined are out of the scripts that follow. Some flexibility is called for, however (like getting stuff users shouldn’t touch somewhere else).

Learning Experiences / Why I Did What I Did

I had no intention of the command line to get so friggin’ long for srs-dump. Adding x264 config options would have added to the insanity. Plus, users can now be given some guidance on x264 settings.

The external terminal was a cool way to go for ultimate feedback. However, I learned very quickly how dangerous copying and pasting can become when new windows keep popping up and stealing focus. The only solution outside of forcing the new terminal into a new process and workspace would be to query the system for a process called mencoder or mplayer in a loop, and at that point you’re not guaranteed that there aren’t any other processes by that name.

The beep thing was the solution I came up with for when I walked away from the computer.

# These should not be changed unless they go out of date

# These aren't used right now, but they'll be useful for error-checking
# when this scripts gets converted to using command-line parameters

#C3=( aar abk afr aka alb amh ara arg arm asm ava ave aym aze bak bam baq bel ben bih bis bod bos bre bul bur cat ces cha che chi chu chv cor cos cre cym cze dan deu div dut dzo ell eng epo est eus ewe fao fas fij fin fra fre fry ful geo ger gla gle glg glv gre grn guj hat hau heb her hin hmo hrv hun hye ibo ice ido iii iku ile ina ind ipk isl ita jav jpn kal kan kas kat kau kaz khm kik kin kir kom kon kor kua kur lao lat lav lim lin lit ltz lub lug mac mah mal mao mar may mkd mlg mlt mon mri msa mya nau nav nbl nde ndo nep nld nno nob nor nya oci oji ori orm oss pan per pli pol por pus que roh ron rum run rus sag san sin slk slo slv sme smo sna snd som sot spa sqi srd srp ssw sun swa swe tah tam tat tel tgk tgl tha tib tir ton tsn tso tuk tur twi uig ukr urd uzb ven vie vol wel wln wol xho yid yor zha zho zul )
#C2=( aa ab af ak sq am ar an hy as av ae ay az ba bm eu be bn bh bi bo bs br bg my ca cs ch ce zh cu cv kw co cr cy cs da de dv nl dz el en eo et eu ee fo fa fj fi fr fr fy ff ka de gd ga gl gv el gn gu ht ha he hz hi ho hr hu hy ig is io ii iu ie ia id ik is it jv ja kl kn ks ka kr kk km ki rw ky kv kg ko kj ku lo la lv li ln lt lb lu lg mk mh ml mi mr ms mk mg mt mn mi ms my na nv nr nd ng ne nl nn nb no ny oc oj or om os pa fa pi pl pt ps qu rm ro ro rn ru sg sa si sk sk sl se sm sn sd so st es sq sc sr ss su sw sv ty ta tt te tg tl th bo ti to tn ts tk tr tw ug uk ur uz ve vi vo cy wa wo xh yi yo za zh zu )
#NC=${#C3[@]}

# These should be changed to your preference

# ROUND( [width] x [height] x [frames] / 800000 ) * 100
BR="bitrate=600"
# Normally 0,0 but hand-drawn animation can benefit from 1,1
DB="deblock=1,1"
# Default aq_mode=1:aq_strength=1.0
# Animation can benefit from 1 and 0.6 or 0 and undefined
AQ="aq_mode=0"
# Default is 1.0,0.0 but animation can benefit from (0.4-0.6),0.0
PR="psy-rd=0.6,0.0"
# Default is fast_pskip but large solid areas may suffer (yes, animation).
PS="nofast_pskip"
# OK = 3, normal = 6, better = 9, starting to go insane = 12
FR="frameref=12:mixed_refs"
# crap = dia, OK = hex, great = umh, insane = esa, nuthouse = tesa
# me_range clamped to 16 if me=dia or me=hex.  Up to 64 otherwise.
ME="me=umh:me_range=32"
# 7 is default, 10 requires trellis=2
SE="subq=9" && TR="trellis=2"
# 1 - 16
BF="bframes=8:b_adapt=2:weight_b:b_pyramid=normal"
# 0 gives more bits to low motion, 1 gives them to high motion.  (0.6)
QC="qcomp=0.6"
# Defaults enable all but p4x4 which should only be used in small res.
PT="partitions=all:8x8dct"
# Should be defaults but acting paranoid to make sure they're enabled.
PD="threads=auto"

XO="$BR:$DB:$AQ:$PR:$PS:$FR:$ME:$SE:$TR:$BF:$QC:$PT:$PD:direct_pred=auto"
#XO="preset=ultrafast"		# Use this for debugging

# If you want, you can launch an External Terminal for the output of
# MPlayer, MEncoder, and FLAC.  If EXT is set, progress information will
# be displayed in the parent console.  If EXT is not set, no progress
# information will be displayed as it will easily get lost.

# It's highly recommended that you get some kind of control of where the
# new windows will pop up, either with devilspie or CCSM.  Otherwise
# they will will steal focus on the current workspace,  which can be
# disasterous when using  Ctrl+X or Ctrl+C in another application.

#EXT="roxterm --separate -e"

# A beep tone can be played after each DVD to grab your attention and
# let you know when the script is ready for the next DVD.

# This is the Final Fantasy score commonly known as "Victory"
BEEP_SEQ="-D 50 -f 523.2 -l 150 -n D-50 -f 523.2 -l 150 -n -D 50 -f 523.2 -l 150 -n -D 50 -f 523.2 -l 300 -n -D 50 -f 415.3 -l 300 -n -D 50 -f 466.2 -l 300 -n -D 200 -f 523.2 -l 150 -n -D 50 -f 466.2 -l 150 -n -f 523.2 -l 600"

srs-dump

Dumps per-episode video, audio, and subtitle streams in a specified location.

85% of the time, only the optical drive, location, project name, and episode span list (with optional framerate conversion and crop settings) is needed. The srs-dump script can usually come up the correct information by doing the following:

  • Look at the length of all DVD titles and pick the longest one. This is especially useful since this is almost always correct but the actual title number itself may change between DVDs.
  • Grab audio/subtitle track IDs and language information from the aformentioned title.
  • Take the number of chapters in the aforemention title and divide by the number of episodes as given in the episode span.

There are times when this logic will fail.

  • The content is split into a handful of titles. I haven’t come across this yet, but a workaround would be to create one project for each title and manually specify -ttl and -csl.
  • The content is split into many titles. This is usually the case of bonus DVDs that contain a hodgepodge of outtakes, trailers, interviews, and/or other bonuses. The srs-dump script doesn’t handle this content well at all.
  • Sometimes chaptering gets downright assinine, and then some special episodes diverge from the normal format and thus drop or add chapters. Most assinine chaptering setup: intro teaser, intro sequence, title card, first quarter, second quarter, commerical lead-out, commercial lead-in, third quarter, fourth quarter, end credits, next episode preview. It takes little imagination how special episodes can mess this up. The workaround is to exclude the DVDs where this happens from the project and put them into their own project manually specifying -csl.

Status

Working but with partial error-checking. Auto-detection is the best idea ever.

Learning Experiences / Why I Did What I Did

My original intention was to just use MPlayer to dump VOB streams. It would have made life so much easier as I could have had one script dedicated to just that and use other scripts to do other stuff. What I found were results of either bad DVD mastering or limitations of MPlayer/MEncoder.

  • Dumping VOB streams loses any framerate switching information
  • Dumping VOB streams includes audio from previous chapters in the title.

Using -dumpstream, -dumpvideo, and -dumpaudio were now out of the question. The replacement for -dumpvideo was obvious. Just encode a video as you normally would, without sound. And actually, this is the best way to go as the video filters do a mch better job when the don’t have to worry about audio.

A replacement for -dumpaudio was not so obvious.

  • Using mplayer -vc null -vo duumy -ao pcm:file was my first hit and actually the best solution.
  • MPlayer suggested using -novideo, which necesitated pcm:fast:file, but that filled up the buffer and crashed resulting in truncated audio..
  • Using mencoder -of rawadio -oac copy -ovc frameno stopped the inclusion of previous chapters, but now included audio from the next chapter. Why will be touched on in a bit

Of course, one of the reasons behind the original VOB dumping plan now reared its ugly head: The time between DVD switching was now greatly increased. With the VOB dumping plan, each DVD took approximately 15 minutes, and you were done with the DVDs after that. Now that video encoding was being done, each DVD could take up to two hours. Not good when you have to work and sleep.

The -gif (Get Image First) flag solved this problem, but introduced two more: Had to find a way to wait until the DVD was loaded and recognized, and the DVD had to be mounted in case it was a dual-layer DVD. The first was trivial to solve. The second I didn’t even know about until I came across my first dual-layer DVD and wondered why libdvdread was having problems.

The only problem then was that the video and audio were still out of sync. The video being longer than source isn’t hard to figure out — the video was a mix of hard and soft telecine and the video filters could only do so much to reverse that. The audio, however, ended up running between original video run time and encoded video run time.

I got the bright idea to mux in at least the main audio track into the encoded video. The results were horrible. The video filters that worked surprisingly well now failed miserably. Video was even longer. Audio from the next chapter snuck in. (Remember that mencoder rawaudio thing? This is why.) The worst part is, there doesn’t seem to be a programatic way to solve this audio sync problem. At any rate, that’s where this script’s job ends.

#!/bin/bash

source ~/.srs-conf

usage () {
cat <<UsageDocumentation
Usage: $0 [OPTIONS]
Takes input from DVDs and creates video, audio, and subtitle streams.

	-odp (/dev/device)
		Optical Drive Path (ex: /dev/sr0)
        -loc (/path/to/somewhere)
		Location where files will be written to.
        -ppn (name)
        	With -epl, this will be a directory appended to -loc
		Without -epl, this wil become part of the file name.
        -ttl (1..99)
		Over-rides automatic title detection.
        -esl (n-n[,n-n...])
		Episode span list.  Must be one span per DVD.  Will
		associate each -csl span with an episode number.  Allows
		"over-chaptering" the -csl list since only the chapter
		spans with associated episode numbers will be ripped.
        -csl (n-n[,n-n...])
        	Chapter span list.  Can be "over-chaptered" when used
        	with -epl. associates chapters with episodes.
        -ail (128..135[,128..135...])
        	Audio ID list.  Specifies which audio tracks to rip from
        	each chapter span.  Each track will be ripped into a WAV
        	file.  If FLAC is present, WAV will be converted to FLAC.
        -all (lll[,lll...])
        	Audio language list.  Identifies the target language of
        	each -ail.  Must be a valid ISO-693-2 three-letter code.
        -sil (0..31[,0..31])
        	Subtitle ID list.  Max 2 subtitles.  Specifies which
        	subtitles to rip from each chapter span.  Each subtitle
        	stream will be in the VOBSUB format.
        -sll (lll[,lll])
        	Subtitle language list.  Max 2 subtitles.  Identifies the
        	target language of each -sil. Must be a valid ISO-693-2
        	three-letter code.
        -frc (24p30t-24|24p30p-30i|24p30i-30i|30t-24p|30i-60p)
        	Framerate Conversion.  If not specified, uses defaults
		MPlayer/MEncoder gets from stream.  Valid values are:
		24p30t-24p	Source	Mixed 24p & 30t
				Output	24p (24p->24p & 30t->24p)
		24p30p-30i	Source	Mixed 24p & 30p
				Output	30i (24p->30t,30p->30i)
		24p30i-30i	Source	Mixed 24p & 30i
				Output	30i (24->30t,30i->30i)
		30t-24p		Source	100% 30t
				Output	24p (30t->24p via IVTC)
		30i-60p		Source	100% 30i
				Output	60p (30i->60p via TFIELDS)
        -crp [left,right,top,bottom]
        	Crops pixels from specified side of the video. Values
        	must be mod2.  If output is to be interlaced, top and
        	bottom must be mod4.  If the resulting video size is not
        	mod16, the script will expand and center the best it that
        	it can, aligned to mod2/mod4 boundaries.
	-gif	Get Image First.  Rips all DVD of a series into an image
		for use later.  Useful since it only takes about 1-2
		hours to rip a DVD, but work and sleep take eight.
UsageDocumentation
}

wait_for_medium() {
  BLANK="  label:                       "
  OUTPUT="$BLANK"
  echo -n "Waiting for medium to be ready.."
  while [ "$OUTPUT" == "$BLANK" ]; do
    echo -n "." && sleep 1
    OUTPUT="`udisks --show-info $1 | grep ""label:""`"
  done
  udisks --mount $1 > /dev/null
  echo "medium ready." && sleep 1
}

wait_for_process() {
  TERMINATED=0
  WAIT_TIME=0
  while [ $TERMINATED == 0 ]; do
    sleep 1 && WAIT_TIME=`expr $WAIT_TIME + 1`
    TERMINATED=`killall -0 $1`
  done
  echo "process completed in $WAIT_TIME seconds"
}

#################### GRAB OPTIONS FROM COMMAND LINE ####################

ODP="" && LOC="" && PPN="" && TTL="" && EPL="" && CHL="" && AIL=""
ALL="" && SIL="" && SLL="" && FRC="" && CRP="" && GIF=0
SRSERR=0 && DVDCNT=0 && NCS="nice -n 19" && TCS="/usr/bin/time -f %E"

while [ "$1" != "" ]; do
    case $1 in
	-odp )		shift && ODP="$1" ;;
        -loc )		shift && LOC="$1" ;;
        -ppn )		shift && PPN="$1" ;;
        -ttl )		shift && TTL="$1" ;;
        -esl )		shift && ESL="$1" ;;
        -csl )		shift && CSL="$1" ;;
        -ail )		shift && AIL="$1" ;;
        -all )		shift && ALL="$1" ;;
        -sil )		shift && SIL="$1" ;;
        -sll )		shift && SLL="$1" ;;
        -frc )		shift && FRC="$1" ;;
        -crp | -crop )	shift && CRP="$1" ;;
        -gif )		GIF=1 ;;
        -h | --help )	usage && exit 0 ;;
        * )		usage && exit 1 ;;
    esac
    shift
done

######################## CHECK FOR REQUIREMENTS ########################

[ `type mplayer &> /dev/null` ] && SRSERR=1 && echo "MPlayer not installed."
[ `type mencoder &> /dev/null` ] && SRSERR=1 && echo "MEncoder not installed."
[ `type x264 &> /dev/null` ] && SRSERR=1 && echo "x264 not installed."
[ `type udisks &> /dev/null` ] && SRSERR=1 && echo "UDisks not installed."

########################## CHECK FOR OPTIONAL ########################## 

[ `type dvdbackup &> /dev/null` ] && echo "DVDBackup not installed, -gif disabled."
[ ! -f /usr/bin/time ] && echo "GNU Time not installed, time stats disabled."
[ `type beep &> /dev/null` ] && "Beep not installed, audible media change requests disabled."
[ `type flac &> /dev/null` ] && "FLAC not installed, WAV compression disabled."

[ $SRSERR != 0 ] && echo "Please install requirements to use this script." && exit 1

###################### WARN OF DEFAULT FALLBACKS #######################

[ "$TTL" == "" ] && ADS=" title"
  
[ "$AIL" == "" ] && ADS="$ADS audio-tracks"

[ "$ALL" == "" ] && ADS="$ADS audio-langs"

[ "$SIL" == "" ] && ADS="$ADS sub-tracks"

[ "$SLL" == "" ] && ADS="$ADS sub-langs"

[ "$CSL" == "" ] && ADS="$ADS chapter-span-list"

[ "$ADS" != "" ] && echo "Using auto-detection for: $ADS"

############################ ERROR CHECKING ############################
############################# FATAL ERRORS ############################# 

[ -f $ODP ] && SRSERR=2 && echo "$ODP not found"

[ "$LOC" == "" ] &&
  SRSERR=2 && echo "Location not specified with -loc."

[ "$PPN" == "" ] &&
  SRSERR=2 && echo "Project name not specified with -ppn."

#[ "$TTL" -lt 1 -o "$TTL" -gt 99 ] &&
#  SRSERR=2 && echo "DVD Title Number must be between 1 and 99."

[ "`echo "$ESL" | grep [^0-9\,-]`" != "" ]  &&
  SRSERR=2 && echo "Episode List contains invalid characters."

[ "`echo "$CSL" | grep [^0-9\,-]`" != "" ]  &&
  SRSERR=2 && echo "Chapter List contains invalid characters."

[ "`echo "$AIL" | grep [^0-9\,]`" != "" ]  &&
  SRSERR=2 && echo "Audio ID List contains invalid characters."

[ "`echo "$ALL" | grep [^a-z\,]`" != "" ]  &&
  SRSERR=2 && echo "Audio Language List contains invalid characters."

[ "`echo "$SIL" | grep [^0-9\,]`" != "" ]  &&
  SRSERR=2 && echo "Subtitle ID List contains invalid characters."

[ "`echo "$CRP" | grep [^0-9\,]`" != "" ]  &&
  SRSERR=2 && echo "Crop settings contain invalid characters."

case $FRC in
  30t-24p | 24p30t-24p | 24p30p-30i | 24p30i-30i |  30i-60p ) ;;
  * ) SRSERR=2 && echo "Invalid framerate conversion type $FRC."
esac

[ $SRSERR != 0 ] && echo "Type $0 (-h | --help) for help." && exit 2

############################# LOGIC ERRORS #############################

ESA=( `echo $ESL | tr ',' ' '` )	# Expand
CSA=( `echo $CSL | tr ',' ' '` )	# all
AIA=( `echo $AIL | tr ',' ' '` )	# lists
ALA=( `echo $ALL | tr ',' ' '` )	# into
SIA=( `echo $SIL | tr ',' ' '` )	# arrays
SLA=( `echo $SLL | tr ',' ' '` )	# M'K?

# ESA should be checked to see if each number is greater than the last.

# CSA should be checked to see is start chapter is >= end chapter
# CSA should be checked to see if the next start is > last end.

# AIA should be checked to see if >= 128 and <= 135

# ALA should be checked for valid ISO codes

# SIA should be checked for limit of two.
# SIA should be checked for >= 0 and <= 31

########################### NON-FATAL ERRORS ###########################

if [ "$CRP" != "" ]; then
  CRL=`echo "$CRP" | cut -d',' -f1` && CRLFIX=`expr \( $CRL + 1 \) / 2 \* 2`
  CRR=`echo "$CRP" | cut -d',' -f2` && CRRFIX=`expr \( $CRR + 1 \) / 2 \* 2`
  CRT=`echo "$CRP" | cut -d',' -f3` && CRTFIX=`expr \( $CRT + 1 \) / 2 \* 2`
  CRB=`echo "$CRP" | cut -d',' -f4` && CRBFIX=`expr \( $CRB + 1 \) / 2 \* 2`

  [ $CRL != $CRLFIX ] && echo "Left crop not even, adjusting to $CRLFIX"
  [ $CRR != $CRRFIX ] && echo "Right crop not even, adjusting to $CRRFIX"
  [ $CRT != $CRTFIX ] && echo "Top crop not even, adjusting to $CRTFIX"
  [ $CRB != $CRBFIX ] && echo "Bottom crop not even, adjusting to $CRBFIX"
fi

[ -f $LOC ] && echo "$LOC doesn't exist...creating." && mkdir -p $LOC
[ "$ESL" != "" ] && mkdir -p $LOC/$PPN

################ GETTING ON WITH THE REST OF OUR LIVES ################# 

#if [ $GIF == 1 ]; then
#  for ESC in ${ESA[@]}; do
#    DVDCNT=`expr $DVDCNT + 1`
#    udisks --unmount $ODP && eject -r $ODP
#    read -p "Insert DVD $DVDCNT and press enter"
#    eject -t $ODP && wait_for_medium $ODP
#    mplayer -dvd-device $ODP dvd://$TTL -frames 30 > /dev/null 2>&1
#    echo -n "Ripping DVD to folder..."
#    $TCS $EXT dvdbackup -i $ODP -o $LOC -n $PPN-dvd-$DVDCNT -M > /dev/null
#  done
#  DVDCNT=0
#fi

case $FRC in
  30t-24p | 24p30t-24p )
    FR="24000/1001" && VF="pullup,softskip" ;;
  24p30p-30i | 24p30i-30i )
    FR="30000/1001" && VF="softpulldown" && XO="$XO:interlace" ;;
  30i-60p )
    FR="60000/1001 -fps 60000/1001" && VF="tfields=2";;
esac

if [ "$CRP" != "" ]; then
  CRW=`expr 720 - $CRLFIX - $CRRFIX`
  CRH=`expr 480 - $CRTFIX - $CRBFIX`
  VF="$VF,crop=$CRW:$CRH:$CRRFIX:$CRTFIX"

  EXW=`expr \( $CRW + 15 \) / 16 \* 16`
  EXH=`expr \( $CRH + 15 \) / 16 \* 16`
  EXX=`expr \( $EXW - $CRW \) / 4 \* 2`
  EXY=`expr \( $EXH - $CRH \) / 4 \* 2`
  VF="$VF,expand=$EXW:$EXH:$EXX:$EXY"
fi

[ -f $LOC/.srs-conf-$PPN ] && source $LOC/.srs-conf-$PPN
X264OPTS="-ovc x264 -x264encopts $XO"

for ESC in ${ESA[@]}; do	# For each Episode Span Current in Episode Span Array

  DVDCNT=`expr $DVDCNT + 1`

  if [ "$ESL" != "" ]; then		# If Epispde List wasn't NUL (disabled) 
    EPC=`echo "$ESC" | cut -d'-' -f1`	# Episode Current = left part of n-n
    EPL=`echo "$ESC" | cut -d'-' -f2`	# Episode Last = right part of n-n
    EPN=`expr $EPL - $EPC + 1`
    BASE=$LOC/$PPN/Episode
    EPC=`printf "%03d" \`expr $EPC \* -1\``
    EPL=`printf "%03d" \`expr $EPL \* -1\``
  else
    EPC=""				# Episode Current = NUL
    BASE=$LOC/$PPN
  fi

  # $ODP can be "overloaded" if using dirs -- actual path not needed anymore
  [ $GIF == 1 ] && ODP=$LOC/$PPN-dvd-$DVDCNT
  DVDOPTS0="-dvd-device $ODP"
  
  TLA=( `echo ``mplayer $DVDOPTS0 dvd://1 -identify -frames 0 2> /dev/null | grep ID_DVD_TITLE | grep LENGTH | cut -d= -f2 | cut -d. -f1``` )

  TLC=1 && TTL=1 && TLG=${TLA[0]}
  while [ $TLC -lt ${#TLA[@]} ]; do
    [ ${TLA[TLC]} -gt $TLG ] && TTL=`expr $TLC + 1` && TLG=${TLA[TLC]}
    TLC=`expr $TLC + 1`
  done
  
  echo "DVD title $TTL is longest at $TLG seconds...using it for DVD $DVDCNT."

  DVDOPTS0="$DVDOPTS0 dvd://$TTL"

  AIA=( `echo ``mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep "aid:" | cut -d: -f5 | cut -b2-4``` )
  ACA=( `echo ``mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep "aid:" | cut -d" " -f6 | cut -d\( -f2 | cut -d\) -f1```)
  ACA=( `echo ${ACA[@]//stereo/2.0}` )
  ALA=( `echo ``mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep ID_AID | cut -d= -f2``` )
 
  echo "Detected language tracks ${AIA[@]} of channels ${ACA[@]} of languages ${ALA[@]}."

  SIA=( `echo ``mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep ID_SUBTITLE_ID | cut -d= -f2``` )
  SLA=( `echo ``mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep ID_SID | cut -d= -f2``` )

  echo "Detected subtitle tracks ${SIA[@]} of language ${SLA[@]}."
  
  CHN=`mplayer $DVDOPTS0 -identify -frames 0 2> /dev/null | grep ID_DVD_TITLE_${TTL}_CHAPTERS | cut -d= -f2`
  CPE=`expr $CHN / $EPN`
  
  echo "Detected $CHN chapters. $EPN episodes specified."
  echo "Assuming $CPE chapters per episode."
  
  IND=0; while [ $IND -lt $EPN ]; do
    CSA[$IND]="`expr $IND \* $CPE + 1`-`expr \( $IND + 1 \) \* $CPE`"
    IND=`expr $IND + 1`
  done
  
  # Hack to make sure the DVD is ready.
  # Otherwise, MPlayer sometimes fails to get the CSS keys on 1st go.
  [ $GIF == 0 ] && mplayer $DVDOPTS0 -frames 30 > /dev/null 2>&1

  for CSC in ${CSA[@]}; do	# For each Chapter Span Current in Chapter Span Array

    [ "$EPC" != "" ] && DVDOPTS0="-dvd-device $ODP dvd://$TTL -chapter $CSC"
    DVDOPTS1="$DVDOPTS0" && DVDOPTS2="$DVDOPTS0"
    
    [ "${SIA[0]}" != "" ] &&
      DVDOPTS1="$DVDOPTS0 -sid ${SIA[0]} -vobsubout $BASE$EPC-${SLA[0]}-${SIA[0]}"
    [ ${#SIA[@]} -gt 1 ] &&
      DVDOPTS2="$DVDOPTS0 -sid ${SIA[1]} -vobsubout $BASE$EPC-${SLA[1]}-${SIA[1]}"
  
    [ "$EXT" != "" ] &&
      echo "Ripping chapters $CSC of title $TTL to $BASE$EPC-mp.avi"
  
    [ "$EXT" != "" ] && echo -n "Pass 1 of 2..."
    $TCS $EXT $NCS mencoder $DVDOPTS1 -nosound $X264OPTS:pass=1 -vf $VF -ofps $FR -o /dev/null
    [ "$EXT" != "" ] && echo -n "Pass 2 of 2..."
    $TCS $EXT $NCS mencoder $DVDOPTS2 -nosound $X264OPTS:pass=2 -vf $VF -ofps $FR -o $BASE$EPC-mp.avi
    rm divx2pass.log*
    
     IND=0; for AIC in ${AIA[@]}; do	# For each Audio ID Current in Audio ID Array
      AUDFIL=$BASE$EPC-${ALA[$IND]}-${ACA[$IND]}-$IND
      [ "$EXT" != "" ] &&
        echo -n "Ripping audio track $AIC to $AUDFIL.wav..."
      $TCS $EXT $NCS mplayer $DVDOPTS0 -aid $AIC -vc null -vo duumy -ao pcm:fast:file=$AUDFIL.wav
      if [ -f /usr/bin/flac ]; then
        [ "$EXT" != "" ] && echo -n "Using FLAC to compress WAV"
        $TCS $EXT $NCS flac --best $AUDFIL.wav
        if [ -f $AUDFIL.flac ]; then
          [ "$EXT" != "" ] && echo -n "FLAC successful, "
          rm -v $AUDFIL.wav
       fi
      fi
      
      IND=`expr $IND + 1`
      
    done				# Done with Audio Current loop

    [ "$EPC" == "" ] && break
    EPC=`printf "%03d" \`expr $EPC - 1\``
    [ $EPC -lt $EPL ] && break
    
  done					# Done with Chap Span loop

  if [ $DVDCNT -lt ${#ESA[@]} -a $GIF != 1 ]; then
    udisks --unmount $ODP && eject -r $ODP
    [ -f /usr/bin/beep ] && beep $BEEP_SEQ
    echo "DVD $DVDCNT has been ripped."
    read -p "Insert next DVD and press Enter..."
    eject -t $ODP && wait_for_medium
  fi
  
done					# Done with Episode Span loop

exit 0

srs-sync

Goal

Help adjust audio to sync with video by taking the source files generated by srs-dump. Loop between asking the user for a sync adjustment and creating a temporary MKV file to test the results until audio is synced back to video.

Status

Not started.

History

What follows is for historical purposes.


I didn’t want people bitching about typos for invalid language codes, so I went and got all the codes from the official ISO 639-1 standard. There’s 184 of them, if you’re curious. It won’t help the user if they mis-type another valid language code (example: ka when the user meant ja), but mind-reading isn’t a job here. :)

This will be changed to the ISO 639-2 standard. The script will expect 3 letters, and knows the corresponding 2-letter code as used in DVDs

CODE

X264ENCOPTS=bitrate=2500:frameref=5:bframes=5:b_pyramid:weight_b:direct_pred=auto:partitions=all:8x8dct:me=umh:subq=7:mixed_refs:trellis=2:threads=0
VALCODES=&quot;aa ab ae af ak am an ar as av ay az ba be bg bh bi bm bn bo br bs ca ce ch co cr cs cu cv cy da de dv dz ee el en eo es et eu fa ff fi fj fo fr fy ga gd gl gn gu gv ha he hi ho hr ht hu hy hz ia id ie ig ii ik io is it iu ja jv ka kg ki kj kk kl km kn ko kr ks ku kv kw ky la lb lg li ln lo lt lu lv mg mh mi mk ml mn mr ms mt my na nb nd ne ng nl nn no nr nv ny oc oj om or os pa pi pl ps pt qu rm rn ro ru rw sa sc sd se sg si sk sl sm sn so sq sr ss st su sv sw ta te tg th ti tk tl tn to tr ts tt tw ty ug uk ur uz ve vi vo wa wo xh yi yo za zh zu&quot;

#==============================================================================
#=========================== FUNCTION DECLARATIONS ============================
#==============================================================================

function convert_stamphms_stampsec {
  HOUR=&quot;`echo $1 | cut -d':' -f1`&quot;
  MINS=&quot;`echo $1 | cut -d':' -f2`&quot;
  SECS=&quot;`echo $1 | cut -d':' -f3`&quot;
  expr $HOUR \* 3600 + $MINS \* 60 + $SECS
}

function convert_stampsec_stamphms {
  HOUR=&quot;`expr $1 / 3600`&quot;
  MINS=&quot;`expr $1 % 3600 / 60`&quot;
  SECS=&quot;`expr $1 % 3600 % 60`&quot;
  printf &quot;%02d:%02d:%02d&quot; $HOUR $MINS $SECS
}

function limited_key_verified_string {
  # $1 = prompt
  # $2 = number of keys to accept
  # $3 = valid input

  OKINPUT=0
  while [ $OKINPUT -eq 0 ]; do
    read -n $2 -p &quot;$1&quot; USRINPUT
    for VALINPUT in $3 ; do
      [ &quot;$USRINPUT&quot; == &quot;$VALINPUT&quot; ] &amp;&amp; OKINPUT=1
    done
    [ $OKINPUT -eq 0 ] &amp;&amp; printf &quot;\a\r%79s\r&quot; &quot; &quot; 1&gt;&amp;2
  done
  printf &quot;$USRINPUT&quot;
}

function limited_key_verified_number {
  # $1 = prompt
  # $2 = number of keys to accept
  # $3 = input to be greater than or equal to
  # $4 = input to be less than or equal to

  OKINPUT=0
  while [ $OKINPUT -eq 0 ]; do
    read -n $2 -p &quot;$1&quot; USRINPUT
    [ &quot;$USRINPUT&quot; -ge &quot;$3&quot; -a &quot;$USRINPUT&quot; -le &quot;$4&quot; ] &amp;&amp; OKINPUT=1 || printf &quot;\a\r%79s\r&quot; &quot; &quot; 1&gt;&amp;2
  done
  printf &quot;$USRINPUT&quot;
}

function onelineinput {
  OKINPUT=0
  while [ $OKINPUT -eq 0 ]; do
    read -e -p &quot;$1&quot; USERINPUT
    for GOODKEYS in $2; do
      [ &quot;$GOODKEY&quot; == &quot;$USERINPUT&quot; ] &amp;&amp; GOODINPUT=1
    done
    [ $OKINPUT -eq 0 ] &amp;&amp; printf &quot;\a\r%79s\r&quot; &quot; &quot; 1&gt;&amp;2
  done
  printf &quot;$ONELINEINPUT&quot;
}

function onenonblank {
  ONENONBLANK=&quot;&quot;
  while [ &quot;$ONENONBLANK&quot; == &quot;&quot; ]; do
    read -e -p &quot;$1&quot; ONENONBLANK
    [ &quot;$ONENONBLANK&quot; == &quot;&quot; ] &amp;&amp; printf &quot;\a\r%79s\r&quot; &quot; &quot; 1&gt;&amp;2
  done
  printf &quot;$ONENONBLANK&quot;
}

function pause {
  read -n 1 -p &quot;$1&quot;
  echo
}

function refresh_steps {
  STEPPROMPT=&quot;STEP &quot;
  for (( LCV=1; $LCV&amp;lt;=4; LCV++ )); do
    [ $LCV == $1 ] &amp;&amp; STEPPROMPT=&quot;$STEPPROMPT[$LCV]&quot; || STEPPROMPT=&quot;$STEPPROMPT $LCV &quot;
  done
  printf &quot;\e[7m%-50s%30s\e[m\n&quot; &quot;$STEPPROMPT &quot; &quot;$2&quot;
  echo
}

function program_header {
  clear
  printf &quot;\e[7m%80s\r%*s\e[m\n&quot; &quot; &quot; `expr \( 80 + ${#1} \) / 2` &quot;$1&quot;
}

CONSIDERATIONS

Chapter markers in VOB streams must appear on I-frames, so there's no need to worry about garbled pictures.

Just because they're supposed to doesn't mean that they do. Surprise surprise.

Using mencoder to dump video and audio into an avi (-ovc copy -oac copy) was considered to save disk space, but problems arose on VOB streams from poorly-mastered DVDs whose framerate switches constantly and non-sensically between 24fps and 30fps. (Vandread -- orginal Geneon release -- and The Adventures of Mini-Goddess(es) -- covert art fucks up the title but the DVD filesystem label is correct -- aka Ah! My Goddess! Being Small Is Convenient, are two examples.) While the copy can be made 24fps or 30fps, the copy function sticks to one frame rate and either drops or duplicates frames if the input frame rate and output frame rate do not match. Going from 24fps to 30fps can leave the resulting output stream with a mix of hard-telecined and hard-duped content. Going from 30fps to 24fps leaves out frames needed to re-assemble clean 24fps progressive footage from telecined content.

The info file route was chosen to make both the batching and coding processes easier. On the coding side, the process can be more easily broken into chunks without having the chunks rely on one another. On the batching side, the user isn't constrained to use the utilities in one fell swoop. This was mostly a consideration for Red Vs. Blue Seasons 1 through 5. The series is 30fps pure interlaced and letter-boxed in Seasons 1 - 4. The crop settings are mostly the same in Seasons 1 - 3, except for a few episodes here and there. Season 4's crop settings are all over the place as the team switched between Halo 1 and Halo 2. Season 5 is encoded professionally with anamorphed 24fps progressive footage.

The info file route was a dumb move. It didn't provide anything that couldn't be readily discovered with a quick play.

CODE

#!/bin/bash

source ./fun.sh

#==============================================================================
#================================ MAIN PROGRAM ================================
#==============================================================================

program_header &quot;DVS - DUMP VOB SPLITS&quot;
refresh_steps 1 &quot;PROJECT INFO&quot;

DVDDRIVE=`onenonblank &quot;DVD device (ex: /media/cdrom): &quot;`
PROJNAME=`onenonblank &quot;Name of this project: &quot;`
PROJPATH=`onenonblank &quot;Place to put the folder for this project: &quot;`
PROJDISC=`limited_key_verified_string &quot;How many DVDs in this project (1-9): &quot; 1 &quot;1 2 3 4 5 6 7 8 9&quot;`

# This could be made better by stepping through the path and making sure each
# folder in the path actually exists.

mkdir &quot;$PROJPATH&quot;
mkdir &quot;$PROJPATH/$PROJNAME&quot;

for (( CURRDISC=1; $CURRDISC&gt;=$PROJDISC; $CURRDISC++ )); do

  program_header &quot;DVS - DUMP VOB SPLITS&quot;
  refresh_steps 2 &quot;TITLE SELECTION&quot;
  pause &quot;Insert DVD $CURRDISC and press any key to continue.&quot;
  echo &quot;Grabbing title information from this DVD...&quot;

  MPOUTPUT=`mplayer -dvd-device $DVDDRIVE dvd://1 -identify -frames 0 2&gt; /dev/null | grep -E &quot;^ID_DVD_TITLE_(.|..)&quot;`
  TITLELEN=( 0 `printf &quot;$MPOUTPUT&quot; | grep &quot;_LENGTH&quot; | cut -d'=' -f2 | cut -d'.' -f1 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
  TITLECHP=( 0 `printf &quot;$MPOUTPUT&quot; | grep &quot;_CHAPTERS&quot; | cut -d'=' -f2 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
  TITLEANG=( 0 `printf &quot;$MPOUTPUT&quot; | grep &quot;_ANGLES&quot; | cut -d'=' -f2 |  sed -e :a -e '$!N; s/\n/ /g; ta'` )

  for (( CURTITLE=1; $CURTITLE&amp;lt;${#TITLELEN[@]}; CURTITLE++ )); do 
    MPOUTPUT=`mplayer -dvd-device $DVDDRIVE dvd://$CURTITLE -identify -frames 0 2&gt; /dev/null | grep -E &quot;^(audio|subtitle)&quot;`

    AUDIOFMT=( xxx `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^audio stream:&quot; | cut -d: -f3 | cut -d' ' -f2 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
    AUDIOCHN=( 0.0 `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^audio stream:&quot; | cut -d: -f3 | cut -d' ' -f3 | cut -c2-4 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
    AUDIOLNG=( xx `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^audio stream:&quot; | cut -d: -f4 | cut -c2-3 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
    AUDIOIDS=( 999 `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^audio stream:&quot; | cut -d: -f5 | cut -c2-4 | sed -e :a -e '$!N; s/\n/ /g; ta'` )
    SBTTLIDS=( 99 `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^subtitle&quot; | cut -d&quot; &quot; -f5 | printf &quot;%02d&quot; | sed -e :a -e '$!N; s/\n/ /g; ta'` )
    SBTTLLNG=( xx `printf &quot;$MPOUTPUT&quot; | grep -E &quot;^subtitle&quot; | cut -d&quot; &quot; -f7 | sed -e :a -e '$!N; s/\n/ /g; ta'` )

    VALAUDID[$CURTITLE]=&quot;${AUDIOIDS[@]}&quot;
    VALSUBID[$CURTITLE]=&quot;${SBTTLIDS[@]}&quot;

    for (( CURAUDIO=1; $CURAUDIO&amp;lt;${#AUDIOIDS[@]}; CURAUDIO++ )); do
      AUDIOSTR[$CURTITLE]=&quot;${AUDIOSTR[$CURTITLE]} ${AUDIOIDS[$CURAUDIO]}:${AUDIOLNG[$CURAUDIO]}-${AUDIOFMT[$CURAUDIO]}-${AUDIOCHN[$CURAUDIO]}&quot;
    done

    for (( CURSBTTL=1; $CURSBTTL&amp;lt;${#SBTTLIDS[@]}; CURSBTTL++ )) do
      [ &quot;${SBTTLLNG[$CURSBTTL]}&quot; == &quot;unknown&quot; ] &amp;&amp; SBTTLLNG[$CURSBTTL]='??'
      SBTTLSTR[$CURTITLE]=&quot;${SBTTLSTR[$CURTITLE]} ${SBTTLIDS[$CURSBTTL]}:${SBTTLLNG[$CURSBTTL]}&quot;
    done

    AUDIOSTR[$CURTITLE]=`printf &quot;${AUDIOSTR[$CURTITLE]}&quot; | cut -c1 --complement`
    SBTTLSTR[$CURTITLE]=`printf &quot;${SBTTLSTR[$CURTITLE]}&quot; | cut -c1 --complement`

  done

  while [ &quot;$VIEWSLCT&quot; != &quot;s&quot; ]; do

    program_header &quot;DVS - DUMP VOB SPLITS&quot;
    refresh_steps 2 &quot;TITLE SELECTION&quot;
    echo &quot;TI RUN-TIME CH AN ----------------AUDIO TRACKS---------------- ----SUBTITLES----&quot;

    for (( CURTITLE=1; $CURTITLE&amp;lt;${#TITLELEN[@]}; CURTITLE++ )); do 
      printf &quot;%02s %s %02s %02s %44s %17s\n&quot; $CURTITLE &quot;`convert_stampsec_stamphms ${TITLELEN[$CURTITLE]}`&quot; ${TITLECHP[$CURTITLE]} ${TITLEANG[$CURTITLE]} &quot;${AUDIOSTR[$CURTITLE]}&quot; &quot;${SBTTLSTR[$CURTITLE]}&quot;
    done

    echo
    VIEWSLCT=`limited_key_verified_string &quot;Do you want to (v)iew or (s)elect titles? &quot; 1 &quot;v s&quot;`
    if [ $VIEWSLCT == v ]; then
      MAXTITLE=`expr ${#TITLELEN[@]} - 1`
      echo; VIEWTITL=`limited_key_verified_number &quot;Title (nn)? &quot; 2 1 $MAXTITLE`
      [ &quot;$VIEWTITL&quot; == &quot;08&quot; ] &amp;&amp; VIEWTITL=8
      [ &quot;$VIEWTITL&quot; == &quot;09&quot; ] &amp;&amp; VIEWTITL=9
      VIEWANGL=`limited_key_verified_number &quot; Angle (n)? &quot; 1 1 ${TITLEANG[$VIEWTITL]}`
      VIEWAUD=`limited_key_verified_string &quot; Audio (nnn)? &quot; 3 &quot;${VALAUDID[$VIEWTITL]}&quot;`
      VIEWSUB=`limited_key_verified_string &quot; Subtitle (nn)? &quot; 2 &quot;${VALSUBID[$VIEWTITL]} 99&quot;`
      [ &quot;$VIEWSUB&quot; == &quot;08&quot; ] &amp;&amp; VIEWSUB=8
      [ &quot;$VIEWSUB&quot; == &quot;09&quot; ] &amp;&amp; VIEWSUB=9
      echo; echo &quot;Playing selection...&quot;
      mplayer -dvd-device $DVDDRIVE dvd://$VIEWTITL -dvdangle $VIEWANGL -aid $VIEWAUD -sid $VIEWSUB -msglevel all=-1 &amp;&gt; /dev/null
    fi
  done
    
  echo
  echo &quot;You can specify multiple titles in a space-separated list (ex: 1 2 3)&quot;
  read -e -p &quot;Which titles do you want to rip? &quot; CHOSTITL

  for CURRTITL in $CHOSTITL; do

    program_header &quot;DVS - DUMP VOB SPLITS&quot;
    refresh_steps 3 &quot;CHAPTER SELECTION&quot;

    TOTLCHAP=&quot;`mplayer -dvd-device $DVDDRIVE dvd://$CURRTITL -identify -frames 0 2&gt; /dev/null | grep 'chapters in this DVD title.' | cut -d' ' -f3`&quot;
    CHPLINES=`expr $TOTLCHAP / 5 + 1`
    CHPLSTHMS=&quot;`mplayer -dvd-device $DVDDRIVE dvd://$CURRTITL -identify -frames 0 2&gt; /dev/null | grep '^CHAPTERS:' | cut -d' ' -f2`&quot;

    for (( CURRCHAP=1; $CURRCHAP&amp;lt;=$TOTLCHAP; CURRCHAP++ )); do
      ONECHPHMS=&quot;`echo $CHPLSTHMS | cut -d',' -f$CURRCHAP`&quot;
      ONECHPSEC=&quot;`convert_stamphms_stampsec $ONECHPHMS`&quot;
      CHPLSTSEC=&quot;${CHPLSTSEC}${ONECHPSEC},&quot;
    done

    CHPLSTSEC=&quot;${CHPLSTSEC}${TITLELEN[$CURRTITL]}&quot;

    echo &quot;CH -LENGTH-  ||  CH -LENGTH-  ||  CH -LENGTH-  ||  CH -LENGTH-  ||  -CH -LENGTH-&quot;

    for (( CHP_YPOS=1; $CHP_YPOS&amp;lt;=$CHPLINES; CHP_YPOS++ )); do
      for (( CHP_XPOS=0; $CHP_XPOS&amp;lt;5; CHP_XPOS++ )); do
        CURRCHAP=`expr $CHP_XPOS \* $CHPLINES + $CHP_YPOS`
        if [ $CURRCHAP -le $TOTLCHAP ]; then      
          NEXTCHAP=&quot;`expr $CURRCHAP + 1`&quot;
          BEGSTAMP=&quot;`echo $CHPLSTSEC | cut -d',' -f$CURRCHAP`&quot;
          ENDSTAMP=&quot;`echo $CHPLSTSEC | cut -d',' -f$NEXTCHAP`&quot;
          CHAPLEN=&quot;`expr $ENDSTAMP - $BEGSTAMP`&quot;
          printf &quot;%2s %8s&quot; $CURRCHAP `convert_stampsec_stamphms $CHAPLEN`
          [ $CHP_XPOS -ne 4 ] &amp;&amp; printf &quot;  ||  &quot;
         fi
      done  
      printf &quot;\n&quot;
    done

    echo
    STEPCHAPS=&quot;`limited_key_verified_string &quot;Would you like to step through the chapters to see what you need (y/n)? &quot; &quot;1&quot; &quot;y n&quot;`&quot;
    echo

    if [ $STEPCHAPS == y ]; then
      echo &quot;MPlayer will spawn in its own window for each chapter.&quot;
      echo &quot;Press Q to stop the video and go on to the next one.&quot;
      pause &quot;Press any key to continue...&quot;
      for (( CURSTEPCHAP=1; $CURSTEPCHAP&amp;lt;=$TOTLCHAP; CURSTEPCHAP++ )); do
        echo &quot;Playing chapter $CURSTEPCHAP&quot;
        mplayer -dvd-device $DVDDRIVE dvd://$CURRTITL -chapter $CURSTEPCHAP-$CURSTEPCHAP -msglevel all=0 &amp;&gt; /dev/null
      done
    fi

    echo &quot;You must specify a range of chapters, even if it is only one chapter.  (ex: 1-1)&quot;
    echo &quot;You can specify a list of ranges in a space-separated list.  (ex: 1-1 2-3 4-4)&quot;
    read -e -p &quot;Which chapters do you want to rip for title $CURRTITL? &quot; CHOSCHAP

    program_header &quot;DVS - DUMP VOB SPLITS&quot;
    refresh_steps 4 &quot;RIP SELECTIONS TO HARD DRIVE&quot;

    for CURRCHAP in $CHOSCHAP; do

      BEGCHAP=`echo $CURRCHAP | cut -d&quot;-&quot; -f1`
      ENDCHAP=`echo $CURRCHAP | cut -d&quot;-&quot; -f2`
      FILEPATH=&quot;$PROJPATH/$PROJNAME/D`printf &quot;%02d&quot; $CURRDISC`T`printf &quot;%02d&quot; $CURRTITL`C`printf &quot;%02d&quot; $BEGCHAP`-`printf &quot;%02d&quot; $ENDCHAP`&quot;

      echo &quot;DVD TITLE `printf &quot;%02d&quot; $CURRTITL` CHAPTERS `printf &quot;%02d&quot; $BEGCHAP` - `printf &quot;%02d&quot; $ENDCHAP`: WRITING INFO FILE&quot;
      echo &quot;ANGS=${TITLEANG[$CURRTITL]}&quot; &gt; $FILEPATH.info
      echo &quot;AUDS=${AUDIOSTR[$CURRTITL]}&quot; &gt;&gt; $FILEPATH.info
      echo &quot;SUBS=${SBTTLSTR[$CURRTITL]}&quot; &gt;&gt; $FILEPATH.info

      echo &quot;DVD TITLE `printf &quot;%02d&quot; $CURRTITL` CHAPTERS `printf &quot;%02d&quot; $BEGCHAP` - `printf &quot;%02d&quot; $ENDCHAP`: DUMPING VOB STREAM&quot;
      mplayer -dvd-device $DVDDRIVE dvd://$CURRTITL -chapter $CURRCHAP -dumpstream -dumpfile &quot;$FILEPATH.vob&quot; -msglevel all=0 &amp;&gt; /dev/null

    done
  done
done

echo &quot;All titles and chapters of all discs have been ripped to the hard drive.&quot;
pause &quot;Press any key to continue.&quot;