1 /*
2  *  lsdvd.c
3  *
4  *  DVD info lister
5  *
6  *  Copyright (C) 2003  EFF
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 version 2 as
10  *  published by the Free Software Foundation;
11  *
12  *  2003	by Chris Phillips
13  *  2003-04-19  Cleanups get_title_name, added dvdtime2msec, added helper macros,
14  *			  output info structures in form of a Perl module, by Henk Vergonet.
15  */
16 #include <stdint.h>
17 #include <string.h>
18 #include <sys/stat.h>
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <unistd.h>
22 #include <getopt.h>
23 #include <dvdread/ifo_read.h>
24 #include "lsdvd.h"
25 #include "ocode.h"
26 
27 static struct { char code[3];	char name[20];}
28 language[] = {
29 	{ "  ", "Not Specified" }, { "aa", "Afar" },	{ "ab", "Abkhazian" }, { "af", "Afrikaans" },	{ "am", "Amharic" },
30 	{ "ar", "Arabic" }, { "as", "Assamese" },	{ "ay", "Aymara" }, { "az", "Azerbaijani" }, { "ba", "Bashkir" },
31 	{ "be", "Byelorussian" }, { "bg", "Bulgarian" }, { "bh", "Bihari" }, { "bi", "Bislama" }, { "bn", "Bengali; Bangla" },
32 	{ "bo", "Tibetan" }, { "br", "Breton" }, { "ca", "Catalan" }, { "co", "Corsican" }, { "cs", "Czech" },
33 	{ "cy", "Welsh" }, { "da", "Dansk" }, { "de", "Deutsch" }, { "dz", "Bhutani" }, { "el", "Greek" }, { "en", "English" },
34 	{ "eo", "Esperanto" }, { "es", "Espanol" }, { "et", "Estonian" }, { "eu", "Basque" }, { "fa", "Persian" },
35 	{ "fi", "Suomi" }, { "fj", "Fiji" }, { "fo", "Faroese" }, { "fr", "Francais" }, { "fy", "Frisian" }, { "ga", "Gaelic" },
36 	{ "gd", "Scots Gaelic" }, { "gl", "Galician" }, { "gn", "Guarani" }, { "gu", "Gujarati" }, { "ha", "Hausa" },
37 	{ "he", "Hebrew" }, { "hi", "Hindi" }, { "hr", "Hrvatski" }, { "hu", "Magyar" }, { "hy", "Armenian" },
38 	{ "ia", "Interlingua" }, { "id", "Indonesian" }, { "ie", "Interlingue" }, { "ik", "Inupiak" }, { "in", "Indonesian" },
39 	{ "is", "Islenska" }, { "it", "Italiano" }, { "iu", "Inuktitut" }, { "iw", "Hebrew" }, { "ja", "Japanese" },
40 	{ "ji", "Yiddish" }, { "jw", "Javanese" }, { "ka", "Georgian" }, { "kk", "Kazakh" }, { "kl", "Greenlandic" },
41 	{ "km", "Cambodian" }, { "kn", "Kannada" }, { "ko", "Korean" }, { "ks", "Kashmiri" }, { "ku", "Kurdish" },
42 	{ "ky", "Kirghiz" }, { "la", "Latin" }, { "ln", "Lingala" }, { "lo", "Laothian" }, { "lt", "Lithuanian" },
43 	{ "lv", "Latvian, Lettish" }, { "mg", "Malagasy" }, { "mi", "Maori" }, { "mk", "Macedonian" }, { "ml", "Malayalam" },
44 	{ "mn", "Mongolian" }, { "mo", "Moldavian" }, { "mr", "Marathi" }, { "ms", "Malay" }, { "mt", "Maltese" },
45 	{ "my", "Burmese" }, { "na", "Nauru" }, { "ne", "Nepali" }, { "nl", "Nederlands" }, { "no", "Norsk" }, { "oc", "Occitan" },
46 	{ "om", "Oromo" }, { "or", "Oriya" }, { "pa", "Punjabi" }, { "pl", "Polish" }, { "ps", "Pashto, Pushto" },
47 	{ "pt", "Portugues" }, { "qu", "Quechua" }, { "rm", "Rhaeto-Romance" }, { "rn", "Kirundi" }, { "ro", "Romanian"  },
48 	{ "ru", "Russian" }, { "rw", "Kinyarwanda" }, { "sa", "Sanskrit" }, { "sd", "Sindhi" }, { "sg", "Sangho" },
49 	{ "sh", "Serbo-Croatian" }, { "si", "Sinhalese" }, { "sk", "Slovak" }, { "sl", "Slovenian" }, { "sm", "Samoan" },
50  	{ "sn", "Shona"  }, { "so", "Somali" }, { "sq", "Albanian" }, { "sr", "Serbian" }, { "ss", "Siswati" },
51 	{ "st", "Sesotho" }, { "su", "Sundanese" }, { "sv", "Svenska" }, { "sw", "Swahili" }, { "ta", "Tamil" },
52 	{ "te", "Telugu" }, { "tg", "Tajik" }, { "th", "Thai" }, { "ti", "Tigrinya" }, { "tk", "Turkmen" }, { "tl", "Tagalog" },
53 	{ "tn", "Setswana" }, { "to", "Tonga" }, { "tr", "Turkish" }, { "ts", "Tsonga" }, { "tt", "Tatar" }, { "tw", "Twi" },
54 	{ "ug", "Uighur" }, { "uk", "Ukrainian" }, { "ur", "Urdu" }, { "uz", "Uzbek" }, { "vi", "Vietnamese" },
55 	{ "vo", "Volapuk" }, { "wo", "Wolof" }, { "xh", "Xhosa" }, { "yi", "Yiddish" }, { "yo", "Yoruba" }, { "za", "Zhuang" },
56 	{ "zh", "Chinese" }, { "zu", "Zulu" }, { "xx", "Unknown" }, { "\0", "Unknown" } };
57 char *video_format[2] = {"NTSC", "PAL"};
58 /* 28.9.2003: Chicken run's aspect ratio is 16:9 or 1.85:1, at index
59    1.  Addionaly using ' in the quoting makes the perl output not
60    parse so changed to " */
61 char *aspect_ratio[4] = {"4/3", "16/9", "\"?:?\"", "16/9"};
62 char *quantization[4] = {"16bit", "20bit", "24bit", "drc"};
63 char *mpeg_version[2] = {"mpeg1", "mpeg2"};
64 /* 28.9.2003: The European chicken run title has height index 3, and
65    576 lines seems right, mplayer -vop cropdetect shows from 552 to
66    576 lines.  What the correct value is for index 2 is harder to say */
67 char *video_height[4] = {"480", "576", "???", "576"};
68 char *video_width[4]  = {"720", "704", "352", "352"};
69 char *permitted_df[4] = {"P&S + Letter", "Pan&Scan", "Letterbox", "?"};
70 char *audio_format[7] = {"ac3", "?", "mpeg1", "mpeg2", "lpcm ", "sdds ", "dts"};
71 int   audio_id[7]     = {0x80, 0, 0xC0, 0xC0, 0xA0, 0, 0x88};
72 /* 28.9.2003: Chicken run again, it has frequency index of 1.
73    According to dvd::rip the frequency is 48000 */
74 char *sample_freq[2]  = {"48000", "48000"};
75 char *audio_type[5]   = {"Undefined", "Normal", "Impaired", "Comments1", "Comments2"};
76 char *subp_type[16]   = {"Undefined", "Normal", "Large", "Children", "reserved", "Normal_CC", "Large_CC", "Children_CC",
77 	"reserved", "Forced", "reserved", "reserved", "reserved", "Director", "Large_Director", "Children_Director"};
78 double frames_per_s[4] = {-1.0, 25.00, -1.0, 29.97};
79 
80 char* program_name;
81 
82 #if 0
83 extern void operl_print(struct dvd_info *dvd_info);
84 extern void oxml_print(struct dvd_info *dvd_info);
85 extern void oruby_print(struct dvd_info *dvd_info);
86 extern void ohuman_print(struct dvd_info *dvd_info);
87 #endif
88 
dvdtime2msec(dvd_time_t * dt)89 static int dvdtime2msec(dvd_time_t *dt)
90 {
91 	double fps = frames_per_s[(dt->frame_u & 0xc0) >> 6];
92 	long   ms;
93 	ms  = (((dt->hour &   0xf0) >> 3) * 5 + (dt->hour   & 0x0f)) * 3600000;
94 	ms += (((dt->minute & 0xf0) >> 3) * 5 + (dt->minute & 0x0f)) * 60000;
95 	ms += (((dt->second & 0xf0) >> 3) * 5 + (dt->second & 0x0f)) * 1000;
96 
97 	if(fps > 0)
98 	ms += (((dt->frame_u & 0x30) >> 3) * 5 + (dt->frame_u & 0x0f)) * 1000.0 / fps;
99 
100 	return ms;
101 }
102 
103 /*
104  * This is used to add up sets of times in the struct. it's not elegant at all
105  * but a quick way to easily add up 4 times at once. tracking the times in usec's
106  * constantly is easier, but without using math.h, it sucks to actually DO anything with it
107  * also it ***has*** to be better to return the playback_time, not just mess with it like this
108  */
converttime(playback_time_t * pt,dvd_time_t * dt)109 static void converttime(playback_time_t *pt, dvd_time_t *dt)
110 {
111 	double fps = frames_per_s[(dt->frame_u & 0xc0) >> 6];
112 
113 	pt->usec = pt->usec + (((dt->frame_u & 0x30) >> 3) * 5 + (dt->frame_u & 0x0f)) * 1000.0 / fps;
114 	pt->second = pt->second + ((dt->second & 0xf0) >> 3) * 5 + (dt->second & 0x0f);
115 	pt->minute = pt->minute + ((dt->minute & 0xf0) >> 3) * 5 + (dt->minute & 0x0f);
116 	pt->hour = pt->hour + ((dt->hour &   0xf0) >> 3) * 5 + (dt->hour   & 0x0f);
117 
118 	if ( pt->usec >= 1000 ) { pt->usec -= 1000; pt->second++; }
119 	if ( pt->second >= 60 ) { pt->second -= 60; pt->minute++; }
120 	if ( pt->minute > 59 ) { pt->minute -= 60; pt->hour++; }
121 }
122 
123 /*
124  *  The following method is based on code from vobcopy, by Robos, with thanks.
125  */
get_title_name(const char * dvd_device,char * title)126 static int get_title_name(const char* dvd_device, char* title)
127 {
128 	FILE *filehandle = 0;
129 	int  i;
130 
131 	if (! (filehandle = fopen(dvd_device, "r"))) {
132 		fprintf(stderr, "Couldn't open %s for title\n", dvd_device);
133 		strcpy(title, "unknown");
134 		return -1;
135 	}
136 
137 	if ( fseek(filehandle, 32768, SEEK_SET )) {
138 		fclose(filehandle);
139 		fprintf(stderr, "Couldn't seek in %s for title\n", dvd_device);
140 		strcpy(title, "unknown");
141 		return -1;
142 	}
143 
144 	{
145 	#define DVD_SEC_SIZ 2048
146 	char tempBuf[ DVD_SEC_SIZ ];
147 
148 	if ( DVD_SEC_SIZ != fread(tempBuf, 1, DVD_SEC_SIZ, filehandle) ) {
149 		fclose(filehandle);
150 		fprintf(stderr, "Couldn't read enough bytes for title.\n");
151 		strcpy(title, "unknown");
152 		return -1;
153 	}
154 	snprintf( title, 32, "%s", tempBuf + 40 );
155 	i=32;
156 	}
157 
158 	fclose (filehandle);
159 
160 	title[32] = '\0';
161 	while(i-- > 2)
162 		if(title[i] == ' ') title[i] = '\0';
163 	return 0;
164 }
165 
lang_name(char * code)166 static char* lang_name(char* code)
167 {
168 	int k=0;
169 	while (memcmp(language[k].code, code, 2) && language[k].name[0] ) { k++; }
170 	return language[k].name;
171 }
172 
version(void)173 static void version(void)
174 {
175 	fprintf(stderr, "lsdvd "VERSION" - GPL Copyright (c) 2002-2005, 2014 \"Written\" by Chris Phillips <acid_kewpie@users.sf.net>\n");
176 }
177 
usage(void)178 static void usage(void)
179 {
180 	version();
181 	fprintf(stderr, "Usage: %s [options] [-t track_number] [dvd path] \n", program_name);
182 	fprintf(stderr, "\n");
183 	fprintf(stderr, "Options:\n");
184 	fprintf(stderr, "\tExtra information:\n");
185 	fprintf(stderr, "\t  -a audio streams\n");
186 	fprintf(stderr, "\t  -d cells\n");
187 	fprintf(stderr, "\t  -n angles\n");
188 	fprintf(stderr, "\t  -c chapters\n");
189 	fprintf(stderr, "\t  -s subpictures\n");
190 	fprintf(stderr, "\t  -P palette\n");
191 	fprintf(stderr, "\t  -v video\n");
192 	fprintf(stderr, "\t  -x all information\n");
193 	fprintf(stderr, "\n");
194 	fprintf(stderr, "\tFormatting:\n");
195 	fprintf(stderr, "\t  -Oh output as human readable (default)\n");
196 	fprintf(stderr, "\t  -Op output as Perl\n");
197 	fprintf(stderr, "\t  -Oy output as Python\n");
198 	fprintf(stderr, "\t  -Or output as Ruby\n");
199 	fprintf(stderr, "\t  -Ox output as XML\n");
200 	/* fprintf(stderr, "\t  -p output as Perl [deprecated]\n"); */
201 	fprintf(stderr, "\n");
202 	fprintf(stderr, "\tOther options:\n");
203 	fprintf(stderr, "\t  -q quiet - no summary totals\n");
204 	fprintf(stderr, "\t  -h this message\n");
205 	fprintf(stderr, "\t  -V version information\n");
206 	fprintf(stderr, "\n");
207 }
208 
209 int opt_a=0, opt_c=0, opt_n=0, opt_p=0, opt_q=0, opt_s=0, opt_t=0, opt_v=0, opt_x=0, opt_d=0, opt_P=0;
210 char opt_O='h';
211 
output_option(char * arg)212 static char output_option(char *arg)
213 {
214 	if (strlen(arg) == 1) {
215 		return arg[0];
216 	} else if (strcmp(arg, "perl") == 0) {
217 		return 'p';
218 	} else if (strcmp(arg, "python") == 0) {
219 		return 'y';
220 	} else if (strcmp(arg, "ruby") == 0) {
221 		return 'r';
222 	} else if (strcmp(arg, "ruby2") == 0) {
223 		return '2';
224 	} else if (strcmp(arg, "xml") == 0) {
225 		return 'x';
226 	} else if (strcmp(arg, "human") == 0) {
227 		return 'h';
228 	} else {
229 		return '\0';
230 	}
231 }
232 
233 
main(int argc,char * argv[])234 int main(int argc, char *argv[])
235 {
236 	char title[33];
237 	dvd_reader_t *dvd;
238 	ifo_handle_t *ifo_zero, **ifo;
239 	pgcit_t *vts_pgcit;
240 	vtsi_mat_t *vtsi_mat;
241 	vmgi_mat_t *vmgi_mat;
242 	audio_attr_t *audio_attr;
243 	video_attr_t *video_attr;
244 	subp_attr_t *subp_attr;
245 	pgc_t *pgc;
246 	int i, j, k, c, titles, cell, vts_ttn, title_set_nr;
247  	char lang_code[3];
248 	char *dvd_device = "/dev/cd0";
249 	int has_title = 0, ret = 0;
250 	int max_length = 0, max_track = 0;
251 	struct stat dvd_stat;
252 
253 	program_name = argv[0];
254 
255 	while ((c = getopt(argc, argv, "acnpPqsdvt:O:xhV?")) != EOF) {
256 		switch (c) {
257 		case 'h':
258 		case '?':	usage();		return 0;
259 		case 'V':	version();		return 0;
260 		case 'a':	opt_a = 1;		break;
261 		case 'd':	opt_d = 1;		break;
262 		case 's':	opt_s = 1;		break;
263 		case 'q':	opt_q = 1;		break;
264 		case 'c':	opt_c = 1;		break;
265 		case 'n':	opt_n = 1;		break;
266 		case 'p':	opt_p = 1;		break;
267 		case 'P':	opt_P = 1;		break;
268 		case 't':	opt_t = atoi(optarg);	break;
269 		case 'O':	opt_O = output_option(optarg);	break;
270 		case 'v':	opt_v = 1;		break;
271 		case 'x':	opt_x = 1;
272 				opt_a = 1;
273 				opt_c = 1;
274 				opt_s = 1;
275 				opt_P = 1;
276 				opt_d = 1;
277 				opt_n = 1;
278 				opt_v = 1;		break;
279 		}
280 	}
281 
282 	if (argv[optind]) { dvd_device = argv[optind]; }
283 
284 	ret = stat(dvd_device, &dvd_stat);
285 	if ( ret < 0 ) {
286 		fprintf(stderr, "Can't find device %s\n", dvd_device);
287 		return 1;
288 	}
289 
290 	dvd = DVDOpen(dvd_device);
291 	if( !dvd ) {
292 		fprintf( stderr, "Can't open disc %s!\n", dvd_device);
293 		return 2;
294 	}
295 	ifo_zero = ifoOpen(dvd, 0);
296 	if ( !ifo_zero ) {
297 		fprintf( stderr, "Can't open main ifo!\n");
298 		return 3;
299 	}
300 
301 	ifo = (ifo_handle_t **)malloc((ifo_zero->vts_atrt->nr_of_vtss + 1) * sizeof(ifo_handle_t *));
302 
303 	for (i=1; i <= ifo_zero->vts_atrt->nr_of_vtss; i++) {
304 		ifo[i] = ifoOpen(dvd, i);
305 		if ( !ifo[i] && opt_t == i ) {
306 			fprintf( stderr, "Can't open ifo %d!\n", i);
307 			return 4;
308 		}
309 	}
310 
311 	titles = ifo_zero->tt_srpt->nr_of_srpts;
312 
313 	if ( opt_t > titles || opt_t < 0) {
314 		fprintf (stderr, "Only %d titles on this disc!", titles);
315 		return 5;
316 	}
317 
318 	has_title = get_title_name(dvd_device, title);
319 
320 	vmgi_mat = ifo_zero->vmgi_mat;
321 
322 	struct dvd_info dvd_info;
323 
324 	dvd_info.discinfo.device = dvd_device;
325 	dvd_info.discinfo.disc_title = has_title ? "unknown" : title;
326 	dvd_info.discinfo.vmg_id =  vmgi_mat->vmg_identifier;
327 	dvd_info.discinfo.provider_id = vmgi_mat->provider_identifier;
328 
329 	dvd_info.title_count = titles;
330 	dvd_info.titles = calloc(titles, sizeof(*dvd_info.titles));
331 
332 	for (j=0; j < titles; j++) {
333 
334 		if ( opt_t == j+1 || opt_t == 0 ) {
335 
336 			/* GENERAL */
337 			if (ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vtsi_mat) {
338 
339 				dvd_info.titles[j].enabled = 1;
340 
341 				vtsi_mat   = ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vtsi_mat;
342 				vts_pgcit  = ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vts_pgcit;
343 				video_attr = &vtsi_mat->vts_video_attr;
344 				vts_ttn = ifo_zero->tt_srpt->title[j].vts_ttn;
345 				vmgi_mat = ifo_zero->vmgi_mat;
346 				title_set_nr = ifo_zero->tt_srpt->title[j].title_set_nr;
347 				pgc = vts_pgcit->pgci_srp[ifo[title_set_nr]->vts_ptt_srpt->title[vts_ttn - 1].ptt[0].pgcn - 1].pgc;
348 				dvd_info.titles[j].general.vts_id = vtsi_mat->vts_identifier;
349 				dvd_info.titles[j].chapter_count_reported = ifo_zero->tt_srpt->title[j].nr_of_ptts;
350 				if(pgc->cell_playback == NULL || pgc->program_map == NULL) {
351 					dvd_info.titles[j].general.length = 0;
352 					dvd_info.titles[j].general.playback_time.hour = 0;
353 					dvd_info.titles[j].general.playback_time.minute = 0;
354 					dvd_info.titles[j].general.playback_time.second = 0;
355 					dvd_info.titles[j].general.playback_time.usec = 0;
356 					dvd_info.titles[j].chapter_count = 0;
357 					dvd_info.titles[j].cell_count = 0;
358 					dvd_info.titles[j].audiostream_count_reported = 0;
359 					dvd_info.titles[j].audiostream_count = 0;
360 					dvd_info.titles[j].subtitle_count_reported = 0;
361 					dvd_info.titles[j].subtitle_count = 0;
362 				} else {
363 					dvd_info.titles[j].general.length = dvdtime2msec(&pgc->playback_time)/1000.0;
364 					converttime(&dvd_info.titles[j].general.playback_time, &pgc->playback_time);
365 					dvd_info.titles[j].chapter_count = pgc->nr_of_programs;
366 					dvd_info.titles[j].cell_count = pgc->nr_of_cells;
367 					dvd_info.titles[j].audiostream_count_reported = vtsi_mat->nr_of_vts_audio_streams;
368 					dvd_info.titles[j].audiostream_count = 0;
369 					for(k = 0; k < dvd_info.titles[j].audiostream_count_reported; k++) {
370 						if((pgc->audio_control[k] & 0x8000) != 0) {
371 							dvd_info.titles[j].audiostream_count++;
372 						}
373 					}
374 					dvd_info.titles[j].subtitle_count_reported = vtsi_mat->nr_of_vts_subp_streams;
375 					dvd_info.titles[j].subtitle_count = 0;
376 					for(k = 0; k < dvd_info.titles[j].subtitle_count_reported; k++) {
377 						if ((pgc->subp_control[k] & 0x80000000) != 0) {
378 							dvd_info.titles[j].subtitle_count++;
379 						}
380 					}
381 					if (dvdtime2msec(&pgc->playback_time) > max_length) {
382 						max_length = dvdtime2msec(&pgc->playback_time);
383 						max_track = j+1;
384 					}
385 				}
386 
387 				if(opt_v) {
388 					dvd_info.titles[j].parameter.vts = ifo_zero->tt_srpt->title[j].title_set_nr;
389 					dvd_info.titles[j].parameter.ttn = ifo_zero->tt_srpt->title[j].vts_ttn;
390 					dvd_info.titles[j].parameter.fps = frames_per_s[(pgc->playback_time.frame_u & 0xc0) >> 6];
391 					dvd_info.titles[j].parameter.format = video_format[video_attr->video_format];
392 					dvd_info.titles[j].parameter.aspect = aspect_ratio[video_attr->display_aspect_ratio];
393 					dvd_info.titles[j].parameter.width = video_width[video_attr->picture_size];
394 					dvd_info.titles[j].parameter.height = video_height[video_attr->video_format];
395 					dvd_info.titles[j].parameter.df = permitted_df[video_attr->permitted_df];
396 
397 				}
398 
399 				/* PALETTE */
400 				if (opt_P) {
401 					const int palsize = 16;
402 					dvd_info.titles[j].palette = malloc(palsize * sizeof(int));
403 					for (i=0; i < palsize; i++) { dvd_info.titles[j].palette[i] = pgc->palette[i]; }
404 				} else {
405 					dvd_info.titles[j].palette = NULL;
406 				}
407 
408 				/* ANGLES */
409 
410 				if (opt_n) {
411 					dvd_info.titles[j].angle_count = ifo_zero->tt_srpt->title[j].nr_of_angles;
412 				} else {
413 					dvd_info.titles[j].angle_count = 0;
414 				}
415 
416 				/* AUDIO */
417 
418 				if (opt_a) {
419 
420 					dvd_info.titles[j].audiostreams = calloc(dvd_info.titles[j].audiostream_count, sizeof(*dvd_info.titles[j].audiostreams));
421 
422 					for (i=0, k=0; i<dvd_info.titles[j].audiostream_count_reported; i++)
423 					{
424 						if((pgc->audio_control[i] & 0x8000) == 0) {
425 							continue;
426 						}
427 						audio_attr = &vtsi_mat->vts_audio_attr[i];
428 						sprintf(lang_code, "%c%c", audio_attr->lang_code>>8, audio_attr->lang_code & 0xff);
429 						if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
430 
431 						dvd_info.titles[j].audiostreams[k].langcode = strdup(lang_code);
432 						dvd_info.titles[j].audiostreams[k].language = lang_name(lang_code);
433 						dvd_info.titles[j].audiostreams[k].format = audio_format[audio_attr->audio_format];
434 						dvd_info.titles[j].audiostreams[k].frequency = sample_freq[audio_attr->sample_frequency];
435 						dvd_info.titles[j].audiostreams[k].quantization = quantization[audio_attr->quantization];
436 						dvd_info.titles[j].audiostreams[k].channels = audio_attr->channels+1;
437 						dvd_info.titles[j].audiostreams[k].ap_mode = audio_attr->application_mode;
438 						dvd_info.titles[j].audiostreams[k].content = audio_type[audio_attr->code_extension];
439 						dvd_info.titles[j].audiostreams[k].streamid = audio_id[audio_attr->audio_format] + i;
440 						k++;
441 					}
442 				} else {
443 					dvd_info.titles[j].audiostreams = NULL;
444 				}
445 
446 				/* CHAPTERS */
447 
448 				cell = 0;
449 				if (opt_c) {
450 					dvd_info.titles[j].chapters = calloc(dvd_info.titles[j].chapter_count, sizeof(*dvd_info.titles[j].chapters));
451 
452 					int ms;
453 					for (i=0; i<dvd_info.titles[j].chapter_count; i++)
454 					{
455 						ms=0;
456 						int next = pgc->program_map[i+1];
457 						if (i == pgc->nr_of_programs - 1) next = pgc->nr_of_cells + 1;
458 
459 						while (cell < next - 1)
460 						{
461 							ms = ms + dvdtime2msec(&pgc->cell_playback[cell].playback_time);
462 							converttime(&dvd_info.titles[j].chapters[i].playback_time, &pgc->cell_playback[cell].playback_time);
463 							cell++;
464 						}
465 						dvd_info.titles[j].chapters[i].startcell = pgc->program_map[i];
466 						dvd_info.titles[j].chapters[i].length = ms * 0.001;
467 
468 
469 					}
470 				}
471 
472 				/* CELLS */
473 
474 
475 				dvd_info.titles[j].cells = calloc(dvd_info.titles[j].cell_count, sizeof(*dvd_info.titles[j].cells));
476 
477 				if (opt_d) {
478 					for (i=0; i<dvd_info.titles[j].cell_count; i++)
479 					{
480 						dvd_info.titles[j].cells[i].length = dvdtime2msec(&pgc->cell_playback[i].playback_time)/1000.0;
481 						converttime(&dvd_info.titles[j].cells[i].playback_time, &pgc->cell_playback[i].playback_time);
482 						/* added to get the start/end sectors */
483 						dvd_info.titles[j].cells[i].first_sector = pgc->cell_playback[i].first_sector;
484 						dvd_info.titles[j].cells[i].last_sector = pgc->cell_playback[i].last_sector;
485 
486 					}
487 				} else {
488 					dvd_info.titles[j].cells = NULL;
489 				}
490 
491 				/* SUBTITLES */
492 
493 				dvd_info.titles[j].subtitles = calloc(dvd_info.titles[j].subtitle_count, sizeof(*dvd_info.titles[j].subtitles));
494 
495 				if (opt_s) {
496 					for (i=0, k=0; i<dvd_info.titles[j].subtitle_count; i++)
497 					{
498 						if ((pgc->subp_control[k] & 0x80000000) == 0)
499 							continue;
500 						subp_attr = &vtsi_mat->vts_subp_attr[i];
501 						sprintf(lang_code, "%c%c", subp_attr->lang_code>>8, subp_attr->lang_code & 0xff);
502 						if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
503 
504 						dvd_info.titles[j].subtitles[k].langcode = strdup(lang_code);
505 						dvd_info.titles[j].subtitles[k].language = lang_name(lang_code);
506 						dvd_info.titles[j].subtitles[k].content = subp_type[subp_attr->code_extension];
507 						dvd_info.titles[j].subtitles[k].streamid = 0x20 + i;
508 						k++;
509 					}
510 				} else {
511 					dvd_info.titles[j].subtitles = NULL;
512 				}
513 
514 			} /* if vtsi_mat */
515 		} /* if not -t */
516 	} /* for each title */
517 
518 	if (! opt_t) { dvd_info.longest_track = max_track; }
519 
520 	if (opt_p) {
521 		ocode_print(&perl_syntax, &dvd_info);
522 	} else {
523 		switch(opt_O) {
524 			case 'p':
525 				ocode_print(&perl_syntax, &dvd_info);
526 				break;
527                         case 'y':
528 			       ocode_print(&python_syntax, &dvd_info);
529 				break;
530 			case 'x':
531 				oxml_print(&dvd_info);
532 				break;
533 			case 'r':
534 				ocode_print(&ruby_syntax, &dvd_info);
535 				break;
536                         case 'd':
537 				ocode_print(&debug_syntax, &dvd_info);
538 				break;
539 			default :
540 				ohuman_print(&dvd_info);
541 				break;
542 		}
543 	}
544 
545 	for (i=1; i <= ifo_zero->vts_atrt->nr_of_vtss; i++) { ifoClose(ifo[i]);	}
546 
547 	ifoClose(ifo_zero);
548 	DVDClose(dvd);
549 
550 	return 0;
551 }
552