X-Git-Url: https://git.wh0rd.org/?a=blobdiff_plain;f=.bin%2Fbackup-dvd;h=d5b9d79ddb0fc2b7a43d6ac44a565d1e5df9f252;hb=942a43e2e16198361bee17df0077435a5064a2ed;hp=5ea4286f760cb21ca111f8c6bcbabbb4df733bf7;hpb=2b2f7f171260b07454bbf077e2b9a07e2eb63ee0;p=home.git diff --git a/.bin/backup-dvd b/.bin/backup-dvd index 5ea4286..d5b9d79 100755 --- a/.bin/backup-dvd +++ b/.bin/backup-dvd @@ -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 Defaults to ${dev} -o Defaults to ${out} -n 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