#!/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:
+ --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}
}
out=${PWD}
vol=
-eval set -- `getopt -- hi:n:o: "$@"`
+unset doit_{backup,modify,mkiso}
+
+eval set -- `getopt -l 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;;
+
+ --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
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
echo
"$@"
}
+
+#
+# Backup the disc.
+#
opts=(
-i ${dev}
-o "${out}"
-n "${Volume}"
)
-if ! e dvdbackup -M "${opts[@]}" ; then
- e dvdbackup -F "${opts[@]}" || exit 1
-fi
-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
+backup_dvd() {
+ echo "Backing up: ${Volume}"
+ if ! e dvdbackup -M "${opts[@]}" ; then
+ e dvdbackup -F "${opts[@]}" || exit 1
+ fi
+}
+
+#
+# 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"
+ diff -u "${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
+
+ 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
+
+ du -h "${out}/${Volume}.iso"
+}
+
+${doit_backup} && backup_dvd
+${doit_modify} && modify_dvd
+${doit_mkiso} && mkiso_dvd
+
+${doit_backup} && eject
+exit 0