Here's a script I use quite a bit........I have a friend who films and edits videos (singers and bands), which get put on a local Comcast channel, periodically. Then he has a master disc made and I rip-n-burn a bunch for him.....
So this script actually started out as two separate scripts, about one page of code for each........Then I started adding a few features, here and there. But, after a few mishaps, I started adding sanity checks until I felt comfortable and could rest easy that I did a good job.......If there was a mistake to be made, I probably made it........
This is not for editing, only ripping and burning.....But rest assured, it will get done right, or the script will scream and holler if something is wrong......
The script is well commented, so the script is also the documentation. Save it as
in the same directory. A good place is to put it is in
Also, this script is not only bash-specific but also made to run on Linux, due to probing the /proc directory for getting the drive info.......I suppose it could be ported to other OSes, but I'll leave that to you.........
- NOTE: This script will always have the latest changes/updates included, while any changes made will be announced in one of the posts below.
#!/bin/sh
################################################################################
# burnvideo/ripvideo - burn and rip DVD videos
#
# This is actually two programs combined into one. The script is saved as
# '/usr/local/bin/burnvideo', and then a 'ripvideo' symlink is made in the same
# directory. Example:
# ln -s burnvideo /usr/local/bin/ripvideo
#
# Requirements:
# Linux OS
# dvdbackup growisofs mkisofs <-- might have to install these
# cat dd isosize fold grep mkdir mount rm rmdir sed tac tr umount
#
# NOTE: This is a Linux only program, due to probing the /proc directory for
# device info. For the external programs, the first three programs may have
# to be installed, while the rest are standard programs and shipped with the
# majority of Linux distros.
#
#
# Written by CTWaley (aka, thegeekster), (c) February 2006
#
# You may do anything you want with this script, as long as the line directly
# above remains with it. Which means, give credit where credit is due!
#
################################################################################
## This is the directory to place the temporary video image when burning discs.
## It should be located in a partition with plenty of room to hold the image.
VIDTMP="/home/fun/video/tmp"
###### DO NOT EDIT BELOW THIS POINT ############################################
## Since we'll be creating files, and possibly directories, make sure we have
## sane permissions
umask 0022
readonly PROG=${0##*/}
## Assigning aliases for easier typing and not forgetting some needed options:
alias echo="echo -e"
alias fold="fold -s"
alias growisofs="growisofs -dvd-compat \
-use-the-force-luke=wrvfy,noload,dao \
-speed=1"
alias sed="sed -r"
alias toUpper="tr [[:lower:]] [[:upper:]]"
## The hex value '\x1b' is the same as the more familiar octal value '\033',
## which represents the <Esc> key on the keyboard
B="\x1b[1m" # Bold
b="\x1b[0m" # Unbold (actually 'undo' whatever was 'done' in the first place)
## These two help identify what the hex values we're using mean"
TAB="\x09"
LF="\x0a"
## CD/DVD drive info using the /proc filesystem instead of 'cdrecord -scanbus'
DEVINFO="$(sed -n '/drive name:/,/DVD-RAM/p' </proc/sys/dev/cdrom/info | sed "s/$TAB+/$TAB/g")"
## Arrays to hold the results of the DEVINFO query. Note - We must reverse the
## results in case there's more than one valid device. The dev/cdrom/info lists
## the devices in reverse order, such as:
## drive name: sr1 sr0
##
## Useful lines to check for in '/proc/sys/dev/cdrom/info' are:
## drive name:
## Can play audio:
## Can write CD-R:
## Can write CD-RW:
## Can read DVD:
## Can write DVD-R:
## Can write DVD-RAM:
##
DRIVE=($(grep 'drive name:' <<<"$DEVINFO" | sed "s/.+:$TAB(.+)/\1/; s/$TAB/$LF/" | tac))
NUMDRIVE=${#DRIVE[@]} ## Number of drives found
# Can read DVDs (0 means 'no', 1 means 'yes')
READDVD=($(grep 'read DVD:' <<<"$DEVINFO" | sed "s/.+:$TAB(.+)/\1/; s/$TAB/$LF/" | tac))
# Can write DVDs (0 means 'no', 1 means 'yes')
WRITEDVDR=($(grep 'write DVD-R:' <<<"$DEVINFO" | sed "s/.+:$TAB(.+)/\1/; s/$TAB/$LF/" | tac))
unset DEVINFO ## We're done with this variable, so let's free up a little memory
##
## Program functions
##
error_msg(){
echo "\n===> $PROG: ERROR: $*" | fold >&2
}
warn_msg(){
echo "\n===> WARNING: $*" | fold >&2
}
## Find the proper type of device asked for and assign to DEV, if it exists.
## Usage: find_dev ${READDVD[*]} -or- find_dev ${WRITEDVD[*]}
find_dev() {
local -i SUB
local -a TEMP=($1)
## Looping in case more than one drive found
for ((SUB=0 ; SUB<NUMDRIVE ; SUB++))
do ## Find first value of 1, assign corresponding drive to DEV and return successful
test ${TEMP[$SUB]} -eq 1 && export DEV=/dev/${DRIVE[$SUB]} && return 0
done
## Else found only 0(s) (not 1), so return unsuccessful
return 1
}
## Find iso size. Usage: find_size image|device
find_size(){
# du -sb <"$1" | cut -f1
isosize "$1"
}
## Random generator function, called from the rip_video() function. (Some of you
## might recognize this as a variation of "gen_pass()" from my version of a
## random password generator <http://bashscripts.org/viewtopic.php?p=808#808> ;-) )
mk_temp(){ ## Usage: VAR=$(mk_temp)
local RNDDEV
test -c /dev/urandom && RNDDEV=/dev/urandom || RNDDEV=/dev/random
echo /tmp/ripvid.$(LC_ALL=C tr -dc "[:alnum:]" <$RNDDEV | head -c 6)
}
## Called from the burn_video() function, if needed. Usage: tmp_iso $SOURCE
tmp_iso(){
local ISOINFO="$(isoinfo -d -i $SOURCE 2>/dev/null)"
export IMAGE="$(sed -n 's/Volume id: (.+)/\1/p' <<<"$ISOINFO").tmp"
## Function that reads the raw data from a device, using 'dd'.
## NOTE; This function is based on a script called 'rawread' by Steve Litt,
## which can be found at "Coasterless CD Burning"
## <http://www.troubleshooters.com/linux/coasterless.htm#rawread>:
raw_read(){
local -i BS COUNT
BS=$(sed -n 's/Logical block size is: (.+)/\1/p' <<<"$ISOINFO")
test -z "$BS" && error_msg "No block size present\n" && exit 1
COUNT=$(sed -n 's/Volume size is: (.+)/\1/p' <<<"$ISOINFO")
test -z "$COUNT" && error_msg "No block count present\n" && exit 1
dd if=$1 bs=$BS count=$COUNT conv=notrunc,noerror > "$VIDTMP/$IMAGE"
IMGSIZE="$(find_size $VIDTMP/$IMAGE)" ## Finding image size for comparison
}
test ! -d "$VIDTMP" && ## Making sure the temp directory exists
{ warn_msg "The directory, ${B}${VIDTMP}${b}, does not exist."
return 1;}
SRCSIZE="$(find_size $SOURCE)" ## Finding the source size on the disc
## If an image already exists, is it the same size as what's on the disc?
if [ -f "$VIDTMP/$IMAGE" ];then
IMGSIZE="$(find_size $VIDTMP/$IMAGE)"
test $IMGSIZE -ne $SRCSIZE && ## If size mismatch, do it over
{ echo "Recreating temp image, ${B}${IMAGE}${b}..."
raw_read "$SOURCE";}
else ## No image found
echo "===> Creating temp image of ${B}${SOURCE}${b}..."
raw_read "$SOURCE"
fi
test $IMGSIZE -ne $SRCSIZE && ## Double-checking for any size mismatches
{ warn_msg "There is a size mismatch between ${B}${SOURCE}${b} and ${B}$VIDTMP/${IMAGE}${b}. This may mean there isn't enough room to create the image, or there may be hardware/software issues to be taken care of."
return 1;}
## Reassigning SOURCE to the temp image
SOURCE="$VIDTMP/$IMAGE"
return 0
}
print_usage(){ ## Usage: print_usage
if [ "$PROG" == "ripvideo" ];then
echo "\n\tUsage: $PROG /path/to/video/directory\n"
elif [ "$PROG" == "burnvideo" ];then
cat<<_USAGE_
Usage: $PROG [-c NUM] /my/video/directory
$PROG [-c NUM] /my/video/filename.iso
The name given to the video will be the name of the directory/ISO given. NUM is
the number of copies to be made (optional).
_USAGE_
fi
}
## This is an important function to determine what we need to do, based on the
## return code. Usage: test_iso image|block device
test_iso(){
isoinfo -i "$1" >/dev/null 2>&1
return $?
## Some results of the return code.
:<<_RETURNS_
0: target is ISO 9660 formatted
1: target is not an ISO image, it is a file or corrupt image
2: target does not exist
5: target is audio formatted
6: target is not an ISO, it is an empty device (no disc in tray)
13: permission denied to open target (fix perms or run as root)
19: target is not an ISO, it is an unused character device
21: target is not an ISO, it is a directory
22: target is not an ISO, it is a blank/unformatted disc
29: target is not an ISO, it is a character device
30: target is unreadable (missing or bad)
255: target is too small to be a valid image
_RETURNS_
}
## 'burnvideo' program converted to a function. Usage: burn_video "
[email protected]"
burn_video(){
## Check for the existence of a DVD writer:
find_dev "${WRITEDVDR[*]}" ||
{ error_msg "No DVD writer found\n" && exit 1;}
## See if we have 'growisofs'. If not, say so and bail
which growisofs >/dev/null 2>&1 ||
{ error_msg "Cannot find 'growisofs', which is needed for burning DVDs.\n"
exit 1;}
## Does more than one disc need to be burned? If so, chop off the
## option switch in $1 to see if NUM is appended to the switch
case $1 in
--copy*) ## We'll only shift once here since NUM _must_ be appended
test -z "${1/--copy=/}" && ## If empty, say so and bail
{ error_msg "No number of copies specified in ${B}${1}${b}.\n"
print_usage <&2 && exit 1;}
NUM=${1/--copy=/} && shift 1
;;
-c*)
if [ -z ${1/-c/} ]; then
## If empty, assign $2 to NUM and shift the command args twice
NUM="$2" && shift 2
## If still empty, say so and bail
test -z "$NUM" &&
{ error_msg "Not enough arguments on the command line."
print_usage >&2 && exit 1;}
else ## It must not be empty, so we'll only need to shift once
NUM="${1/-c/}" && shift 1
fi
;;
*) ## We don't have any request for more than one disc to burn
NUM=1
;;
esac
## Sanity checks
# Okay let's see if we have a valid number to work with:
[[ $NUM =~ [^[0-9]]* ]] && ## If not all numbers, say so and bail
{ error_msg "${B}${NUM}${b} is not a valid, whole decimal number.\n"
exit 1;}
# Now, let's see if we have any args left after the shifting:
test -z "$1" &&
{ error_msg "Not enough arguments on the command line."
print_usage >&2 && exit 1;}
# Assign SOURCE before test if $1 exists (will also accept 'file:///' URLs):
test "$1" == "." && SOURCE="$(pwd)" || SOURCE="$(sed 's|file:/{1,3}|/|' <<<"$1")"
# Finally, if the last arg does not exist, say so and bail:
test ! -e "$SOURCE" &&
{ error_msg "${B}${SOURCE}${b} does not exist. Might be a typo.\n" && exit 1;}
## This test is to see what action is needed, based on the return code:
test_iso "$SOURCE"
case $? in
0) #<--- We're dealing with a valid iso (either an image or block device)
## Sanity check
test $(egrep '(AUDIO|VIDEO)_TS' <<<"$(isoinfo -p -i $SOURCE)" | wc -l) -ne 2 &&
{ error_msg "The ISO, ${B}${SOURCE##*/}${b}, does not contain a valid video directory structure. You will need to make another one. Make sure there is both a ${B}VIDEO_TS${b} and an ${B}AUDIO_TS${b} subdirectory present.\n" && exit 1;}
## If the source is a block device (not an iso image) and more
## than one burn is requested, then create a temorary image.
## This will reduce the overall time it takes to produce the
## finished videos:
if [ -b $SOURCE -a $NUM -gt 1 ];then
tmp_iso $SOURCE && eject $SOURCE 2>/dev/null ||
{ echo -n "If you wish to continue, the copying will be done directly from drive to drive (on-the-fly). However, the time to create the new discs will be much longer for each disc. Do you wish to continue? [Y|n] " | fold
## A 20-second timeout should be plenty of time to answer
read -t20
## If reply equal "n[o]", abort:
[[ "$REPLY" =~ "^[nN][oO]?$" ]] && echo "===> Aborting\n" && exit 0;}
fi; unset REPLY
SRCSIZE="$(find_size $SOURCE)"
## See if we have enough room to burn a 4.7G DVD:
if [ $SRCSIZE -ge 4700000000 ]; then
warn_msg "The source is too big to fit on a 4.7G DVD. You will need a 'double-layer' disc, and a DVD writer that supports burning double-layered discs. Do you wish to continue? [N|y] " | fold && read -t20
## Reply must equal "y[es]", or abort:
[[ "$REPLY" =~ "^[yY]([eE][sS])?$" ]] || echo "===> Aborting\n" && exit 0
fi
## Looping for the number of discs to burn
for ((INT=1 ; INT<=NUM ; INT++))
do
echo "${B}---> Starting disc $INT of ${NUM}:${b}"
## Feature - Insert blank disc before burning
test_iso $DEV
test $? -ne 22 &&
{ eject $DEV 2>/dev/null
read -p "Place blank DVD in tray and press <Enter> to continue...";}
## Burn, baby, burn.....Or holler a warning if something goes wrong:
growisofs -Z $DEV="$SOURCE" ||
{ warn_msg "Problem burning ISO image to disk. :-(\n" && continue;}
## Sanity checks
test_iso $DEV
if [ $? -ne 0 ];then
warn_msg "This newly created disc is not a valid ISO 9660 formatted disc. You will need to burn another one. :-("
else
test $(find_size $DEV) -ne $SRCSIZE &&
{ warn_msg "This newly created disc is not the same size as the source. You will need to burn another one. :-(";}
fi
done
;;
1) #<--- Probably an ordinary file, or corrupt image file
error_msg "${B}${SOURCE}${b} is not an ISO 9660 formatted file.\n" && exit 1
;;
2) #<--- File or device doesn't exist
error_msg "No such file or device\n --> ${B}${SOURCE}${b} <--\n" && exit 2
;;
13) #<--- Not enough permissions
error_msg "Not enough permissions to read ${B}${SOURCE}${b}. Either fix the permissions problem, or run this script as root.\n" && exit 13
;;
21) #<--- We're dealing with a directory
## Sanity check
test ! -d "${SOURCE}/VIDEO_TS" -o ! -d "${SOURCE}/AUDIO_TS" &&
{ error_msg "The directory structure is incorrect for a video DVD. There must be a ${B}VIDEO_TS${b} /AND/ ${B}AUDIO_TS${b} subdirectory present. Also, make sure you supplied the correct path on the command line.\n" && exit 1;}
## Extra growisofs options for burning from a directory tree
## and not an image:
OPTS="-Z $DEV \
-dvd-video \
-volid '${SOURCE##*/}' \
-sysid '' \
-appid '' \
-preparer '' \
'$SOURCE'"
## Looping for the number of discs to burn
for ((INT=1 ; INT<=NUM ; INT++))
do
echo "${B}---> Starting disc $INT of ${NUM}:${b}"
## Feature - Insert blank disc before burning
test_iso $DEV
test $? -ne 22 &&
{ eject $DEV 2>/dev/null
read -p "Place blank DVD in tray and press <Enter> to continue...";}
## Burn, baby, burn.....Or holler a warning if something goes wrong:
growisofs $OPTS ||
{ warn_msg "Problem burning ISO image to disk. :-(\n" && continue;}
## Sanity check
test_iso $DEV
test $? -ne 0 &&
{ warn_msg "This newly created disc is not a valid ISO 9660 formatted disc. You will need to burn another one. :-(";}
done
;;
30) #<--- Either no DVD in tray or a corrupt DVD
error_msg "Cannot read ${B}${SOURCE}${b}. Make sure there's a DVD in the tray.\n"
exit 30
;;
esac
## Cleanup
test "$IMAGE" && rm -f "$VIDTMP/$IMAGE"
echo "...Done"
}
## 'ripvideo' program converted to a function. Usage: rip_video "
[email protected]"
rip_video(){
## Check for the existence of a DVD drive:
find_dev "${READDVD[*]}" ||
{ error_msg "No DVD drive found.\n" && exit 1;}
## See if we have 'dvdbackup'. If not, say so and bail.
## NOTE: Dvdbackup requires 'libdvdread'. If you want to rip encrypted
## DVDs, such as retail ones, you will need to have 'libdvdcss'
## installed as well.
which dvdbackup >/dev/null 2>&1 ||
{ error_msg "Cannot find 'dvdbackup', which is needed for ripping DVDs.\n"
exit 1;}
whereis libdvdcss | grep -q 'libdvdcss:$' &&
{ warn_msg "Cannot find 'libdvdcss'. This is required only for backing up encrypted DVDs, such as retail movie discs."
read -t20 -p "Do you wish to continue? [Y|n]"
[[ "$REPLY" =~ "^[nN][oO]?$" ]] && echo "===> Aborting\n" && exit 0;}
unset REPLY
## Sanity check
test -d "$1" && DEST=${1%/} || ## If destination isn't a directory, bail
{ error_msg "${B}${1}${b} is not a valid directory. Aborting operation.\n" >&2
exit 1;}
test "$DEST" == "." && DEST=$(pwd)
## This test is to see what action is needed, based on the return code:
test_iso "$DEV"
case $? in
0) #<--- We're dealing with a valid iso (an image or block device)
## Instead of making multiple calls to 'isoinfo', make one call
## and save results.
ISOINFO="$(isoinfo -d -i $DEV 2>/dev/null)"
## Now we'll start weeding out the desired info we saved:
# For LABEL, we'll ask if the current one found is okay:
LABEL="$(sed -n 's/Volume id: (.+)/\1/p' <<<"$ISOINFO" | toUpper)"
LABEL="${LABEL//[^[:alnum:]]/_}"
read -t20 -p "
Enter a name of your choosing for the video, or simply hit <Enter> to use the default [$LABEL]: "
test "$REPLY" && LABEL="$(toUpper <<<"${REPLY//[^[:alnum:]]/_}")"
unset REPLY
# The rest of the info we'll be needing for later:
SYSID="$(sed -n 's/System id: (.+)/\1/p' <<<"$ISOINFO")"
VOLSETID="$(sed -n 's/Volume set id: (.+)/\1/p' <<<"$ISOINFO")"
PUBLISHER="$(sed -n 's/Publisher id: (.+)/\1/p' <<<"$ISOINFO")"
COPYRIGHT="$(sed -n 's/Copyright File id: (.+)/\1/p' <<<"$ISOINFO")"
BIBLIO="$(sed -n 's/Bibliographic File id: (.+)/\1/p' <<<"$ISOINFO")"
VOLSETSIZE="$(sed -n 's/Volume set size is: (.+)/\1/p' <<<"$ISOINFO")"
VOLSETSEQ="$(sed -n 's/Volume set sequence number is: (.+)/\1/p' <<<"$ISOINFO")"
if [ "$LABEL" ];then ## If we have a name, then it's a go
## Sanity check to make sure we don't overwrite an existing backup tree:
if [ ! -d "$DEST/$LABEL/VIDEO_TS" ];then
echo "\n===> Backing up directory structure..."
## Let 'er rip. If somethng bad happens in the process, bail
dvdbackup -M -i $DEV -o "$DEST" -n $LABEL ||
{ RETVAL= $? && echo "Aborting operation." && exit $RETVAL;}
else ## Found an existing 'VIDEO_TS' directory
error_msg "${B}$DEST/$LABEL/VIDEO_TS${b} already exists and will not be overwritten. Choose another destination, or remove ${B}$DEST/$LABEL/VIDEO_TS${b} to continue.\n" && exit 1
fi
## Make an 'AUDIO_TS' directory for backward compatibility
## on old DVD players. Even if there was one on the
## original disc, dvdbackup doesn't bother with it, probably
## because it's always empty, nowadays.
mkdir -p "$DEST/$LABEL/AUDIO_TS" || exit $?
else ## If no name, bail
error_msg "No name for ISO. Aborting operation.\n" && exit 1
fi
## For some retail DVDs, there might be extra stuff in it, so we
## might as well copy them, too:
MOUNT=$(mk_temp) && mkdir $MOUNT
mount $DEV $MOUNT -t udf -o ro
( cd $MOUNT
ls -A | egrep -iv 'video_ts|audio_ts' | while read
do
test ! -f $DEST/$LABEL/$REPLY && cp -R $REPLY $DEST/$LABEL
done )
sync && umount $MOUNT && rmdir $MOUNT
# Make sure filenames are uppercase:
find $DEST/$LABEL | sort -r | while read
do
NEW="$(toUpper <<<${REPLY##*/})"
test "${REPLY%/*}/$NEW" != "$REPLY" && mv $REPLY ${REPLY%/*}/$NEW
done
## Now we'll create an iso image of the backup for storage,
## using the info from the original disc:
echo "\n===> Creating DVD image..."
mkisofs -dvd-video \
-volid "$LABEL" \
-sysid "$SYSID" \
-appid "" \
-publisher "$PUBLISHER" \
-copyright "$COPYRIGHT" \
-biblio "$BIBLIO" \
-volset "$VOLSETID" \
-volset-size "$VOLSETSIZE" \
-volset-seqno "$VOLSETSEQ"\
-preparer "" \
-o "$DEST/$LABEL.dvd" \
"$DEST/$LABEL" ||
{ RETVAL=$? && echo "Aborting operation." && exit $RETVAL;}
## Let's make sure we have a good image (more sanity checks)
test_iso $DEST/$LABEL.dvd
if [ $? -ne 0 ];then ## If not good, bail
{ error_msg "The image, ${B}$LABEL.dvd${b}, is not a valid ISO 9660 image. You will need to start over." && exit 1;}
else ## If good, recheck for proper video structure
## This test may seem redundant and unnecessary, but I've
## acually burned a batch of DVDs with an incorect video
## tree in the image :-\
test $(egrep '(AUDIO|VIDEO)_TS' <<<"$(isoinfo -p -i $DEST/$LABEL.dvd)" |
wc -l) -ne 2 &&
{ error_msg "The ISO, ${B}$LABEL.dvd${b}, does not contain a valid video directory structure. You will need to make another one. Make sure there is both a ${B}VIDEO_TS${b} and an ${B}AUDIO_TS${b} subdirectory present.\n" && exit 1;}
fi
## Another redundant sanity check before cleaning up :-\
test "$DEST/$LABEL" != "$DEST/" && rm -rf "$DEST/$LABEL"
## Whew! Made it all the way....... :-D
echo "\n===> Done. Saved DVD iso as: ${B}$DEST/$LABEL.dvd${b}\n"
;;
1) #<--- Probably an ordinary file, or corrupt image file
error_msg "The disc in ${B}${DEV}${b} is not an ISO 9660 formatted disc.\n"
exit 1
;;
13) #<--- Not enough permissions
error_msg "Not enough permissions to read ${B}${DEV}${b}. Either fix the permissions problem, or run this script as root.\n" && exit 13
;;
22) #<--- An unformatted (blank) disc
error_msg "The disc in ${B}${DEV}${b} is a blank disc.\n"
exit 22
;;
30) #<--- Either no DVD in tray or a corrupt DVD
error_msg "Cannot read ${B}${DEV}${b}. Make sure there's a DVD in the tray.\n"
exit 30
;;
esac
}
##################
##### MAIN #####
##################
test -z "$1" &&
{ error_msg "No arguments given on command line."
print_usage >&2 && exit 1;}
## checking for required 'mkisofs' program
which mkisofs >/dev/null 2>&1 ||
{ error_msg "Cannot find 'mkisofs'.\n" && exit 1;}
case $PROG in # How were we called?
burnvideo) burn_video "
[email protected]"
;;
ripvideo) rip_video "
[email protected]"
;;
*) ## Making sure the script hasn't been renamed accidently... ^_^
echo "\n===> ERROR: This program has been renamed. The proper name for this program is 'burnvideo', along with a symlink (in the same directory) pointing to it called 'ripvideo'.\n" | fold >&2
exit 1
;;
esac
eject $DEV 2>/dev/null
exit 0