3 err
() { printf 'error: %b\n' "$*" 1>&2; exit 1; }
6 backup-dvd [options] [commands]
9 -i <input dev> Defaults to ${dev}
10 -o <output dir> Defaults to ${out}
11 -n <volume name> Defaults to volume on disk
14 --errors Image the raw disc first w/ddrescue
15 --backup Back up the disc
16 --modify Tweak runtime settings (needs -n)
17 --mkiso Create an iso (needs -n)
19 If no commands are specified, then all are run.
28 unset doit_
{backup
,modify
,mkiso
}
31 eval set -- `getopt -l errors,backup,modify,mkiso -- hi:n:o: "$@"`
32 while [[ -n $1 ]] ; do
39 --errors) doit_errors
=true
; shift;;
40 --backup) doit_backup
=true
; shift;;
41 --modify) doit_modify
=true
; shift;;
42 --mkiso) doit_mkiso
=true
; shift;;
55 if [[ -z ${doit_backup}${doit_modify}${doit_mkiso} ]] ; then
64 Publisher
= # -publisher
69 if ${doit_backup} ; then
70 if [[ -z ${Volume} ]] ; then
71 info
=$
(iso-info
${dev}) ||
exit 1
73 eval $
(echo "${info}" |
awk -F: '
74 (NF > 1 && $1 !~ /image/) {
78 print $1 "=\"" $2 "\"";
82 if [[ -z ${Volume} ]] ; then
83 echo "Unable to parse Volume out of ISO"
87 for v
in Application Preparer Publisher System Volume Volume_Set
; do
89 done > "${out}/.${Volume}.vars.sh"
91 [[ -z ${Volume} ]] && err
"Need to specify -n with --modify/--mkiso"
92 .
"${out}/.${Volume}.vars.sh" ||
exit 1
97 [[ ${a} == *" "* ||
${#a} == 0 ]] && fmt='"%s"' ||
fmt='%s'
98 printf "${fmt} " "${a}"
105 # Mirror the disc w/ddrescue which tolerates errors.
109 local raw
=".${Volume}.raw"
110 local log
="${raw}.log"
112 -v -b 2048 ${dev} "${raw}" "${log}"
114 ddrescue -p -n "${opts[@]}"
115 ddrescue -d -r 3 "${opts[@]}"
116 ddrescue -d -r 3 -R "${opts[@]}"
124 echo "Backing up
: ${Volume}"
130 if ! e dvdbackup -M "${opts[@]}" ; then
131 e dvdbackup -F "${opts[@]}" || exit 1
137 # Modify some of the runtime settings.
141 while [[ $# -gt 3 ]]; do
151 local file=$1 off=$2 num=${3:-1}
152 hexdump -v -n ${num} -s $((${off})) -e "1/1 \"%02x${sep}\"" "${file}"
157 (for b
; do printf "\x${b#0x}"; done) | \
159 bs
=1 seek
=$
((${off})) \
160 conv
=notrunc status
=none
163 # DVDs have sectors of 2048 bytes (2^11).
164 local file=$1 off
=$2 num
=${3:-4}
165 echo $
(( $
(bytes_get
"${file}" "${off}" "${num}") << 11 ))
169 # Back up the .IFO files before we edit them.
170 local ifo=$1 magic=$2
171 [[ -e ${ifo} ]] || return 1
173 if [[ $(bytes_get "${ifo}" 0 12) != "0x${magic}" ]] ; then
174 echo "not a VMG IFO file: ${ifo}"
177 if [[ ! -e ${ifo}.bak ]] ; then
178 cp -a "${ifo}" "${ifo}.bak"
185 [[ -e ifodump ]] || return 0
188 ./ifodump -f "${file}.bak" > "${file}.bak.dmp"
189 ./ifodump -f "${file}" > "${file}.dmp"
190 diff -u "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff"
192 grep '^[+-]' "${file}.diff" | \
194 -e '^-VMG Category:.*' \
195 -e '^+VMG Category: 00000000 (Region Code=ff)' \
196 -e '^[-+].*Title playback type:' \
197 -e '^-.*Title or time play:1' \
198 -e '^+.*Title or time play:0' \
199 -e '^-Prohibited user operations:' \
200 -e '^+Prohibited user operations: Angle Change, $' \
201 -e '^+Prohibited user operations: None$' \
202 | egrep -v -e '^[-+]([0-9a-f]{2} )+$'
204 rm "${file}".{{,bak.}dmp,diff}
205 if [[ -z ${out} ]] ; then
208 echo "error in ifo modification:"
215 # http://dvdnav.mplayerhq.hu/dvdinfo/pgc.html
216 local file=$1 pgci_off=$2 indent=$3
217 local num_pgcs p pgc_off off
219 num_pgcs=$(bytes_get "${file}" ${pgci_off} 2)
220 for (( p = 0; p < num_pgcs; ++p )) ; do
223 $(bytes_get "${file}" $(( pgci_off + 8 + 8 * p + 4 )) 4)
225 off=$(( pgc_off + 8 ))
226 pgc_puo=$(bytes_get "${file}" ${off} 4)
227 printf '%bpgc #%2i @ %#06x: %s: ' "${indent}" ${p} ${vmgm_pgc_off} ${pgc_puo}
228 new_pgc_puo=$(( pgc_puo & (1 << 22) )) # Keep angle field.
229 if [[ ${pgc_puo} -ne ${new_pgc_puo} ]] ; then
230 bytes_set "${file}" ${off} 0 $(printf '%x' $(( new_pgc_puo >> 16 ))) 0 0
239 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vts.html#pgciut
240 local file=$1 pgci_ut_off=$2
241 local num_langs l off
243 num_langs=$(bytes_get "${file}" ${pgci_ut_off} 2)
244 for (( l = 0; l < num_langs; ++l )) ; do
247 $(bytes_get "${file}" $(( pgci_ut_off + 8 + 8 * l + 4 )) 4)
249 printf '\tclearing lang #%i @ %#06x\n' ${l} ${off}
250 process_pgci "${file}" ${off} '\t\t'
255 # Back up the .IFO files before we edit them.
257 _check_ifo "${vmgi}" "445644564944454f2d564d47" || return 1
261 # Clear the region code.
262 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo.html
263 # 0x23 - region code restrict byte - set to 0x00 for region free.
264 echo "clearing region code"
265 bytes_set "${vmgi}" 0x23 0x00
267 # Clear all Prohibited User Operations (PUOs) in TT_SRPT (Uop1 & Uop0).
268 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vmg.html
269 echo "clearing PUOs in TT_SRPT"
270 tt_srpt_sec=$(dvdsec "${vmgi}" 0xC4)
272 num_titles=$(bytes_get "${vmgi}" ${tt_srpt_sec} 2)
273 for (( t = 0; t < num_titles; ++t )) ; do
274 off=$(( tt_srpt_sec + 8 + (t * 12) ))
275 printf "\ttitle type #%2i @ %#06x: " "${t}" "${off}"
276 title_type=$(bytes_get "${vmgi}" ${off} 1)
277 uop0=$(( title_type & 1 ))
278 uop1=$(( title_type & 2 ))
279 printf "%s (uop1:%i uop0:%i): " ${title_type} ${uop1} ${uop0}
280 new_title_type=$(( title_type & ~3 ))
281 if [[ ${title_type} -ne ${new_title_type} ]] ; then
282 bytes_set "${vmgi}" ${off} $(printf '%x' ${new_title_type})
289 # Clear all the PUOs in the PGCs. But Preserve the angle bit.
290 # Note: Removal of the angle PUO can cause certain standalone players to
291 # display the "angle" icon during playback. Removal of the angle PUO
292 # whilst permitted is therefore not recommended.
293 # http://dvdnav.mplayerhq.hu/dvdinfo/uops.html
294 #fp_pgc_addr=$(bytes_get "${vmgi}" 0x84 4)
295 #num_pgcs=$(bytes_get "${vmgi}" $(( fp_pgc_sec + 1 )) 1)
296 echo "clearing PUOs in PGCs"
297 vmgm_pgci_ut_sec=$(dvdsec "${vmgi}" 0xC8)
298 process_pgci_ut "${vmgi}" ${vmgm_pgci_ut_sec}
302 # Back up the .IFO files before we edit them.
304 _check_ifo "${vtsi}" "445644564944454f2d565453" || return 1
308 echo "clearing PUOs in PGCs"
310 vts_pgci_sec=$(dvdsec "${vtsi}" 0xCC)
311 process_pgci "${vtsi}" ${vts_pgci_sec} '\t'
313 vtsm_pgci_ut_sec=$(dvdsec "${vtsi}" 0xD0)
314 process_pgci_ut "${vtsi}" ${vtsm_pgci_ut_sec}
318 local out_vts_dir="${out}/${Volume}/VIDEO_TS"
321 if ${doit_errors} ; then
322 echo "skipping modifications due to possible errors in files"
326 for vmgi in "${out_vts_dir}"/VIDEO_TS.{IFO,BUP} ; do
327 process_vmgi "${vmgi}"
328 check_changes "${vmgi}" || exit 1
331 for vtsi in "${out_vts_dir}"/VTS_??_?.{IFO,BUP} ; do
332 process_vtsi "${vtsi}"
333 check_changes "${vtsi}" || exit 1
338 # Finally, create a new iso.
341 set -- mkisofs -quiet -dvd-video -no-bak \
342 -A "${Application}" \
344 -publisher "${Publisher}" \
347 -volset "${Volume_Set}" \
348 -o "${Volume}.iso" "${Volume}"
349 sh="${out}/.${Volume}.sh"
352 echo "cd '${out}' || exit 1"
353 echo ". './.${Volume}.vars.sh'"
357 echo 'echo "$@"; exec "$@"'
362 md5sum "${out}/${Volume}.iso" > "${out}/${Volume}.md5"
364 du -h "${out}/${Volume}.iso"
365 sudo chattr +i "${out}/${Volume}.iso"
369 ${doit_errors} && raw_read_dvd
370 ${doit_backup} && backup_dvd
371 ${doit_modify} && modify_dvd
372 ${doit_mkiso} && mkiso_dvd
374 ${doit_backup} && eject