1 /*
2 dvdauthor mainline, interpretation of command-line options and parsing of
3 dvdauthor XML control files
4 */
5 /*
6 * Copyright (C) 2002 Scott Smith (trckjunky@users.sourceforge.net)
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or (at
11 * your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but
14 * WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
21 * MA 02110-1301 USA.
22 */
23
24 #include "config.h"
25
26 #include "compat.h"
27
28 #include <assert.h>
29 #include <ctype.h>
30 #include <fcntl.h>
31
32 #include "conffile.h"
33 #include "dvdauthor.h"
34 #include "readxml.h"
35 #include "rgb.h"
36
37 int default_video_format = VF_NONE;
38 char provider_str[PROVIDER_SIZE];
39
40 /* common parsing bits for both command line and XML file */
41
42 #define RGB2YCrCb(R,G,B) ((((int)RGB2Y(R,G,B))<<16)|(((int)RGB2Cr(R,G,B))<<8)|(((int)RGB2Cb(R,G,B))))
43
44 static int readdvdauthorxml(const char *xmlfile,const char *fb);
45
46 static enum
47 {
48 chapters_neither, /* neither of following seen yet */
49 chapters_cells, /* vob chapters specified via cells (and possibly also "chapters" attribute) */
50 chapters_chapters, /* only via "chapters" attribute */
51 }
52 hadchapter = chapters_neither; /* info about last VOB in current PGC */
53 static int
54 pauselen=0;
55 static bool
56 writeoutput = true;
57 static char
58 *cur_chapters = 0;
59
parsevideoopts(struct pgcgroup * va,const char * o)60 static void parsevideoopts(struct pgcgroup *va,const char *o)
61 {
62 char *s;
63 while(NULL!=(s=str_extract_until(&o,"+"))) {
64 if(pgcgroup_set_video_attr(va,VIDEO_ANY,s)) {
65 fprintf(stderr,"ERR: Video option '%s' overrides previous option\n",s);
66 exit(1);
67 }
68 free(s);
69 }
70 }
71
parseaudiotrack(struct pgcgroup * va,const char * o,int c)72 static void parseaudiotrack(struct pgcgroup *va,const char *o,int c)
73 {
74 char *s;
75 while(NULL!=(s=str_extract_until(&o,"+"))) {
76 if(pgcgroup_set_audio_attr(va,AUDIO_ANY,s,c)) {
77 fprintf(stderr,"ERR: Audio option '%s' on track %d overrides previous option\n",s,c);
78 exit(1);
79 }
80 free(s);
81 }
82 }
83
parseaudioopts(struct pgcgroup * va,const char * o)84 static void parseaudioopts(struct pgcgroup *va,const char *o)
85 {
86 char *s;
87 int ch=0;
88 while(NULL!=(s=str_extract_until(&o,", "))) {
89 parseaudiotrack(va,s,ch);
90 free(s);
91 ch++;
92 }
93 }
94
parsesubpicturetrack(struct pgcgroup * va,const char * o,int c)95 static void parsesubpicturetrack(struct pgcgroup *va,const char *o,int c)
96 {
97 char *s;
98 while(NULL!=(s=str_extract_until(&o,"+"))) {
99 if(pgcgroup_set_subpic_attr(va,SPU_ANY,s,c)) {
100 fprintf(stderr,"ERR: Subpicture option '%s' on track %d overrides previous option\n",s,c);
101 exit(1);
102 }
103 free(s);
104 }
105 }
106
parsesubpictureopts(struct pgcgroup * va,const char * o)107 static void parsesubpictureopts(struct pgcgroup *va,const char *o)
108 {
109 char *s;
110 int ch=0;
111 while(NULL!=(s=str_extract_until(&o,", "))) {
112 parsesubpicturetrack(va,s,ch);
113 free(s);
114 ch++;
115 }
116 }
117
parsebutton(struct pgc * va,char * b)118 static void parsebutton(struct pgc *va,char *b)
119 {
120 char *bnm=0;
121
122 if( strchr(b,'=') ) {
123 char *p=strchr(b,'=');
124 p[0]=0;
125 bnm=b;
126 b=p+1;
127 }
128 pgc_add_button(va,bnm,b);
129 }
130
parseinstructions(struct pgc * va,const char * b)131 static void parseinstructions(struct pgc *va,const char *b)
132 {
133 char *c=str_extract_until(&b,"=");
134 if(!strcasecmp(c,"post")) {
135 pgc_set_post(va,b);
136 } else if(!strcasecmp(c,"pre")) {
137 pgc_set_pre(va,b);
138 } else {
139 fprintf(stderr,"Unknown instruction block: %s\n",c);
140 exit(1);
141 }
142 free(c);
143 }
144
parseentries(struct pgc * p,vtypes ismenuf,const char * b)145 static void parseentries(struct pgc *p, vtypes ismenuf, const char *b)
146 {
147 char *v;
148 while (NULL != (v = str_extract_until(&b, ", ")))
149 {
150 pgc_add_entry(p, ismenuf, v);
151 free(v);
152 } /*while*/
153 } /*parseentries*/
154
parsechapter(const char * s)155 static double parsechapter(const char *s)
156 {
157 double total=0,field=0;
158 int i;
159
160 for( i=0; s[i]; i++ ) {
161 if(!strchr(s+i,':')) {
162 char *last;
163 field=strtod(s+i,&last);
164 if( *last ) {
165 fprintf(stderr,"ERR: Cannot parse chapter timestamp '%s'\n",s);
166 exit(1);
167 }
168 break;
169 } else if(isdigit(s[i]))
170 field=field*10+s[i]-'0';
171 else if(s[i]==':') {
172 total=total*60+field;
173 field=0;
174 } else {
175 fprintf(stderr,"ERR: Cannot parse chapter timestamp '%s'\n",s);
176 exit(1);
177 }
178 }
179 return total*60+field;
180 }
181
parsechapters(const char * o,struct source * src,int pauselen)182 static void parsechapters(const char *o, struct source *src, int pauselen)
183 /* parses a chapter spec and adds corresponding cell definitions. */
184 {
185 char *s;
186 double last = 0;
187 cell_chapter_types lastchap = CELL_NEITHER;
188 while (NULL != (s = str_extract_until(&o, ", ")))
189 {
190 const double total = parsechapter(s);
191 if (total > last)
192 {
193 source_add_cell(src, last, total, lastchap, 0, 0);
194 last = total;
195 lastchap = CELL_CHAPTER_PROGRAM;
196 }
197 else if (total == last)
198 lastchap = CELL_CHAPTER_PROGRAM;
199 /* else report error? user specified chapter times in wrong order? */
200 free(s);
201 } /*while*/
202 source_add_cell(src, last, -1, lastchap, pauselen, 0);
203 } /*parsechapters*/
204
readpalette(struct pgc * p,const char * fname)205 static void readpalette(struct pgc *p,const char *fname)
206 {
207 int i,rgbf;
208 struct vfile h;
209
210 h=varied_open(fname, O_RDONLY, "palette file");
211
212 /* write out colors, the hex is the 0yuv combined in one integer 00yyuuvv */
213 i=strlen(fname);
214 rgbf=( i>=4 && !strcasecmp(fname+i-4,".rgb") );
215 for( i=0; i<16; i++ ) {
216 int pcolor;
217 fscanf(h.h, "%x", &pcolor);
218 if( rgbf ) {
219 const int r=(pcolor>>16)&255,
220 g=(pcolor>>8)&255,
221 b=(pcolor)&255;
222 pcolor=RGB2YCrCb(r,g,b);
223 }
224 pgc_set_color(p,i,pcolor);
225 }
226
227 #if 0
228 int b;
229 unsigned char groups[24];
230
231 memset(groups,0,24);
232 i=0;
233 while(fscanf(h.h,"%x",&b) == 1 && i<24 ) {
234 groups[i++]=b>>24;
235 groups[i++]=b>>16;
236 groups[i++]=b>>8;
237 groups[i++]=b;
238 if( !(i&7) )
239 pgc_set_buttongroup(p,(i-8)/8,groups+(i-8));
240 }
241 #endif
242 varied_close(h);
243 }
244
usage()245 static void usage()
246 {
247 #ifdef HAVE_GETOPT_LONG
248 #define LONGOPT(x) x
249 #define LONGOPT2(x,y) x
250 #else
251 #define LONGOPT(x)
252 #define LONGOPT2(x,y) y
253 #endif
254
255 fprintf(stderr,
256 "syntax: dvdauthor [-o VTSBASE | -n] [options] VOBFILE(s)\n"
257 "\n\t-x XMLFILE where XMLFILE is a configuration file describing the\n"
258 "\t structure of the DVD to create. If you use a config file, then you\n"
259 "\t do not need to specify any other options, except -o and -n.\n"
260 "\n\t-n skips writing any files, for testing purposes. MUST occur before any\n"
261 "\t other options.\n"
262 "\n\t" LONGOPT("--video=VOPTS or ") "-v VOPTS where VOPTS is a plus (+) separated list of\n"
263 "\t video options. dvdauthor will try to infer any unspecified options.\n"
264 "\t\tpal, ntsc, 4:3, 16:9, 720xfull, 720x576, 720x480, 704xfull,\n"
265 "\t\t704x576, 704x480, 352xfull, 352x576, 352x480, 352xhalf,\n"
266 "\t\t352x288, 352x240, nopanscan, noletterbox, crop.\n"
267 "\t Default is ntsc, 4:3, 720xfull\n"
268 "\n\t" LONGOPT("--audio=AOPTS or ") "-a AOPTS where AOPTS is a plus (+) separated list of\n"
269 "\t options for an audio track, with each track separated by a\n"
270 "\t comma (,). For example -a ac3+en,mp2+de specifies two audio\n"
271 "\t tracks: the first is an English track encoded in AC3, the second is\n"
272 "\t a German track encoded using MPEG-1 layer 2 compression.\n"
273 "\t\tac3, mp2, pcm, dts, 16bps, 20bps, 24bps, drc, surround, nolang,\n"
274 "\t\t1ch, 2ch, 3ch, 4ch, 5ch, 6ch, 7ch, 8ch, and any two letter\n"
275 "\t\tISO 639 language abbreviation.\n"
276 "\t Default is 1 track, mp2, 20bps, nolang, 2ch.\n"
277 "\t 'ac3' implies drc, 6ch.\n"
278 "\n\t" LONGOPT("--subpictures=SOPTS or ") "-s SOPTS where SOPTS is a plus (+) separated list\n"
279 "\t of options for a subpicture track, with each track separated by a\n"
280 "\t comma (,).\n"
281 "\t\tnolang and any two letter language abbreviation (see -a)\n"
282 "\t Default is no subpicture tracks.\n"
283 "\n\t" LONGOPT("--palette[=FILE] or ") "-p FILE or -P where FILE specifies where to get the\n"
284 "\t subpicture palette. Settable per title and per menu. If the\n"
285 "\t filename ends in .rgb (case insensitive) then it is assumed to be\n"
286 "\t RGB, otherwise it is YUV. Entries should be 6 hexadecimal digits.\n"
287 "\t FILE defaults to xste-palette.dat\n"
288 "\n\t" LONGOPT2("--file=FILE or -f FILE or FILE","-f FILE") " where FILE is either a file, a pipe, or a\n"
289 "\t shell command ending in | which supplies an MPEG-2 system stream\n"
290 "\t with VOB sectors inserted in the appropriate places\n"
291 "\t (using mplex -f 8 to generate)\n"
292 "\n\t" LONGOPT("--chapter[s][=COPTS] or ") "-c COPTS or -C where COPTS is a comma (,)\n"
293 "\t separated list of chapter markers. Each marker is of the form\n"
294 "\t [[h:]mm:]ss[.frac] and is relative to the SCR of the next file\n"
295 "\t listed (independent of any timestamp transposing that occurs within\n"
296 "\t dvdauthor). The chapter markers ONLY apply to the next file listed.\n"
297 "\t COPTS defaults to 0\n"
298 "\n\t" LONGOPT("--menu or ") "-m creates a menu.\n"
299 "\n\t" LONGOPT("--title or ") "-t creates a title.\n"
300 "\n\t" LONGOPT("--toc or ") "-T creates the table of contents file instead of a titleset.\n"
301 "\t If this option is used, it should be listed first, and you may not\n"
302 "\t specify any titles.\n"
303 "\n\t" LONGOPT("--entry=EOPTS or ") "-e EOPTS makes the current menu the default for\n"
304 "\t certain circumstances. EOPTS is a comma separated list of any of:\n"
305 "\t\tfor TOC menus: title\n"
306 "\t\tfor VTS menus: root, ptt, audio, subtitle, angle\n"
307 "\n\t" LONGOPT("--button or ") "-b DEST specifies what command to issue for each button.\n"
308 "\t See " LONGOPT("--instructions or ") "-i for a description of\n"
309 "\t DEST.\n"
310 "\n\t" LONGOPT("--instructions or ") "-i post=DEST executes the DEST instructions at the\n"
311 "\t end of the title.\n"
312 "\n\t" LONGOPT("--fpc or ") "-F CMD sets the commands to be executed when the disc is first\n"
313 "\t inserted.\n"
314 "\n\t" LONGOPT("--jumppad or ") "-j enables the creation of jumppads, which allow greater\n"
315 "\t flexibility in choosing jump/call destinations.\n"
316 "\n\t" LONGOPT("--allgprm or ") "-g enables the use of all 16 general purpose registers.\n"
317 "\n\t" LONGOPT("--help or ") "-h displays this screen.\n"
318 );
319 exit(1);
320 }
321
322 /* bits for authoring via command line */
323 #define NOXML \
324 if (xmlfile) \
325 { \
326 fprintf \
327 ( \
328 stderr, \
329 "ERR: Cannot use command line options after specifying XML config file\n" \
330 ); \
331 return 1; \
332 } /*if*/
333 #define MAINDEF \
334 if (!usedtocflag) \
335 { \
336 fprintf(stderr, "ERR: Must first specify -t, -m, or -x.\n"); \
337 return 1; \
338 } /*if*/ \
339 if (istoc && istitle) \
340 { \
341 fprintf(stderr, "ERR: TOC cannot have titles\n"); \
342 return 1; \
343 } /*if*/ \
344 if (!va[istitle]) \
345 { \
346 va[istitle] = pgcgroup_new((int)istoc + 1 - (int)istitle); \
347 } /*if*/
348
349 #define MAINDEFPGC \
350 MAINDEF \
351 if (!curpgc) \
352 curpgc = pgc_new();
353
354 #define MAINDEFVOB \
355 MAINDEFPGC \
356 if (!curvob) \
357 curvob = source_new();
358
359 #define FLUSHPGC \
360 if (curpgc) \
361 { \
362 pgcgroup_add_pgc(va[istitle], curpgc); \
363 curpgc = 0; \
364 } /*if*/
365
main(int argc,char ** argv)366 int main(int argc, char **argv)
367 {
368 struct pgcgroup *va[2]; /* element 0 for doing menus, 1 for doing titles */
369 struct menugroup *mg;
370 char *fbase = 0; /* output directory name */
371 char * xmlfile = 0; /* name of XML control file, if any */
372 bool istitle = true; /* index into va */
373 bool istoc = false, /* true if doing VMG, false if doing titleset */
374 usedtocflag = false; /* indicates that istoc can no longer be changed */
375 struct pgc *curpgc = 0,* fpc = 0;
376 struct source *curvob = 0;
377 #ifdef HAVE_GETOPT_LONG
378 const static struct option longopts[]={
379 {"video",1,0,'v'},
380 {"audio",1,0,'a'},
381 {"subpictures",1,0,'s'},
382 {"palette",2,0,'p'},
383 {"file",1,0,'f'},
384 {"chapters",2,0,'c'},
385 {"help",0,0,'h'},
386 {"menu",0,0,'m'},
387 {"title",0,0,'t'},
388 {"button",1,0,'b'},
389 {"toc",0,0,'T'},
390 {"instructions",1,0,'i'},
391 {"entry",1,0,'e'},
392 {"fpc",1,0,'F'},
393 {"jumppad",0,0,'j'},
394 {"allgprm",0,0,'g'},
395 {0,0,0,0}
396 };
397 #define GETOPTFUNC(x,y,z) getopt_long(x,y,"-" z,longopts,NULL)
398 #else
399 #define GETOPTFUNC(x,y,z) getopt(x,y,z)
400 #endif
401
402 default_video_format = get_video_format();
403 init_locale();
404 fputs(PACKAGE_HEADER("dvdauthor"), stderr);
405 if (default_video_format != VF_NONE)
406 {
407 fprintf
408 (
409 stderr,
410 "INFO: default video format is %s\n",
411 default_video_format == VF_PAL ? "PAL" : "NTSC"
412 );
413 }
414 else
415 {
416 #if defined(DEFAULT_VIDEO_FORMAT)
417 # if DEFAULT_VIDEO_FORMAT == 1
418 fprintf(stderr, "INFO: default video format is NTSC\n");
419 # elif DEFAULT_VIDEO_FORMAT == 2
420 fprintf(stderr, "INFO: default video format is PAL\n");
421 # endif
422 #else
423 fprintf(stderr, "INFO: no default video format, must explicitly specify NTSC or PAL\n");
424 #endif
425 } /*if*/
426
427 if (argc < 1)
428 {
429 fprintf(stderr, "ERR: No arguments!\n");
430 return 1;
431 } /*if*/
432 memset(va, 0, sizeof(struct pgcgroup *) * 2);
433
434 while (true)
435 {
436 int c = GETOPTFUNC(argc, argv, "f:o:O:v:a:s:hc:Cp:Pmtb:Ti:e:x:jgn");
437 if (c == -1)
438 break;
439 switch (c)
440 {
441 case 'h':
442 usage();
443 break;
444
445 case 'x':
446 if (usedtocflag)
447 {
448 fprintf
449 (
450 stderr,
451 "ERR: Cannot specify XML config file after using command line options\n"
452 );
453 return 1;
454 } /*if*/
455 if (xmlfile)
456 {
457 fprintf(stderr, "ERR: only one XML file allowed\n");
458 return 1;
459 } /*if*/
460 xmlfile = optarg;
461 break;
462
463 case 'n':
464 writeoutput = false;
465 break;
466
467 case 'j':
468 dvdauthor_enable_jumppad();
469 break;
470
471 case 'g':
472 dvdauthor_enable_allgprm();
473 break;
474
475 case 'T':
476 NOXML
477 if (usedtocflag)
478 {
479 fprintf
480 (
481 stderr,
482 "ERR: TOC (-T) option must come first because I am a lazy programmer.\n"
483 );
484 return 1;
485 } /*if*/
486 istoc = true;
487 usedtocflag = true; // just for completeness (also see -x FOO)
488 break;
489
490 case 'O':
491 delete_output_dir = true;
492 /* and fallthru */
493 case 'o':
494 fbase = optarg;
495 break;
496
497 case 'm':
498 NOXML
499 FLUSHPGC
500 usedtocflag = true; // force -T to occur before -m
501 hadchapter = chapters_neither; /* reset for new menu */
502 istitle = false;
503 break;
504
505 case 't':
506 NOXML
507 if (istoc)
508 {
509 fprintf(stderr, "ERR: TOC cannot have titles\n");
510 return 1;
511 } /*if*/
512 FLUSHPGC
513 usedtocflag = true;
514 hadchapter = chapters_neither; /* reset for new title */
515 istitle = true;
516 break;
517
518 case 'a':
519 NOXML
520 MAINDEF
521 parseaudioopts(va[istitle], optarg);
522 break;
523
524 case 'v':
525 NOXML
526 MAINDEF
527 parsevideoopts(va[istitle], optarg);
528 break;
529
530 case 's':
531 NOXML
532 MAINDEF
533 parsesubpictureopts(va[istitle], optarg);
534 break;
535
536 case 'b':
537 NOXML
538 MAINDEFPGC
539 parsebutton(curpgc, optarg);
540 break;
541
542 case 'i':
543 NOXML
544 MAINDEFPGC
545 parseinstructions(curpgc, optarg);
546 break;
547
548 case 'F':
549 NOXML
550 if (!istoc)
551 {
552 fprintf(stderr, "ERR: You may only specify FPC commands on the VMGM\n");
553 return 1;
554 } /*if*/
555 usedtocflag = true;
556 if (fpc)
557 {
558 fprintf(stderr,"ERR: FPC commands already specified\n");
559 return 1;
560 } /*if*/
561 fpc = pgc_new();
562 pgc_set_pre(fpc, optarg);
563 break;
564
565 case 'e':
566 NOXML
567 MAINDEFPGC
568 if (istitle)
569 {
570 fprintf(stderr, "ERR: Cannot specify an entry for a title.\n");
571 return 1;
572 } /*if*/
573 parseentries(curpgc, istoc ? VTYPE_VMGM : VTYPE_VTSM, optarg);
574 break;
575
576 case 'P':
577 /* NOXML */
578 optarg = 0; /* use default palette name */
579 /* fallthru */
580 case 'p':
581 NOXML
582 MAINDEFPGC
583 readpalette(curpgc, optarg ? optarg : "xste-palette.dat");
584 break;
585
586 case 1:
587 case 'f':
588 NOXML
589 MAINDEFVOB
590 source_set_filename(curvob, optarg);
591 if (hadchapter == chapters_chapters )
592 hadchapter = chapters_cells; /* chapters already specified */
593 else
594 source_add_cell
595 (
596 /*source =*/ curvob,
597 /*starttime =*/ 0,
598 /*endtime =*/ -1,
599 /*chap =*/
600 hadchapter == chapters_neither ?
601 CELL_CHAPTER_PROGRAM /* first vob in pgc */
602 :
603 CELL_NEITHER, /* subsequent vobs in pgc */
604 /*pause =*/ 0,
605 /*cmd =*/ 0
606 );
607 /* default to single chapter/cell for entire source file */
608 pgc_add_source(curpgc, curvob);
609 curvob = 0;
610 break;
611
612 case 'C':
613 /* NOXML */
614 optarg = 0;
615 /* fallthru */
616 case 'c':
617 NOXML
618 if (curvob)
619 {
620 fprintf(stderr, "ERR: cannot list -c twice for one file.\n");
621 return 1;
622 } /*if*/
623 MAINDEFVOB
624 hadchapter = chapters_chapters;
625 if (optarg)
626 parsechapters(optarg, curvob, 0);
627 else
628 source_add_cell(curvob, 0, -1, CELL_CHAPTER_PROGRAM, 0, 0);
629 /* default to single chapter for entire source file */
630 break;
631
632 default:
633 fprintf(stderr, "ERR: getopt returned bad code %d\n", c);
634 return 1;
635 } /*switch*/
636 } /*while*/
637 if (xmlfile)
638 {
639 return readdvdauthorxml(xmlfile, fbase);
640 }
641 else
642 {
643 if (curvob)
644 {
645 fprintf(stderr, "ERR: Chapters defined without a file source.\n");
646 return 1;
647 } /*if*/
648 FLUSHPGC
649 if (!va[0])
650 {
651 va[0] = pgcgroup_new(istoc ? VTYPE_VMGM : VTYPE_VTSM);
652 mg = menugroup_new();
653 menugroup_add_pgcgroup(mg, "en", va[0]);
654 /* fixme: would be nice to make language a preference setting, perhaps default to locale? */
655 }
656 else
657 mg = 0;
658 if (!va[1] && !istoc)
659 va[1] = pgcgroup_new(VTYPE_VTS);
660 if (!fbase && writeoutput)
661 {
662 fbase = get_outputdir();
663 if (!fbase)
664 usage();
665 } /*if*/
666 if (optind != argc)
667 {
668 fprintf(stderr, "ERR: bad version of getopt; please precede all sources with '-f'\n");
669 return 1;
670 } /*if*/
671 if (fbase)
672 {
673 const int l = strlen(fbase);
674 if (l && fbase[l - 1] == '/')
675 fbase[l - 1] = 0;
676 } /*if*/
677 if (istoc)
678 dvdauthor_vmgm_gen(fpc, mg, fbase);
679 else
680 dvdauthor_vts_gen(mg, va[1], fbase);
681 pgc_free(fpc);
682 menugroup_free(mg);
683 pgcgroup_free(va[1]);
684 return 0;
685 } /*if*/
686 } /*main*/
687
688 /* authoring via XML file */
689
690 enum {
691 DA_BEGIN=0,
692 DA_ROOT,
693 DA_SET,
694 DA_PGCGROUP,
695 DA_PGC,
696 DA_VOB,
697 DA_SUBP,
698 DA_NOSUB
699 };
700
701 static struct pgcgroup
702 *titles=0, /* titles saved here (only one set allowed) */
703 *curgroup=0; /* current menus or titles */
704 static struct menugroup
705 *mg=0, /* current menu group (for titleset or vmgm) */
706 *vmgmmenus=0; /* vmgm menu group saved here on completion */
707 static struct pgc *curpgc=0,*fpc=0;
708 static struct source *curvob=0;
709 static const char
710 *fbase = 0, /* output directory name */
711 *buttonname = 0; /* name of button currently being defined */
712 static vtypes
713 ismenuf = VTYPE_VTS; /* type of current pgcgroup structure being parsed */
714 static bool
715 istoc = false, /* true for vmgm, false for titleset */
716 hadtoc = false; /* set to true when vmgm seen */
717 static int
718 setvideo=0, /* to keep count of <video> tags */
719 setaudio=0, /* to keep count of <audio> tags */
720 setsubpicture=0, /* to keep count of <subpicture> tags */
721 subpmode=DA_NOSUB,
722 /* for determining context of a <subpicture> tag:
723 DA_NOSUB -- invalid
724 DA_PGC -- inside a <pgc> tag (no "lang" attribute allowed)
725 DA_PGCGROUP -- inside a <menus> or <titles> tag, must come before the <pgc> tags
726 */
727 subpstreamid=-1; /* id attribute of current <stream> saved here */
728 static char *subpstreammode=0; /* mode attribute of current <stream> saved here */
729 static enum
730 {
731 vob_has_neither, /* neither of following seen yet */
732 vob_has_chapters_pause, /* vob has "chapters" or "pause" attribute */
733 vob_has_cells, /* vob has <cell> subtags */
734 }
735 vobbasic; /* info about each VOB */
736 static cell_chapter_types
737 cell_chapter;
738 static double
739 cell_starttime,
740 cell_endtime;
741 static char
742 menulang[3];
743
set_video_attr(int attr,const char * s)744 static void set_video_attr(int attr,const char *s)
745 {
746 if (ismenuf != VTYPE_VTS)
747 menugroup_set_video_attr(mg, attr, s);
748 else
749 pgcgroup_set_video_attr(titles, attr, s);
750 }
751
set_audio_attr(int attr,const char * s,int ch)752 static void set_audio_attr(int attr,const char *s,int ch)
753 {
754 if (ismenuf != VTYPE_VTS)
755 menugroup_set_audio_attr(mg,attr,s,ch);
756 else
757 pgcgroup_set_audio_attr(titles,attr,s,ch);
758 }
759
set_subpic_attr(int attr,const char * s,int ch)760 static void set_subpic_attr(int attr,const char *s,int ch)
761 {
762 if (ismenuf != VTYPE_VTS)
763 menugroup_set_subpic_attr(mg,attr,s,ch);
764 else
765 pgcgroup_set_subpic_attr(titles,attr,s,ch);
766 }
767
set_subpic_stream(int ch,const char * m,int id)768 static void set_subpic_stream(int ch,const char *m,int id)
769 {
770 if (ismenuf != VTYPE_VTS)
771 menugroup_set_subpic_stream(mg,ch,m,id);
772 else
773 pgcgroup_set_subpic_stream(titles,ch,m,id);
774 }
775
parse_pause(const char * f)776 static int parse_pause(const char *f)
777 {
778 if (!strcmp(f,"inf"))
779 return 255;
780 else
781 return strtounsigned(f, "pause time"); /* should check it's in [0 .. 254] */
782 }
783
dvdauthor_outputdir(const char * s)784 static void dvdauthor_outputdir(const char *s)
785 {
786 if (!fbase)
787 {
788 fbase = localize_filename(s);
789 } /*if*/
790 }
791
dvdauthor_jumppad(const char * s)792 static void dvdauthor_jumppad(const char *s)
793 {
794 if(xml_ison(s, "jumppad"))
795 dvdauthor_enable_jumppad();
796 }
797
dvdauthor_allgprm(const char * s)798 static void dvdauthor_allgprm(const char *s)
799 {
800 if (xml_ison(s, "allgprm"))
801 dvdauthor_enable_allgprm();
802 }
803
dvdauthor_video_format(const char * s)804 static void dvdauthor_video_format
805 (
806 const char * s
807 )
808 {
809 if (!strcmp(s, "ntsc") || !strcmp(s, "NTSC"))
810 {
811 default_video_format = VF_NTSC;
812 }
813 else if (!strcmp(s, "pal") || !strcmp(s, "PAL"))
814 {
815 default_video_format = VF_PAL;
816 }
817 else
818 {
819 fprintf(stderr, "ERR: Unrecognized video format \"%s\"\n", s);
820 parser_err = true;
821 } /*if*/
822 } /*dvdauthor_video_format*/
823
dvdauthor_provider(const char * s)824 static void dvdauthor_provider
825 (
826 const char * s
827 )
828 {
829 strncpy(provider_str, s, PROVIDER_SIZE);
830 provider_str[PROVIDER_SIZE - 1] = 0;
831 } /*dvdauthor_provider*/
832
getfbase()833 static void getfbase()
834 {
835 if (!writeoutput)
836 fbase = 0;
837 else if (!fbase)
838 {
839 fprintf(stderr, "ERR: Must specify working directory\n");
840 parser_err = true;
841 } /*if*/
842 } /*getfbase*/
843
dvdauthor_start(void)844 static void dvdauthor_start(void)
845 {
846 strncpy(provider_str, PACKAGE_STRING, PROVIDER_SIZE);
847 provider_str[PROVIDER_SIZE - 1] = 0;
848 } /*dvdauthor_start*/
849
dvdauthor_end(void)850 static void dvdauthor_end(void)
851 /* called on </dvdauthor> end tag, generates the VMGM if specified.
852 This needs to be done after all the titles, so it can include
853 information about them. */
854 {
855 if (hadtoc)
856 {
857 dvdauthor_vmgm_gen(fpc, vmgmmenus, fbase);
858 pgc_free(fpc);
859 menugroup_free(vmgmmenus);
860 fpc = 0;
861 vmgmmenus = 0;
862 } /*if*/
863 } /*dvdauthor_end*/
864
titleset_start()865 static void titleset_start()
866 {
867 mg=menugroup_new();
868 istoc = false;
869 }
870
titleset_end()871 static void titleset_end()
872 { /* called on </titles> end tag, generates the output titleset. */
873 getfbase();
874 if (!parser_err)
875 {
876 if (!titles)
877 titles = pgcgroup_new(VTYPE_VTS);
878 dvdauthor_vts_gen(mg, titles, fbase);
879 menugroup_free(mg);
880 pgcgroup_free(titles);
881 mg = 0;
882 titles = 0;
883 } /*if*/
884 } /*titleset_end*/
885
vmgm_start()886 static void vmgm_start()
887 {
888 if (hadtoc) {
889 fprintf(stderr,"ERR: Can only define one VMGM\n");
890 parser_err = true;
891 return;
892 }
893 mg=menugroup_new();
894 istoc = true;
895 hadtoc = true;
896 }
897
vmgm_end()898 static void vmgm_end()
899 {
900 getfbase();
901 // we put off compilation of vmgm until after the titlesets
902 vmgmmenus=mg;
903 mg=0;
904 }
905
fpc_start()906 static void fpc_start()
907 {
908 if( !istoc ) {
909 fprintf(stderr,"ERR: Can only specify <fpc> under <vmgm>\n");
910 parser_err = true;
911 return;
912 }
913 if( fpc ) {
914 fprintf(stderr,"ERR: Already defined <fpc>\n");
915 parser_err = true;
916 return;
917 }
918 fpc=pgc_new();
919 parser_acceptbody = true;
920 }
921
fpc_end()922 static void fpc_end()
923 {
924 pgc_set_pre(fpc,parser_body);
925 }
926
pgcgroup_start()927 static void pgcgroup_start()
928 /* common part of both <menus> and <titles> start. */
929 {
930 setvideo = 0;
931 setaudio = -1;
932 setsubpicture = -1;
933 subpmode = DA_PGCGROUP;
934 } /*pgcgroup_start*/
935
titles_start()936 static void titles_start()
937 /* called on a <titles> tag. */
938 {
939 if (titles)
940 {
941 fprintf(stderr, "ERR: Titles already defined\n");
942 parser_err = true;
943 }
944 else if (istoc)
945 {
946 fprintf(stderr, "ERR: Cannot have titles in a VMGM\n");
947 parser_err = true;
948 }
949 else
950 {
951 titles = pgcgroup_new(VTYPE_VTS);
952 curgroup = titles;
953 ismenuf = VTYPE_VTS;
954 pgcgroup_start();
955 } /*if*/
956 }
957
menus_start()958 static void menus_start()
959 /* called on a <menus> tag. */
960 {
961 ismenuf = (istoc ? VTYPE_VMGM : VTYPE_VTSM);
962 curgroup = pgcgroup_new(ismenuf);
963 pgcgroup_start();
964 strcpy(menulang, "en"); /* default. */
965 /* fixme: would be nice to make this a preference setting, perhaps default to locale? */
966 /* fixme: no check for buffer overflow! */
967 }
968
menus_lang(const char * lang)969 static void menus_lang(const char *lang)
970 {
971 strcpy(menulang, lang);
972 /* fixme: no check for buffer overflow! */
973 } /*menus_lang*/
974
menus_end()975 static void menus_end()
976 {
977 menugroup_add_pgcgroup(mg, menulang, curgroup);
978 curgroup = 0;
979 } /*menus_end*/
980
video_start()981 static void video_start()
982 /* called on a <video> tag. */
983 {
984 if (setvideo)
985 {
986 fprintf(stderr, "ERR: Already defined video characteristics for this PGC group\n");
987 /* only one video stream allowed */
988 parser_err = true;
989 }
990 else
991 setvideo = 1;
992 }
993
video_format(const char * c)994 static void video_format(const char *c)
995 {
996 set_video_attr(VIDEO_FORMAT, c);
997 }
998
video_aspect(const char * c)999 static void video_aspect(const char *c)
1000 {
1001 set_video_attr(VIDEO_ASPECT, c);
1002 }
1003
video_resolution(const char * c)1004 static void video_resolution(const char *c)
1005 {
1006 set_video_attr(VIDEO_RESOLUTION, c);
1007 }
1008
video_widescreen(const char * c)1009 static void video_widescreen(const char *c)
1010 {
1011 set_video_attr(VIDEO_WIDESCREEN, c);
1012 }
1013
video_caption(const char * c)1014 static void video_caption(const char *c)
1015 {
1016 set_video_attr(VIDEO_CAPTION, c);
1017 }
1018
audio_start()1019 static void audio_start()
1020 /* called on an <audio> tag. */
1021 {
1022 setaudio++;
1023 if (setaudio >= 8)
1024 {
1025 fprintf(stderr, "ERR: Attempting to define too many audio streams for this PGC group\n");
1026 parser_err = true;
1027 } /*if*/
1028 }
1029
audio_format(const char * c)1030 static void audio_format(const char *c)
1031 {
1032 set_audio_attr(AUDIO_FORMAT, c, setaudio);
1033 }
1034
audio_quant(const char * c)1035 static void audio_quant(const char *c)
1036 {
1037 set_audio_attr(AUDIO_QUANT, c, setaudio);
1038 }
1039
audio_dolby(const char * c)1040 static void audio_dolby(const char *c)
1041 {
1042 set_audio_attr(AUDIO_DOLBY, c, setaudio);
1043 }
1044
audio_lang(const char * c)1045 static void audio_lang(const char *c)
1046 {
1047 set_audio_attr(AUDIO_LANG, c, setaudio);
1048 }
1049
audio_samplerate(const char * c)1050 static void audio_samplerate(const char *c)
1051 {
1052 set_audio_attr(AUDIO_SAMPLERATE, c, setaudio);
1053 }
1054
audio_content(const char * c)1055 static void audio_content(const char *c)
1056 {
1057 set_audio_attr(AUDIO_CONTENT, c, setaudio);
1058 } /*audio_content*/
1059
audio_channels(const char * c)1060 static void audio_channels(const char *c)
1061 {
1062 char ch[4];
1063 if (strlen(c) == 1)
1064 {
1065 ch[0] = c[0];
1066 ch[1] = 'c';
1067 ch[2] = 'h';
1068 ch[3] = 0;
1069 c = ch;
1070 } /*if*/
1071 set_audio_attr(AUDIO_CHANNELS, c, setaudio);
1072 }
1073
subattr_group_start()1074 static void subattr_group_start()
1075 /* called for a <subpicture> tag immediately within a pgcgroup (<menus> or <titles>). */
1076 {
1077 if (subpmode != DA_PGCGROUP)
1078 {
1079 fprintf(stderr, "ERR: Define all the subpictures before defining PGCs\n");
1080 parser_err = true;
1081 return;
1082 } /*if*/
1083 setsubpicture++;
1084 if (setsubpicture >= 32)
1085 {
1086 fprintf(stderr, "ERR: Attempting to define too many subpicture streams for this PGC group\n");
1087 parser_err = true;
1088 } /*if*/
1089 } /*subattr_group_start*/
1090
subattr_pgc_start()1091 static void subattr_pgc_start()
1092 /* called for a <subpicture> tag immediately within a <pgc>. */
1093 {
1094 setsubpicture++;
1095 if (setsubpicture >= 32)
1096 {
1097 fprintf(stderr, "ERR: Attempting to define too many subpicture streams for this PGC group\n");
1098 parser_err = true;
1099 } /*if*/
1100 } /*subattr_pgc_start*/
1101
subattr_lang(const char * c)1102 static void subattr_lang(const char *c)
1103 {
1104 if (subpmode == DA_PGCGROUP)
1105 set_subpic_attr(SPU_LANG, c, setsubpicture);
1106 else
1107 {
1108 fprintf
1109 (
1110 stderr,
1111 "ERR: Cannot set subpicture language within a pgc; do it within titles or menus\n"
1112 );
1113 parser_err = true;
1114 } /*if*/
1115 } /*subattr_lang*/
1116
subattr_content(const char * c)1117 static void subattr_content(const char * c)
1118 {
1119 if (subpmode == DA_PGCGROUP)
1120 {
1121 set_subpic_attr(SPU_CONTENT, c, setsubpicture);
1122 }
1123 else
1124 {
1125 fprintf
1126 (
1127 stderr,
1128 "ERR: Cannot set subpicture content type within a pgc; do it within titles or menus\n"
1129 );
1130 parser_err = true;
1131 } /*if*/
1132 } /*subattr_content*/
1133
stream_start()1134 static void stream_start()
1135 /* called on a <stream> tag. */
1136 {
1137 subpstreamid = -1;
1138 subpstreammode = 0;
1139 } /*stream_start*/
1140
substream_id(const char * c)1141 static void substream_id(const char *c)
1142 /* saves the id attribute for the current <stream> tag. */
1143 {
1144 subpstreamid = strtounsigned(c, "subpicture stream id");
1145 if (subpstreamid < 0 || subpstreamid >= 32)
1146 {
1147 fprintf(stderr, "ERR: Subpicture stream id must be 0-31: '%s'.\n", c);
1148 parser_err = true;
1149 } /*if*/
1150 } /*substream_id*/
1151
substream_mode(const char * c)1152 static void substream_mode(const char *c)
1153 /* saves the mode attribute for the current <stream> tag. */
1154 {
1155 subpstreammode = strdup(c); /* won't leak, because I won't be called more than once */
1156 }
1157
stream_end()1158 static void stream_end()
1159 {
1160 if (subpstreamid == -1 || !subpstreammode)
1161 {
1162 fprintf(stderr, "ERR: Must define the mode and id for the stream.\n");
1163 parser_err = true;
1164 }
1165 else if (subpmode == DA_PGCGROUP)
1166 {
1167 set_subpic_stream(setsubpicture, subpstreammode, subpstreamid);
1168 }
1169 else
1170 pgc_set_subpic_stream(curpgc, setsubpicture, subpstreammode, subpstreamid);
1171 free(subpstreammode);
1172 } /*stream_end*/
1173
pgc_start()1174 static void pgc_start()
1175 {
1176 curpgc = pgc_new();
1177 hadchapter = chapters_neither; /* reset for new menu/title */
1178 setsubpicture = -1;
1179 subpmode = DA_PGC;
1180 }
1181
pgc_entry(const char * e)1182 static void pgc_entry(const char *e)
1183 {
1184 // xml attributes can only be defined once, so entry="foo" entry="bar" won't work
1185 // instead, use parseentries...
1186 parseentries(curpgc, ismenuf, e);
1187 }
1188
pgc_palette(const char * p)1189 static void pgc_palette(const char *p)
1190 {
1191 readpalette(curpgc,p);
1192 }
1193
pgc_pause(const char * c)1194 static void pgc_pause(const char *c)
1195 {
1196 pgc_set_stilltime(curpgc,parse_pause(c));
1197 }
1198
pgc_end()1199 static void pgc_end()
1200 {
1201 pgcgroup_add_pgc(curgroup,curpgc);
1202 curpgc=0;
1203 subpmode=DA_NOSUB;
1204 }
1205
pre_start()1206 static void pre_start()
1207 {
1208 parser_acceptbody = true;
1209 }
1210
pre_end()1211 static void pre_end()
1212 {
1213 pgc_set_pre(curpgc,parser_body);
1214 }
1215
post_start()1216 static void post_start()
1217 {
1218 parser_acceptbody = true;
1219 }
1220
post_end()1221 static void post_end()
1222 {
1223 pgc_set_post(curpgc,parser_body);
1224 }
1225
vob_start()1226 static void vob_start()
1227 {
1228 curvob = source_new();
1229 pauselen = 0;
1230 vobbasic = vob_has_neither; /* to begin with */
1231 /* but note hadchapter keeps its value from previous VOB in this PGC
1232 if no chapters attribute or cells are seen */
1233 cell_endtime = 0;
1234 }
1235
vob_file(const char * f)1236 static void vob_file(const char *f)
1237 {
1238 f = localize_filename(f);
1239 source_set_filename(curvob, f);
1240 free(f);
1241 }
1242
vob_chapters(const char * c)1243 static void vob_chapters(const char *c)
1244 {
1245 vobbasic = vob_has_chapters_pause;
1246 hadchapter = chapters_chapters;
1247 cur_chapters = strdup(c); /* won't leak, because I won't be called more than once per <vob> */
1248 }
1249
vob_pause(const char * c)1250 static void vob_pause(const char *c)
1251 {
1252 vobbasic = vob_has_chapters_pause;
1253 pauselen = parse_pause(c);
1254 }
1255
vob_end()1256 static void vob_end()
1257 {
1258 if (vobbasic != vob_has_cells) /* = vob_has_chapters_pause or vob_has_neither */
1259 {
1260 if (hadchapter == chapters_chapters) /* vob has chapters attribute */
1261 {
1262 parsechapters(cur_chapters, curvob, pauselen);
1263 hadchapter = chapters_cells;
1264 /* subsequent <vob>s in this PGC shall be only cells, unless
1265 they have their own "chapter" attribute */
1266 }
1267 else /* hadchapter = chapters_neither or chapters_cells */
1268 source_add_cell
1269 (
1270 /*source =*/ curvob,
1271 /*starttime =*/ 0,
1272 /*endtime =*/ -1,
1273 /*chap =*/
1274 hadchapter == chapters_neither ?
1275 CELL_CHAPTER_PROGRAM /* first <vob> in <pgc> */
1276 :
1277 CELL_NEITHER, /* subsequent <vob>s in <pgc> */
1278 /*pause =*/ pauselen,
1279 /*cmd =*/ 0
1280 );
1281 /* default to single chapter/cell for entire source file */
1282 } /*if*/
1283 pgc_add_source(curpgc, curvob);
1284 free(cur_chapters);
1285 cur_chapters = 0;
1286 curvob = 0;
1287 } /*vob_end*/
1288
cell_start()1289 static void cell_start()
1290 {
1291 parser_acceptbody = true; /* collect cell commands */
1292 assert(vobbasic != vob_has_chapters_pause);
1293 /* no "chapters" or "pause" attribute on containing <vob> allowed */
1294 vobbasic = vob_has_cells;
1295 cell_starttime = cell_endtime; /* new cell starts by default where previous one ends */
1296 cell_endtime = -1;
1297 cell_chapter = CELL_NEITHER; /* to begin with */
1298 pauselen = 0;
1299 hadchapter = chapters_cells;
1300 }
1301
cell_parsestart(const char * f)1302 static void cell_parsestart(const char *f)
1303 {
1304 if (f[0] == 0)
1305 {
1306 fprintf(stderr,"ERR: Empty cell start time\n");
1307 exit(1);
1308 } /*if*/
1309 cell_starttime = parsechapter(f);
1310 }
1311
cell_parseend(const char * f)1312 static void cell_parseend(const char *f)
1313 {
1314 if (f[0] == 0)
1315 {
1316 fprintf(stderr,"ERR: Empty cell end time\n");
1317 exit(1);
1318 } /*if*/
1319 cell_endtime = parsechapter(f);
1320 }
1321
cell_parsechapter(const char * f)1322 static void cell_parsechapter(const char *f)
1323 {
1324 if (xml_ison(f, "chapter"))
1325 cell_chapter = CELL_CHAPTER_PROGRAM;
1326 }
1327
cell_parseprogram(const char * f)1328 static void cell_parseprogram(const char *f)
1329 {
1330 if (xml_ison(f, "program") && cell_chapter != CELL_CHAPTER_PROGRAM)
1331 cell_chapter = CELL_PROGRAM;
1332 }
1333
cell_pauselen(const char * f)1334 static void cell_pauselen(const char *f)
1335 {
1336 pauselen=parse_pause(f);
1337 }
1338
cell_end()1339 static void cell_end()
1340 {
1341 assert (cell_starttime >= 0);
1342 source_add_cell(curvob, cell_starttime, cell_endtime, cell_chapter, pauselen, parser_body);
1343 pauselen = 0;
1344 }
1345
button_start()1346 static void button_start()
1347 {
1348 parser_acceptbody = true;
1349 buttonname=0;
1350 }
1351
button_name(const char * f)1352 static void button_name(const char *f)
1353 {
1354 buttonname=strdup(f);
1355 }
1356
button_end()1357 static void button_end()
1358 {
1359 pgc_add_button(curpgc,buttonname,parser_body);
1360 if(buttonname) free((char *)buttonname);
1361 }
1362
1363 static struct elemdesc elems[]={
1364 {"dvdauthor", DA_BEGIN, DA_ROOT, dvdauthor_start, dvdauthor_end},
1365 {"titleset", DA_ROOT, DA_SET, titleset_start, titleset_end},
1366 {"vmgm", DA_ROOT, DA_SET, vmgm_start, vmgm_end},
1367 {"fpc", DA_SET, DA_NOSUB, fpc_start, fpc_end},
1368 {"titles", DA_SET, DA_PGCGROUP,titles_start, 0},
1369 {"menus", DA_SET, DA_PGCGROUP,menus_start, menus_end},
1370 {"video", DA_PGCGROUP,DA_NOSUB, video_start, 0},
1371 {"audio", DA_PGCGROUP,DA_NOSUB, audio_start, 0},
1372 {"subpicture",DA_PGCGROUP,DA_SUBP, subattr_group_start, 0},
1373 {"pgc", DA_PGCGROUP,DA_PGC, pgc_start, pgc_end},
1374 {"pre", DA_PGC, DA_NOSUB, pre_start, pre_end},
1375 {"post", DA_PGC, DA_NOSUB, post_start, post_end},
1376 {"button", DA_PGC, DA_NOSUB, button_start, button_end},
1377 {"vob", DA_PGC, DA_VOB, vob_start, vob_end},
1378 {"subpicture",DA_PGC, DA_SUBP, subattr_pgc_start, 0},
1379 {"cell", DA_VOB, DA_NOSUB, cell_start, cell_end},
1380 {"stream", DA_SUBP, DA_NOSUB, stream_start, stream_end},
1381 {0,0,0,0,0}
1382 };
1383
1384 static struct elemattr attrs[]={
1385 {"dvdauthor","dest",dvdauthor_outputdir},
1386 {"dvdauthor","jumppad",dvdauthor_jumppad},
1387 {"dvdauthor","allgprm",dvdauthor_allgprm},
1388 {"dvdauthor","format",dvdauthor_video_format},
1389 {"dvdauthor","provider",dvdauthor_provider},
1390
1391 {"menus","lang",menus_lang},
1392
1393 {"vob","file",vob_file},
1394 {"vob","chapters",vob_chapters},
1395 {"vob","pause",vob_pause},
1396
1397 {"cell","start",cell_parsestart},
1398 {"cell","end",cell_parseend},
1399 {"cell","chapter",cell_parsechapter},
1400 {"cell","program",cell_parseprogram},
1401 {"cell","pause",cell_pauselen},
1402
1403 {"button","name",button_name},
1404
1405 {"video","format",video_format},
1406 {"video","aspect",video_aspect},
1407 {"video","resolution",video_resolution},
1408 {"video","widescreen",video_widescreen},
1409 {"video","caption",video_caption},
1410
1411 {"audio","format",audio_format},
1412 {"audio","quant",audio_quant},
1413 {"audio","dolby",audio_dolby},
1414 {"audio","lang",audio_lang},
1415 {"audio","channels",audio_channels},
1416 {"audio","samplerate",audio_samplerate},
1417 {"audio","content",audio_content},
1418
1419 {"subpicture","lang",subattr_lang},
1420 {"subpicture","content",subattr_content},
1421 {"stream","mode",substream_mode},
1422 {"stream","id",substream_id},
1423
1424 {"pgc","entry",pgc_entry},
1425 {"pgc","palette",pgc_palette},
1426 {"pgc","pause",pgc_pause},
1427 {0,0,0}
1428 };
1429
readdvdauthorxml(const char * xmlfile,const char * fb)1430 static int readdvdauthorxml(const char *xmlfile, const char *fb)
1431 {
1432 fbase = fb;
1433 if (!fbase)
1434 {
1435 fbase = get_outputdir();
1436 } /*if*/
1437 return readxml(xmlfile, elems, attrs);
1438 }
1439