]>
Commit | Line | Data |
---|---|---|
4108a92c MF |
1 | #!/bin/bash |
2 | ||
d6d9f2ac | 3 | err() { printf 'error: %b\n' "$*" 1>&2; exit 1; } |
4108a92c MF |
4 | usage() { |
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 | ||
24 | dev=/dev/cdrom | |
25 | out=${PWD} | |
2b2f7f17 | 26 | vol= |
4108a92c | 27 | |
d6d9f2ac | 28 | unset doit_{backup,modify,mkiso} |
a356b2d3 | 29 | doit_errors=false |
d6d9f2ac | 30 | |
a356b2d3 | 31 | eval set -- `getopt -l errors,backup,modify,mkiso -- hi:n:o: "$@"` |
4108a92c MF |
32 | while [[ -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 | |
48 | done | |
49 | ||
d6d9f2ac MF |
50 | doit_all() { |
51 | : ${doit_backup:=$1} | |
52 | : ${doit_modify:=$1} | |
53 | : ${doit_mkiso:=$1} | |
54 | } | |
55 | if [[ -z ${doit_backup}${doit_modify}${doit_mkiso} ]] ; then | |
56 | doit_all true | |
57 | else | |
58 | doit_all false | |
59 | fi | |
60 | ||
4108a92c MF |
61 | # iso-info mkisofs |
62 | Application= # -A | |
63 | Preparer= # -p | |
64 | Publisher= # -publisher | |
65 | System= # -sysid | |
2b2f7f17 | 66 | Volume=${vol} # -V |
4108a92c MF |
67 | Volume_Set= # -volset |
68 | ||
d6d9f2ac MF |
69 | if ${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" | |
90 | else | |
91 | [[ -z ${Volume} ]] && err "Need to specify -n with --modify/--mkiso" | |
92 | . "${out}/.${Volume}.vars.sh" || exit 1 | |
93 | fi | |
4108a92c MF |
94 | |
95 | e() { | |
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 | # | |
107 | raw_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 |
123 | backup_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 | # | |
140 | bytes_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 | } | |
155 | bytes_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 | } | |
163 | dvdsec() { | |
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 | ||
185 | check_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" | |
191 | diff -u "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff" | |
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 | ||
215 | process_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 | ||
239 | process_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 | ||
255 | process_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 | ||
302 | process_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 | ||
318 | modify_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 | # | |
341 | mkiso_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 | |
376 | exit 0 |