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