]>
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 | ) | |
d6d9f2ac MF |
130 | if ! e dvdbackup -M "${opts[@]}" ; then |
131 | e dvdbackup -F "${opts[@]}" || exit 1 | |
132 | fi | |
133 | } | |
134 | ||
135 | # | |
136 | # Modify some of the runtime settings. | |
137 | # | |
138 | bytes_get() { | |
139 | local pfx='0x' sep | |
140 | while [[ $# -gt 3 ]]; do | |
141 | case $1 in | |
142 | "-p") pfx=$2;; | |
143 | "-s") sep=$2;; | |
144 | *) break;; | |
145 | esac | |
146 | shift 2 | |
147 | done | |
148 | printf '%b' "${pfx}" | |
149 | ||
150 | local file=$1 off=$2 num=${3:-1} | |
151 | hexdump -v -n ${num} -s $((${off})) -e "1/1 \"%02x${sep}\"" "${file}" | |
152 | } | |
153 | bytes_set() { | |
154 | local file=$1 off=$2 | |
155 | shift 2 | |
156 | (for b; do printf "\x${b#0x}"; done) | \ | |
157 | dd of="${file}" \ | |
158 | bs=1 seek=$((${off})) \ | |
159 | conv=notrunc status=none | |
160 | } | |
161 | dvdsec() { | |
162 | # DVDs have sectors of 2048 bytes (2^11). | |
163 | local file=$1 off=$2 num=${3:-4} | |
164 | echo $(( $(bytes_get "${file}" "${off}" "${num}") << 11 )) | |
165 | } | |
166 | ||
167 | _check_ifo() { | |
168 | # Back up the .IFO files before we edit them. | |
169 | local ifo=$1 magic=$2 | |
170 | [[ -e ${ifo} ]] || return 1 | |
171 | ||
172 | if [[ $(bytes_get "${ifo}" 0 12) != "0x${magic}" ]] ; then | |
173 | echo "not a VMG IFO file: ${ifo}" | |
174 | return 1 | |
175 | fi | |
176 | if [[ ! -e ${ifo}.bak ]] ; then | |
177 | cp -a "${ifo}" "${ifo}.bak" | |
178 | fi | |
179 | ||
180 | return 0 | |
181 | } | |
182 | ||
183 | check_changes() { | |
184 | [[ -e ifodump ]] || return 0 | |
185 | ||
186 | local file=$1 | |
187 | ./ifodump -f "${file}.bak" > "${file}.bak.dmp" | |
188 | ./ifodump -f "${file}" > "${file}.dmp" | |
189 | diff -u "${file}.bak.dmp" "${file}.dmp" | sed -e 1d -e 2d > "${file}.diff" | |
190 | local out=$( | |
191 | grep '^[+-]' "${file}.diff" | \ | |
192 | grep -v \ | |
193 | -e '^-VMG Category:.*' \ | |
194 | -e '^+VMG Category: 00000000 (Region Code=ff)' \ | |
195 | -e '^[-+].*Title playback type:' \ | |
196 | -e '^-.*Title or time play:1' \ | |
197 | -e '^+.*Title or time play:0' \ | |
198 | -e '^-Prohibited user operations:' \ | |
199 | -e '^+Prohibited user operations: Angle Change, $' \ | |
200 | -e '^+Prohibited user operations: None$' \ | |
201 | | egrep -v -e '^[-+]([0-9a-f]{2} )+$' | |
202 | ) | |
203 | rm "${file}".{{,bak.}dmp,diff} | |
204 | if [[ -z ${out} ]] ; then | |
205 | return 0 | |
206 | else | |
207 | echo "error in ifo modification:" | |
208 | echo "${out}" | |
209 | return 1 | |
210 | fi | |
211 | } | |
212 | ||
213 | process_pgci() { | |
214 | # http://dvdnav.mplayerhq.hu/dvdinfo/pgc.html | |
215 | local file=$1 pgci_off=$2 indent=$3 | |
216 | local num_pgcs p pgc_off off | |
217 | ||
218 | num_pgcs=$(bytes_get "${file}" ${pgci_off} 2) | |
219 | for (( p = 0; p < num_pgcs; ++p )) ; do | |
220 | pgc_off=$(( | |
221 | pgci_off + | |
222 | $(bytes_get "${file}" $(( pgci_off + 8 + 8 * p + 4 )) 4) | |
223 | )) | |
224 | off=$(( pgc_off + 8 )) | |
225 | pgc_puo=$(bytes_get "${file}" ${off} 4) | |
226 | printf '%bpgc #%2i @ %#06x: %s: ' "${indent}" ${p} ${vmgm_pgc_off} ${pgc_puo} | |
227 | new_pgc_puo=$(( pgc_puo & (1 << 22) )) # Keep angle field. | |
228 | if [[ ${pgc_puo} -ne ${new_pgc_puo} ]] ; then | |
229 | bytes_set "${file}" ${off} 0 $(printf '%x' $(( new_pgc_puo >> 16 ))) 0 0 | |
230 | echo "cleared" | |
231 | else | |
232 | echo "nothing to do" | |
233 | fi | |
234 | done | |
235 | } | |
236 | ||
237 | process_pgci_ut() { | |
238 | # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vts.html#pgciut | |
239 | local file=$1 pgci_ut_off=$2 | |
240 | local num_langs l off | |
241 | ||
242 | num_langs=$(bytes_get "${file}" ${pgci_ut_off} 2) | |
243 | for (( l = 0; l < num_langs; ++l )) ; do | |
244 | off=$(( | |
245 | pgci_ut_off + | |
246 | $(bytes_get "${file}" $(( pgci_ut_off + 8 + 8 * l + 4 )) 4) | |
247 | )) | |
248 | printf '\tclearing lang #%i @ %#06x\n' ${l} ${off} | |
249 | process_pgci "${file}" ${off} '\t\t' | |
250 | done | |
251 | } | |
252 | ||
253 | process_vmgi() { | |
254 | # Back up the .IFO files before we edit them. | |
255 | local vmgi=$1 | |
256 | _check_ifo "${vmgi}" "445644564944454f2d564d47" || return 1 | |
257 | ||
258 | echo "${vmgi}" | |
259 | ||
260 | # Clear the region code. | |
261 | # http://dvdnav.mplayerhq.hu/dvdinfo/ifo.html | |
262 | # 0x23 - region code restrict byte - set to 0x00 for region free. | |
263 | echo "clearing region code" | |
264 | bytes_set "${vmgi}" 0x23 0x00 | |
265 | ||
266 | # Clear all Prohibited User Operations (PUOs) in TT_SRPT (Uop1 & Uop0). | |
267 | # http://dvdnav.mplayerhq.hu/dvdinfo/ifo_vmg.html | |
268 | echo "clearing PUOs in TT_SRPT" | |
269 | tt_srpt_sec=$(dvdsec "${vmgi}" 0xC4) | |
270 | ||
271 | num_titles=$(bytes_get "${vmgi}" ${tt_srpt_sec} 2) | |
272 | for (( t = 0; t < num_titles; ++t )) ; do | |
273 | off=$(( tt_srpt_sec + 8 + (t * 12) )) | |
274 | printf "\ttitle type #%2i @ %#06x: " "${t}" "${off}" | |
275 | title_type=$(bytes_get "${vmgi}" ${off} 1) | |
276 | uop0=$(( title_type & 1 )) | |
277 | uop1=$(( title_type & 2 )) | |
278 | printf "%s (uop1:%i uop0:%i): " ${title_type} ${uop1} ${uop0} | |
279 | new_title_type=$(( title_type & ~3 )) | |
280 | if [[ ${title_type} -ne ${new_title_type} ]] ; then | |
281 | bytes_set "${vmgi}" ${off} $(printf '%x' ${new_title_type}) | |
282 | echo "cleared" | |
283 | else | |
284 | echo "nothing to do" | |
285 | fi | |
286 | done | |
287 | ||
288 | # Clear all the PUOs in the PGCs. But Preserve the angle bit. | |
289 | # Note: Removal of the angle PUO can cause certain standalone players to | |
290 | # display the "angle" icon during playback. Removal of the angle PUO | |
291 | # whilst permitted is therefore not recommended. | |
292 | # http://dvdnav.mplayerhq.hu/dvdinfo/uops.html | |
293 | #fp_pgc_addr=$(bytes_get "${vmgi}" 0x84 4) | |
294 | #num_pgcs=$(bytes_get "${vmgi}" $(( fp_pgc_sec + 1 )) 1) | |
295 | echo "clearing PUOs in PGCs" | |
296 | vmgm_pgci_ut_sec=$(dvdsec "${vmgi}" 0xC8) | |
297 | process_pgci_ut "${vmgi}" ${vmgm_pgci_ut_sec} | |
298 | } | |
299 | ||
300 | process_vtsi() { | |
301 | # Back up the .IFO files before we edit them. | |
302 | local vtsi=$1 | |
303 | _check_ifo "${vtsi}" "445644564944454f2d565453" || return 1 | |
304 | ||
305 | echo "${vtsi}" | |
306 | ||
307 | echo "clearing PUOs in PGCs" | |
308 | ||
309 | vts_pgci_sec=$(dvdsec "${vtsi}" 0xCC) | |
310 | process_pgci "${vtsi}" ${vts_pgci_sec} '\t' | |
311 | ||
312 | vtsm_pgci_ut_sec=$(dvdsec "${vtsi}" 0xD0) | |
313 | process_pgci_ut "${vtsi}" ${vtsm_pgci_ut_sec} | |
314 | } | |
315 | ||
316 | modify_dvd() { | |
317 | local out_vts_dir="${out}/${Volume}/VIDEO_TS" | |
318 | local vmgi vtsi | |
319 | ||
a356b2d3 MF |
320 | if ${doit_errors} ; then |
321 | echo "skipping modifications due to possible errors in files" | |
322 | return | |
323 | fi | |
324 | ||
d6d9f2ac MF |
325 | for vmgi in "${out_vts_dir}"/VIDEO_TS.{IFO,BUP} ; do |
326 | process_vmgi "${vmgi}" | |
327 | check_changes "${vmgi}" || exit 1 | |
328 | done | |
329 | ||
330 | for vtsi in "${out_vts_dir}"/VTS_??_?.{IFO,BUP} ; do | |
331 | process_vtsi "${vtsi}" | |
332 | check_changes "${vtsi}" || exit 1 | |
333 | done | |
334 | } | |
335 | ||
336 | # | |
337 | # Finally, create a new iso. | |
338 | # | |
339 | mkiso_dvd() { | |
340 | set -- mkisofs -quiet -dvd-video -no-bak \ | |
341 | -A "${Application}" \ | |
342 | -p "${Preparer}" \ | |
343 | -publisher "${Publisher}" \ | |
344 | -sysid "${System}" \ | |
345 | -V "${Volume}" \ | |
346 | -volset "${Volume_Set}" \ | |
347 | -o "${Volume}.iso" "${Volume}" | |
348 | sh="${out}/.${Volume}.sh" | |
349 | ( | |
350 | echo "#!/bin/sh" | |
351 | echo "cd '${out}' || exit 1" | |
352 | echo ". './.${Volume}.vars.sh'" | |
353 | printf 'set -- ' | |
354 | printf '%q ' "$@" | |
355 | echo | |
356 | echo 'echo "$@"; exec "$@"' | |
357 | ) > "${sh}" | |
358 | chmod a+x "${sh}" | |
359 | "${sh}" || exit | |
360 | ||
361 | du -h "${out}/${Volume}.iso" | |
a356b2d3 | 362 | sudo chattr +i "${out}/${Volume}.iso" |
d6d9f2ac MF |
363 | } |
364 | ||
a356b2d3 | 365 | ${doit_errors} && raw_read_dvd |
d6d9f2ac MF |
366 | ${doit_backup} && backup_dvd |
367 | ${doit_modify} && modify_dvd | |
368 | ${doit_mkiso} && mkiso_dvd | |
369 | ||
370 | ${doit_backup} && eject | |
371 | exit 0 |