]> git.wh0rd.org Git - home.git/blob - .bin/backup-dvd
cros-board: fix double slash and add more helpful commands
[home.git] / .bin / backup-dvd
1 #!/bin/bash
2
3 err() { printf 'error: %b\n' "$*" 1>&2; exit 1; }
4 usage() {
5         cat <<-EOF
6         backup-dvd [options] [commands]
7
8         Options:
9          -i <input dev>      Defaults to ${dev}
10          -o <output dir>     Defaults to ${out}
11          -n <volume name>    Defaults to volume on disk
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.
19         EOF
20         exit ${1:-0}
21 }
22
23 dev=/dev/cdrom
24 out=${PWD}
25 vol=
26
27 unset doit_{backup,modify,mkiso}
28
29 eval set -- `getopt -l backup,modify,mkiso -- hi:n:o: "$@"`
30 while [[ -n $1 ]] ; do
31         case $1 in
32                 -h) usage;;
33                 -i) dev=$2; shift 2;;
34                 -o) out=$2; shift 2;;
35                 -n) vol=$2; shift 2;;
36
37                 --backup) doit_backup=true; shift;;
38                 --modify) doit_modify=true; shift;;
39                 --mkiso)  doit_mkiso=true;  shift;;
40
41                 --) shift; break;;
42                 -*) usage 1;;
43                 *)  usage 2; break;;
44         esac
45 done
46
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
58 # iso-info              mkisofs
59 Application=    # -A
60 Preparer=               # -p
61 Publisher=              # -publisher
62 System=                 # -sysid
63 Volume=${vol}   # -V
64 Volume_Set=             # -volset
65
66 if ${doit_backup} ; then
67         if [[ -z ${Volume} ]] ; then
68                 info=$(iso-info ${dev}) || exit 1
69
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
78
79         if [[ -z ${Volume} ]] ; then
80                 echo "Unable to parse Volume out of ISO"
81                 iso-info ${dev}
82         fi
83
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
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 }
100
101 #
102 # Backup the disc.
103 #
104 opts=(
105         -i ${dev}
106         -o "${out}"
107         -n "${Volume}"
108 )
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