SRS Project

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.

FUN – SRS function script, provides common functions
DVS – dump vob streams by disc, title, and chapter.
BEF – batch encode files
JAF – (optionally) join avi files
BRF – batch rename files


FUN

GOAL

Clean up code and get fancy with stuff that would otherwise make the code a PITA to wade through.

HOW

Relegate commonly-used groups of lines into functions. Provide C-like returns (strings) with echos, printfs, and other commands whose output goes through stdin/out.

CONSIDERATIONS

You have to be careful in BASH. The echo command will strip out existing newlines and insert a newline at the end without the -n option. Printf is more desirable.

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. :)

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="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"

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

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

function convert_stampsec_stamphms {
  HOUR="`expr $1 / 3600`"
  MINS="`expr $1 % 3600 / 60`"
  SECS="`expr $1 % 3600 % 60`"
  printf "%02d:%02d:%02d" $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 "$1" USRINPUT
    for VALINPUT in $3 ; do
      [ "$USRINPUT" == "$VALINPUT" ] && OKINPUT=1
    done
    [ $OKINPUT -eq 0 ] && printf "\a\r%79s\r" " " 1>&2
  done
  printf "$USRINPUT"
}

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 "$1" USRINPUT
    [ "$USRINPUT" -ge "$3" -a "$USRINPUT" -le "$4" ] && OKINPUT=1 || printf "\a\r%79s\r" " " 1>&2
  done
  printf "$USRINPUT"
}

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

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

function pause {
  read -n 1 -p "$1"
  echo
}

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

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

DVS

GOAL

Ease the series-ripping process by dumping streams by discs, titles, chapters, and angles.

HOW

DVS provides an interface that will cycle through a user-specified number of discs. For each disc, DVS will provide an informative list of titles available, including run time, chapter count, angles available, audio track info, and subtitle info. DVS allows the user to preview titles before a selection is made. Once title selections are made, SRS will then provide an informative list of chapters and ask for a list of chapters to dump from. When DVS dumps the VOB stream, it will also write an info file for the angles, audio tracks, and subtitles it found.

CONSIDERATIONS

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

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 series, orginal Geneon release. This is probably the main reason Funimation is doing a re-release.) 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.

CODE

#!/bin/bash

source ./fun.sh

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

program_header "DVS - DUMP VOB SPLITS"
refresh_steps 1 "PROJECT INFO"

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

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

mkdir "$PROJPATH"
mkdir "$PROJPATH/$PROJNAME"

for (( CURRDISC=1; $CURRDISC>=$PROJDISC; $CURRDISC++ )); do

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

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

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

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

    VALAUDID[$CURTITLE]="${AUDIOIDS[@]}"
    VALSUBID[$CURTITLE]="${SBTTLIDS[@]}"

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

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

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

  done

  while [ "$VIEWSLCT" != "s" ]; do

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

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

    echo
    VIEWSLCT=`limited_key_verified_string "Do you want to (v)iew or (s)elect titles? " 1 "v s"`
    if [ $VIEWSLCT == v ]; then
      MAXTITLE=`expr ${#TITLELEN[@]} - 1`
      echo; VIEWTITL=`limited_key_verified_number "Title (nn)? " 2 1 $MAXTITLE`
      [ "$VIEWTITL" == "08" ] && VIEWTITL=8
      [ "$VIEWTITL" == "09" ] && VIEWTITL=9
      VIEWANGL=`limited_key_verified_number " Angle (n)? " 1 1 ${TITLEANG[$VIEWTITL]}`
      VIEWAUD=`limited_key_verified_string " Audio (nnn)? " 3 "${VALAUDID[$VIEWTITL]}"`
      VIEWSUB=`limited_key_verified_string " Subtitle (nn)? " 2 "${VALSUBID[$VIEWTITL]} 99"`
      [ "$VIEWSUB" == "08" ] && VIEWSUB=8
      [ "$VIEWSUB" == "09" ] && VIEWSUB=9
      echo; echo "Playing selection..."
      mplayer -dvd-device $DVDDRIVE dvd://$VIEWTITL -dvdangle $VIEWANGL -aid $VIEWAUD -sid $VIEWSUB -msglevel all=-1 &> /dev/null
    fi
  done

  echo
  echo "You can specify multiple titles in a space-separated list (ex: 1 2 3)"
  read -e -p "Which titles do you want to rip? " CHOSTITL

  for CURRTITL in $CHOSTITL; do

    program_header "DVS - DUMP VOB SPLITS"
    refresh_steps 3 "CHAPTER SELECTION"

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

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

    CHPLSTSEC="${CHPLSTSEC}${TITLELEN[$CURRTITL]}"

    echo "CH -LENGTH-  ||  CH -LENGTH-  ||  CH -LENGTH-  ||  CH -LENGTH-  ||  -CH -LENGTH-"

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

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

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

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

    program_header "DVS - DUMP VOB SPLITS"
    refresh_steps 4 "RIP SELECTIONS TO HARD DRIVE"

    for CURRCHAP in $CHOSCHAP; do

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

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

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

    done
  done
done

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

BEF

GOAL

Provide an informative and intuitive batch interface for angle selection, audio selection, subtitle ripping, crop settings, and common video filters. Allow the user to get on with their daily lives while BEF encodes over the next two days.

HOW

Not there yet.

CONSIDERATIONS

The console sucks for this job. :)


JAF

GOAL

Provide an interface to join the resulting AVI files.

HOW

Not there yet

CONSIDERATIONS

Episodic content sometimes use hooks — a brief period of content that appears before the opening credits or after the ending credits. While the content is usually chaptered accordingly, this will make for a pretty strange file division if you leave out the credits. This script will help you bring the pieces back together.


BRF

GOAL

Provide an interface to rename files in a directory.

HOW

Not there yet.

CONSIDERATIONS

To keep things short and simple, DVS uses really terse file names. This helps users make the file names mean something.

No Comments Yet »

RSS feed for comments on this post. TrackBack URI

Leave a comment

XHTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Blog at WordPress.com. | Theme: Pool by Borja Fernandez.
Entries and comments feeds.