]> git.wh0rd.org - home.git/blame - .bin/backup-dvd
cros-board: update
[home.git] / .bin / backup-dvd
CommitLineData
4108a92c
MF
1#!/bin/bash
2
d6d9f2ac 3err() { printf 'error: %b\n' "$*" 1>&2; exit 1; }
4108a92c
MF
4usage() {
5 cat <<-EOF
d6d9f2ac 6 backup-dvd [options] [commands]
4108a92c
MF
7
8 Options:
9 -i <input dev> Defaults to ${dev}
10 -o <output dir> Defaults to ${out}
2b2f7f17 11 -n <volume name> Defaults to volume on disk
d6d9f2ac
MF
12
13 Commands:
a356b2d3 14 --errors Image the raw disc first w/ddrescue
d6d9f2ac
MF
15 --backup Back up the disc
16 --modify Tweak runtime settings (needs -n)
17 --mkiso Create an iso (needs -n)
18
19 If no commands are specified, then all are run.
4108a92c
MF
20 EOF
21 exit ${1:-0}
22}
23
24dev=/dev/cdrom
25out=${PWD}
2b2f7f17 26vol=
4108a92c 27
d6d9f2ac 28unset doit_{backup,modify,mkiso}
a356b2d3 29doit_errors=false
d6d9f2ac 30
a356b2d3 31eval set -- `getopt -l errors,backup,modify,mkiso -- hi:n:o: "$@"`
4108a92c
MF
32while [[ -n $1 ]] ; do
33 case $1 in
34 -h) usage;;
35 -i) dev=$2; shift 2;;
36 -o) out=$2; shift 2;;
2b2f7f17 37 -n) vol=$2; shift 2;;
d6d9f2ac 38
a356b2d3 39 --errors) doit_errors=true; shift;;
d6d9f2ac
MF
40 --backup) doit_backup=true; shift;;
41 --modify) doit_modify=true; shift;;
42 --mkiso) doit_mkiso=true; shift;;
43
4108a92c
MF
44 --) shift; break;;
45 -*) usage 1;;
46 *) usage 2; break;;
47 esac
48done
49
d6d9f2ac
MF
50doit_all() {
51 : ${doit_backup:=$1}
52 : ${doit_modify:=$1}
53 : ${doit_mkiso:=$1}
54}
55if [[ -z ${doit_backup}${doit_modify}${doit_mkiso} ]] ; then
56 doit_all true
57else
58 doit_all false
59fi
60
4108a92c
MF
61# iso-info mkisofs
62Application= # -A
63Preparer= # -p
64Publisher= # -publisher
65System= # -sysid
2b2f7f17 66Volume=${vol} # -V
4108a92c
MF
67Volume_Set= # -volset
68
d6d9f2ac
MF
69if ${doit_backup} ; then
70 if [[ -z ${Volume} ]] ; then
71 info=$(iso-info ${dev}) || exit 1
4108a92c 72
d6d9f2ac
MF
73 eval $(echo "${info}" | awk -F: '
74 (NF > 1 && $1 !~ /image/) {
75 sub(/ *$/, "", $1);
76 sub(/ /, "_", $1);
77 sub(/^ */, "", $2);
78 print $1 "=\"" $2 "\"";
79 }')
80 fi
4108a92c 81
d6d9f2ac
MF
82 if [[ -z ${Volume} ]] ; then
83 echo "Unable to parse Volume out of ISO"
84 iso-info ${dev}
85 fi
4108a92c 86
d6d9f2ac
MF
87 for v in Application Preparer Publisher System Volume Volume_Set ; do
88 echo "${v}='${!v}'"
89 done > "${out}/.${Volume}.vars.sh"
90else
91 [[ -z ${Volume} ]] && err "Need to specify -n with --modify/--mkiso"
92 . "${out}/.${Volume}.vars.sh" || exit 1
93fi
4108a92c
MF
94
95e() {
96 for a ; do
97 [[ ${a} == *" "* || ${#a} == 0 ]] && fmt='"%s"' || fmt='%s'
98 printf "${fmt} " "${a}"
99 done
100 echo
101 "$@"
102}
d6d9f2ac 103
a356b2d3
MF
104#
105# Mirror the disc w/ddrescue which tolerates errors.
106#
107raw_read_dvd() {
108 echo "Imaging disc"
109 local raw=".${Volume}.raw"
110 local log="${raw}.log"
111 local opts=(
112 -v -b 2048 ${dev} "${raw}" "${log}"
113 )
114 ddrescue -p -n "${opts[@]}"
115 ddrescue -d -r 3 "${opts[@]}"
116 ddrescue -d -r 3 -R "${opts[@]}"
117 dev=${raw}
118}
119
d6d9f2ac
MF
120#
121# Backup the disc.
122#
d6d9f2ac
MF
123backup_dvd() {
124 echo "Backing up: ${Volume}"
a356b2d3
MF
125 local opts=(
126 -i ${dev}
127 -o "${out}"
128 -n "${Volume}"
129 )
16cc6646 130 sync
d6d9f2ac
MF
131 if ! e dvdbackup -M "${opts[@]}" ; then
132 e dvdbackup -F "${opts[@]}" || exit 1
133 fi
4ec333bc 134 sync
d6d9f2ac
MF
135}
136
137#
138# Modify some of the runtime settings.
139#
140bytes_get() {
141 local pfx='0x' sep
142 while [[ $# -gt 3 ]]; do
143 case $1 in
144 "-p") pfx=$2;;
145 "-s") sep=$2;;
146 *) break;;
147 esac
148 shift 2
149 done
150 printf '%b' "${pfx}"
151
152 local file=$1 off=$2 num=${3:-1}
153 hexdump -v -n ${num} -s $((${off})) -e "1/1 \"%02x${sep}\"" "${file}"
154}
155bytes_set() {
156 local file=$1 off=$2
157 shift 2
158 (for b; do printf "\x${b#0x}"; done) | \
159 dd of="${file}" \
160 bs=1 seek=$((${off})) \
161 conv=notrunc status=none
162}
163dvdsec() {
164 # DVDs have sectors of 2048 bytes (2^11).
165 local file=$1 off=$2 num=${3:-4}
166 echo $(( $(bytes_get "${file}" "${off}" "${num}") << 11 ))
167}
168
169_check_ifo() {
170 # Back up the .IFO files before we edit them.
171 local ifo=$1 magic=$2
172 [[ -e ${ifo} ]] || return 1
173
174 if [[ $(bytes_get "${ifo}" 0 12) != "0x${magic}" ]] ; then
175 echo "not a VMG IFO file: ${ifo}"
176 return 1
177 fi
178 if [[ ! -e ${ifo}.bak ]] ; then
179 cp -a "${ifo}" "${ifo}.bak"
180 fi
181
182 return 0
183}
184
185check_changes() {
186 [[ -e ifodump ]] || return 0
187
188 local file=$1
189 ./ifodump -f "${file}.bak" > "${file}.bak.dmp"
190 ./ifodump -f "${file}" > "${file}.dmp"
37ae4bcb 191 vapier-diff "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff"
d6d9f2ac
MF
192 local out=$(
193 grep '^[+-]' "${file}.diff" | \
194 grep -v \
195 -e '^-VMG Category:.*' \
196 -e '^+VMG Category: 00000000 (Region Code=ff)' \
197 -e '^[-+].*Title playback type:' \
198 -e '^-.*Title or time play:1' \
199 -e '^+.*Title or time play:0' \
200 -e '^-Prohibited user operations:' \
201 -e '^+Prohibited user operations: Angle Change, $' \
202 -e '^+Prohibited user operations: None$' \
203 | egrep -v -e '^[-+]([0-9a-f]{2} )+$'
204 )
205 rm "${file}".{{,bak.}dmp,diff}
206 if [[ -z ${out} ]] ; then
207 return 0
208 else
209 echo "error in ifo modification:"
210 echo "${out}"
211 return 1
212 fi
213}
214
215process_pgci() {
216 # http://dvdnav.mplayerhq.hu/dvdinfo/pgc.html
217 local file=$1 pgci_off=$2 indent=$3
218 local num_pgcs p pgc_off off
219
220 num_pgcs=$(bytes_get "${file}" ${pgci_off} 2)
221 for (( p = 0; p < num_pgcs; ++p )) ; do
222 pgc_off=$((
223 pgci_off +
224 $(bytes_get "${file}" $(( pgci_off + 8 + 8 * p + 4 )) 4)
225 ))
226 off=$(( pgc_off + 8 ))
227 pgc_puo=$(bytes_get "${file}" ${off} 4)
228 printf '%bpgc #%2i @ %#06x: %s: ' "${indent}" ${p} ${vmgm_pgc_off} ${pgc_puo}
229 new_pgc_puo=$(( pgc_puo & (1 << 22) )) # Keep angle field.
230 if [[ ${pgc_puo} -ne ${new_pgc_puo} ]] ; then
231 bytes_set "${file}" ${off} 0 $(printf '%x' $(( new_pgc_puo >> 16 ))) 0 0
232 echo "cleared"
233 else
234 echo "nothing to do"
235 fi
236 done
237}
238
239process_pgci_ut() {
240 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vts.html#pgciut
241 local file=$1 pgci_ut_off=$2
242 local num_langs l off
243
244 num_langs=$(bytes_get "${file}" ${pgci_ut_off} 2)
245 for (( l = 0; l < num_langs; ++l )) ; do
246 off=$((
247 pgci_ut_off +
248 $(bytes_get "${file}" $(( pgci_ut_off + 8 + 8 * l + 4 )) 4)
249 ))
250 printf '\tclearing lang #%i @ %#06x\n' ${l} ${off}
251 process_pgci "${file}" ${off} '\t\t'
252 done
253}
254
255process_vmgi() {
256 # Back up the .IFO files before we edit them.
257 local vmgi=$1
258 _check_ifo "${vmgi}" "445644564944454f2d564d47" || return 1
259
260 echo "${vmgi}"
261
262 # Clear the region code.
263 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo.html
264 # 0x23 - region code restrict byte - set to 0x00 for region free.
265 echo "clearing region code"
266 bytes_set "${vmgi}" 0x23 0x00
267
268 # Clear all Prohibited User Operations (PUOs) in TT_SRPT (Uop1 & Uop0).
269 # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vmg.html
270 echo "clearing PUOs in TT_SRPT"
271 tt_srpt_sec=$(dvdsec "${vmgi}" 0xC4)
272
273 num_titles=$(bytes_get "${vmgi}" ${tt_srpt_sec} 2)
274 for (( t = 0; t < num_titles; ++t )) ; do
275 off=$(( tt_srpt_sec + 8 + (t * 12) ))
276 printf "\ttitle type #%2i @ %#06x: " "${t}" "${off}"
277 title_type=$(bytes_get "${vmgi}" ${off} 1)
278 uop0=$(( title_type & 1 ))
279 uop1=$(( title_type & 2 ))
280 printf "%s (uop1:%i uop0:%i): " ${title_type} ${uop1} ${uop0}
281 new_title_type=$(( title_type & ~3 ))
282 if [[ ${title_type} -ne ${new_title_type} ]] ; then
283 bytes_set "${vmgi}" ${off} $(printf '%x' ${new_title_type})
284 echo "cleared"
285 else
286 echo "nothing to do"
287 fi
288 done
289
290 # Clear all the PUOs in the PGCs. But Preserve the angle bit.
291 # Note: Removal of the angle PUO can cause certain standalone players to
292 # display the "angle" icon during playback. Removal of the angle PUO
293 # whilst permitted is therefore not recommended.
294 # http://dvdnav.mplayerhq.hu/dvdinfo/uops.html
295 #fp_pgc_addr=$(bytes_get "${vmgi}" 0x84 4)
296 #num_pgcs=$(bytes_get "${vmgi}" $(( fp_pgc_sec + 1 )) 1)
297 echo "clearing PUOs in PGCs"
298 vmgm_pgci_ut_sec=$(dvdsec "${vmgi}" 0xC8)
299 process_pgci_ut "${vmgi}" ${vmgm_pgci_ut_sec}
300}
301
302process_vtsi() {
303 # Back up the .IFO files before we edit them.
304 local vtsi=$1
305 _check_ifo "${vtsi}" "445644564944454f2d565453" || return 1
306
307 echo "${vtsi}"
308
309 echo "clearing PUOs in PGCs"
310
311 vts_pgci_sec=$(dvdsec "${vtsi}" 0xCC)
312 process_pgci "${vtsi}" ${vts_pgci_sec} '\t'
313
314 vtsm_pgci_ut_sec=$(dvdsec "${vtsi}" 0xD0)
315 process_pgci_ut "${vtsi}" ${vtsm_pgci_ut_sec}
316}
317
318modify_dvd() {
319 local out_vts_dir="${out}/${Volume}/VIDEO_TS"
320 local vmgi vtsi
321
a356b2d3
MF
322 if ${doit_errors} ; then
323 echo "skipping modifications due to possible errors in files"
324 return
325 fi
326
d6d9f2ac
MF
327 for vmgi in "${out_vts_dir}"/VIDEO_TS.{IFO,BUP} ; do
328 process_vmgi "${vmgi}"
329 check_changes "${vmgi}" || exit 1
330 done
331
332 for vtsi in "${out_vts_dir}"/VTS_??_?.{IFO,BUP} ; do
333 process_vtsi "${vtsi}"
334 check_changes "${vtsi}" || exit 1
335 done
336}
337
338#
339# Finally, create a new iso.
340#
341mkiso_dvd() {
342 set -- mkisofs -quiet -dvd-video -no-bak \
343 -A "${Application}" \
344 -p "${Preparer}" \
345 -publisher "${Publisher}" \
346 -sysid "${System}" \
347 -V "${Volume}" \
348 -volset "${Volume_Set}" \
349 -o "${Volume}.iso" "${Volume}"
350 sh="${out}/.${Volume}.sh"
351 (
352 echo "#!/bin/sh"
353 echo "cd '${out}' || exit 1"
354 echo ". './.${Volume}.vars.sh'"
355 printf 'set -- '
356 printf '%q ' "$@"
357 echo
358 echo 'echo "$@"; exec "$@"'
359 ) > "${sh}"
360 chmod a+x "${sh}"
361 "${sh}" || exit
21776703
MF
362 sync
363 md5sum "${out}/${Volume}.iso" > "${out}/${Volume}.md5"
d6d9f2ac
MF
364
365 du -h "${out}/${Volume}.iso"
a356b2d3 366 sudo chattr +i "${out}/${Volume}.iso"
4ec333bc 367 sync
d6d9f2ac
MF
368}
369
a356b2d3 370${doit_errors} && raw_read_dvd
d6d9f2ac
MF
371${doit_backup} && backup_dvd
372${doit_modify} && modify_dvd
373${doit_mkiso} && mkiso_dvd
374
375${doit_backup} && eject
376exit 0