]> git.wh0rd.org - home.git/blobdiff - .bin/backup-dvd
ddnuke: add fdisk+prompt to startup, and rough ETA
[home.git] / .bin / backup-dvd
index 5ea4286f760cb21ca111f8c6bcbabbb4df733bf7..d5b9d79ddb0fc2b7a43d6ac44a565d1e5df9f252 100755 (executable)
@@ -1,13 +1,22 @@
 #!/bin/bash
 
+err() { printf 'error: %b\n' "$*" 1>&2; exit 1; }
 usage() {
        cat <<-EOF
-       backup-dvd [options]
+       backup-dvd [options] [commands]
 
        Options:
         -i <input dev>      Defaults to ${dev}
         -o <output dir>     Defaults to ${out}
         -n <volume name>    Defaults to volume on disk
+
+       Commands:
+        --errors   Image the raw disc first w/ddrescue
+        --backup   Back up the disc
+        --modify   Tweak runtime settings (needs -n)
+        --mkiso    Create an iso (needs -n)
+
+       If no commands are specified, then all are run.
        EOF
        exit ${1:-0}
 }
@@ -16,19 +25,39 @@ dev=/dev/cdrom
 out=${PWD}
 vol=
 
-eval set -- `getopt -- hi:n:o: "$@"`
+unset doit_{backup,modify,mkiso}
+doit_errors=false
+
+eval set -- `getopt -l errors,backup,modify,mkiso -- hi:n:o: "$@"`
 while [[ -n $1 ]] ; do
        case $1 in
                -h) usage;;
                -i) dev=$2; shift 2;;
                -o) out=$2; shift 2;;
                -n) vol=$2; shift 2;;
+
+               --errors) doit_errors=true; shift;;
+               --backup) doit_backup=true; shift;;
+               --modify) doit_modify=true; shift;;
+               --mkiso)  doit_mkiso=true;  shift;;
+
                --) shift; break;;
                -*) usage 1;;
                *)  usage 2; break;;
        esac
 done
 
+doit_all() {
+       : ${doit_backup:=$1}
+       : ${doit_modify:=$1}
+       : ${doit_mkiso:=$1}
+}
+if [[ -z ${doit_backup}${doit_modify}${doit_mkiso} ]] ; then
+       doit_all true
+else
+       doit_all false
+fi
+
 # iso-info             mkisofs
 Application=   # -A
 Preparer=              # -p
@@ -37,24 +66,31 @@ System=                     # -sysid
 Volume=${vol}  # -V
 Volume_Set=            # -volset
 
-if [[ -z ${Volume} ]] ; then
-       info=$(iso-info ${dev}) || exit 1
+if ${doit_backup} ; then
+       if [[ -z ${Volume} ]] ; then
+               info=$(iso-info ${dev}) || exit 1
 
-       eval $(echo "${info}" | awk -F: '
-               (NF > 1 && $1 !~ /image/) {
-                       sub(/ *$/, "", $1);
-                       sub(/ /, "_", $1);
-                       sub(/^ */, "", $2);
-                       print $1 "=\"" $2 "\"";
-       }')
-fi
+               eval $(echo "${info}" | awk -F: '
+                       (NF > 1 && $1 !~ /image/) {
+                               sub(/ *$/, "", $1);
+                               sub(/ /, "_", $1);
+                               sub(/^ */, "", $2);
+                               print $1 "=\"" $2 "\"";
+               }')
+       fi
 
-if [[ -z ${Volume} ]] ; then
-       echo "Unable to parse Volume out of ISO"
-       iso-info ${dev}
-fi
+       if [[ -z ${Volume} ]] ; then
+               echo "Unable to parse Volume out of ISO"
+               iso-info ${dev}
+       fi
 
-echo "Backing up: ${Volume}"
+       for v in Application Preparer Publisher System Volume Volume_Set ; do
+               echo "${v}='${!v}'"
+       done > "${out}/.${Volume}.vars.sh"
+else
+       [[ -z ${Volume} ]] && err "Need to specify -n with --modify/--mkiso"
+       . "${out}/.${Volume}.vars.sh" || exit 1
+fi
 
 e() {
        for a ; do
@@ -64,15 +100,277 @@ e() {
        echo
        "$@"
 }
-e dvdbackup -M -i ${dev} -o "${out}" -n "${Volume}"
-e mkisofs -quiet -dvd-video \
-       -A "${Application}" \
-       -p "${Preparer}" \
-       -publisher "${Publisher}" \
-       -sysid "${System}" \
-       -V "${Volume}" \
-       -volset "${Volume_Set}" \
-       -o "${out}/${Volume}.iso" "${Volume}"
-du -h "${out}/${Volume}.iso"
-
-exec eject
+
+#
+# Mirror the disc w/ddrescue which tolerates errors.
+#
+raw_read_dvd() {
+       echo "Imaging disc"
+       local raw=".${Volume}.raw"
+       local log="${raw}.log"
+       local opts=(
+               -v -b 2048 ${dev} "${raw}" "${log}"
+       )
+       ddrescue -p -n "${opts[@]}"
+       ddrescue -d -r 3 "${opts[@]}"
+       ddrescue -d -r 3 -R "${opts[@]}"
+       dev=${raw}
+}
+
+#
+# Backup the disc.
+#
+backup_dvd() {
+       echo "Backing up: ${Volume}"
+       local opts=(
+               -i ${dev}
+               -o "${out}"
+               -n "${Volume}"
+       )
+       sync
+       if ! e dvdbackup -M "${opts[@]}" ; then
+               e dvdbackup -F "${opts[@]}" || exit 1
+       fi
+       sync
+}
+
+#
+# Modify some of the runtime settings.
+#
+bytes_get() {
+       local pfx='0x' sep
+       while [[ $# -gt 3 ]]; do
+               case $1 in
+               "-p") pfx=$2;;
+               "-s") sep=$2;;
+               *) break;;
+               esac
+               shift 2
+       done
+       printf '%b' "${pfx}"
+
+       local file=$1 off=$2 num=${3:-1}
+       hexdump -v -n ${num} -s $((${off})) -e "1/1 \"%02x${sep}\"" "${file}"
+}
+bytes_set() {
+       local file=$1 off=$2
+       shift 2
+       (for b; do printf "\x${b#0x}"; done) | \
+               dd of="${file}" \
+                       bs=1 seek=$((${off})) \
+                       conv=notrunc status=none
+}
+dvdsec() {
+       # DVDs have sectors of 2048 bytes (2^11).
+       local file=$1 off=$2 num=${3:-4}
+       echo $(( $(bytes_get "${file}" "${off}" "${num}") << 11 ))
+}
+
+_check_ifo() {
+       # Back up the .IFO files before we edit them.
+       local ifo=$1 magic=$2
+       [[ -e ${ifo} ]] || return 1
+
+       if [[ $(bytes_get "${ifo}" 0 12) != "0x${magic}" ]] ; then
+               echo "not a VMG IFO file: ${ifo}"
+               return 1
+       fi
+       if [[ ! -e ${ifo}.bak ]] ; then
+               cp -a "${ifo}" "${ifo}.bak"
+       fi
+
+       return 0
+}
+
+check_changes() {
+       [[ -e ifodump ]] || return 0
+
+       local file=$1
+       ./ifodump -f "${file}.bak" > "${file}.bak.dmp"
+       ./ifodump -f "${file}" > "${file}.dmp"
+       vapier-diff "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff"
+       local out=$(
+               grep '^[+-]' "${file}.diff" | \
+                       grep -v \
+                               -e '^-VMG Category:.*' \
+                               -e '^+VMG Category: 00000000 (Region Code=ff)' \
+                               -e '^[-+].*Title playback type:' \
+                               -e '^-.*Title or time play:1' \
+                               -e '^+.*Title or time play:0' \
+                               -e '^-Prohibited user operations:' \
+                               -e '^+Prohibited user operations: Angle Change, $' \
+                               -e '^+Prohibited user operations: None$' \
+                       | egrep -v -e '^[-+]([0-9a-f]{2} )+$'
+       )
+       rm "${file}".{{,bak.}dmp,diff}
+       if [[ -z ${out} ]] ; then
+               return 0
+       else
+               echo "error in ifo modification:"
+               echo "${out}"
+               return 1
+       fi
+}
+
+process_pgci() {
+       # http://dvdnav.mplayerhq.hu/dvdinfo/pgc.html
+       local file=$1 pgci_off=$2 indent=$3
+       local num_pgcs p pgc_off off
+
+       num_pgcs=$(bytes_get "${file}" ${pgci_off} 2)
+       for (( p = 0; p < num_pgcs; ++p )) ; do
+               pgc_off=$((
+                       pgci_off +
+                       $(bytes_get "${file}" $(( pgci_off + 8 + 8 * p + 4 )) 4)
+               ))
+               off=$(( pgc_off + 8 ))
+               pgc_puo=$(bytes_get "${file}" ${off} 4)
+               printf '%bpgc #%2i @ %#06x: %s: ' "${indent}" ${p} ${vmgm_pgc_off} ${pgc_puo}
+               new_pgc_puo=$(( pgc_puo & (1 << 22) )) # Keep angle field.
+               if [[ ${pgc_puo} -ne ${new_pgc_puo} ]] ; then
+                       bytes_set "${file}" ${off} 0 $(printf '%x' $(( new_pgc_puo >> 16 ))) 0 0
+                       echo "cleared"
+               else
+                       echo "nothing to do"
+               fi
+       done
+}
+
+process_pgci_ut() {
+       # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vts.html#pgciut
+       local file=$1 pgci_ut_off=$2
+       local num_langs l off
+
+       num_langs=$(bytes_get "${file}" ${pgci_ut_off} 2)
+       for (( l = 0; l < num_langs; ++l )) ; do
+               off=$((
+                       pgci_ut_off +
+                       $(bytes_get "${file}" $(( pgci_ut_off + 8 + 8 * l + 4 )) 4)
+               ))
+               printf '\tclearing lang #%i @ %#06x\n' ${l} ${off}
+               process_pgci "${file}" ${off} '\t\t'
+       done
+}
+
+process_vmgi() {
+       # Back up the .IFO files before we edit them.
+       local vmgi=$1
+       _check_ifo "${vmgi}" "445644564944454f2d564d47" || return 1
+
+       echo "${vmgi}"
+
+       # Clear the region code.
+       # http://dvdnav.mplayerhq.hu/dvdinfo/ifo.html
+       # 0x23 - region code restrict byte - set to 0x00 for region free.
+       echo "clearing region code"
+       bytes_set "${vmgi}" 0x23 0x00
+
+       # Clear all Prohibited User Operations (PUOs) in TT_SRPT (Uop1 & Uop0).
+       # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vmg.html
+       echo "clearing PUOs in TT_SRPT"
+       tt_srpt_sec=$(dvdsec "${vmgi}" 0xC4)
+
+       num_titles=$(bytes_get "${vmgi}" ${tt_srpt_sec} 2)
+       for (( t = 0; t < num_titles; ++t )) ; do
+               off=$(( tt_srpt_sec + 8 + (t * 12) ))
+               printf "\ttitle type #%2i @ %#06x: " "${t}" "${off}"
+               title_type=$(bytes_get "${vmgi}" ${off} 1)
+               uop0=$(( title_type & 1 ))
+               uop1=$(( title_type & 2 ))
+               printf "%s (uop1:%i uop0:%i): " ${title_type} ${uop1} ${uop0}
+               new_title_type=$(( title_type & ~3 ))
+               if [[ ${title_type} -ne ${new_title_type} ]] ; then
+                       bytes_set "${vmgi}" ${off} $(printf '%x' ${new_title_type})
+                       echo "cleared"
+               else
+                       echo "nothing to do"
+               fi
+       done
+
+       # Clear all the PUOs in the PGCs.  But Preserve the angle bit.
+       #   Note: Removal of the angle PUO can cause certain standalone players to
+       #   display the "angle" icon during playback. Removal of the angle PUO
+       #   whilst permitted is therefore not recommended.
+       # http://dvdnav.mplayerhq.hu/dvdinfo/uops.html
+       #fp_pgc_addr=$(bytes_get "${vmgi}" 0x84 4)
+       #num_pgcs=$(bytes_get "${vmgi}" $(( fp_pgc_sec + 1 )) 1)
+       echo "clearing PUOs in PGCs"
+       vmgm_pgci_ut_sec=$(dvdsec "${vmgi}" 0xC8)
+       process_pgci_ut "${vmgi}" ${vmgm_pgci_ut_sec}
+}
+
+process_vtsi() {
+       # Back up the .IFO files before we edit them.
+       local vtsi=$1
+       _check_ifo "${vtsi}" "445644564944454f2d565453" || return 1
+
+       echo "${vtsi}"
+
+       echo "clearing PUOs in PGCs"
+
+       vts_pgci_sec=$(dvdsec "${vtsi}" 0xCC)
+       process_pgci "${vtsi}" ${vts_pgci_sec} '\t'
+
+       vtsm_pgci_ut_sec=$(dvdsec "${vtsi}" 0xD0)
+       process_pgci_ut "${vtsi}" ${vtsm_pgci_ut_sec}
+}
+
+modify_dvd() {
+       local out_vts_dir="${out}/${Volume}/VIDEO_TS"
+       local vmgi vtsi
+
+       if ${doit_errors} ; then
+               echo "skipping modifications due to possible errors in files"
+               return
+       fi
+
+       for vmgi in "${out_vts_dir}"/VIDEO_TS.{IFO,BUP} ; do
+               process_vmgi "${vmgi}"
+               check_changes "${vmgi}" || exit 1
+       done
+
+       for vtsi in "${out_vts_dir}"/VTS_??_?.{IFO,BUP} ; do
+               process_vtsi "${vtsi}"
+               check_changes "${vtsi}" || exit 1
+       done
+}
+
+#
+# Finally, create a new iso.
+#
+mkiso_dvd() {
+       set -- mkisofs -quiet -dvd-video -no-bak \
+               -A "${Application}" \
+               -p "${Preparer}" \
+               -publisher "${Publisher}" \
+               -sysid "${System}" \
+               -V "${Volume}" \
+               -volset "${Volume_Set}" \
+               -o "${Volume}.iso" "${Volume}"
+       sh="${out}/.${Volume}.sh"
+       (
+       echo "#!/bin/sh"
+       echo "cd '${out}' || exit 1"
+       echo ". './.${Volume}.vars.sh'"
+       printf 'set -- '
+       printf '%q ' "$@"
+       echo
+       echo 'echo "$@"; exec "$@"'
+       ) > "${sh}"
+       chmod a+x "${sh}"
+       "${sh}" || exit
+       sync
+       md5sum "${out}/${Volume}.iso" > "${out}/${Volume}.md5"
+
+       du -h "${out}/${Volume}.iso"
+       sudo chattr +i "${out}/${Volume}.iso"
+       sync
+}
+
+${doit_errors} && raw_read_dvd
+${doit_backup} && backup_dvd
+${doit_modify} && modify_dvd
+${doit_mkiso} && mkiso_dvd
+
+${doit_backup} && eject
+exit 0