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