/*
 *  lsdvd.c
 *
 *  DVD info lister
 *
 *  Copyright (C) 2003  EFF
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *  2003	by Chris Phillips
 *  2003-04-19  Cleanups get_title_name, added dvdtime2msec, added helper macros,
 *              output info structures in form of a Perl module, by Henk Vergonet.
 *  2004-02-25  now prints the stream-id's for audio and subpicture streams, by Reinhardt Wolf
 */
#include <dvdread/ifo_read.h>
#include <string.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

/*
   Simple helper macros for generating Perl structures
*/
static int _lvl = 0;
static int _lvl_type[256];
#define INDENT  { int i; for(i=0; i<_lvl; i++) printf("  "); }
#define DEF(name, x...) {               \
 INDENT;                                \
 printf("%s => ", name);                \
 printf(x); printf(",\n");              \
}
#define HASH(name) {                                            \
 INDENT;                                                        \
 (name ?							\
   printf((_lvl ? "%s => {\n" : "our %%%s = (\n"), name) :	\
   printf("{\n"));						\
 _lvl++; _lvl_type[_lvl] = 0;                                   \
}
#define ARRAY(name) {					\
 INDENT;						\
 printf(_lvl ? "%s => [\n" : "our @%s = (\n", name);	\
 _lvl_type[_lvl] = 1;	_lvl++;				\
}
#define RETURN  { _lvl--; INDENT;					\
 opt_p ? printf("%s\n", 						\
    _lvl ?   (_lvl_type[_lvl] ? "]," : "},")	:	");") : _lvl++;		\
}
#define START {		\
  HASH("lsdvd");	\
}
#define STOP {		\
  while(_lvl) RETURN;	\
}

static struct { char code[3];	char name[20];}
language[] = {
	{ "  ", "Not Specified" }, { "aa", "Afar" },	{ "ab", "Abkhazian" }, { "af", "Afrikaans" },	{ "am", "Amharic" },
	{ "ar", "Arabic" }, { "as", "Assamese" },	{ "ay", "Aymara" }, { "az", "Azerbaijani" }, { "ba", "Bashkir" },
	{ "be", "Byelorussian" }, { "bg", "Bulgarian" }, { "bh", "Bihari" }, { "bi", "Bislama" }, { "bn", "Bengali; Bangla" },
	{ "bo", "Tibetan" }, { "br", "Breton" }, { "ca", "Catalan" }, { "co", "Corsican" }, { "cs", "Czech" },
	{ "cy", "Welsh" }, { "da", "Dansk" }, { "de", "Deutsch" }, { "dz", "Bhutani" }, { "el", "Greek" }, { "en", "English" },
	{ "eo", "Esperanto" }, { "es", "Espanol" }, { "et", "Estonian" }, { "eu", "Basque" }, { "fa", "Persian" },
	{ "fi", "Suomi" }, { "fj", "Fiji" }, { "fo", "Faroese" }, { "fr", "Francais" }, { "fy", "Frisian" }, { "ga", "Gaelic" },
	{ "gd", "Scots Gaelic" }, { "gl", "Galician" }, { "gn", "Guarani" }, { "gu", "Gujarati" }, { "ha", "Hausa" },
	{ "he", "Hebrew" }, { "hi", "Hindi" }, { "hr", "Hrvatski" }, { "hu", "Magyar" }, { "hy", "Armenian" },
	{ "ia", "Interlingua" }, { "id", "Indonesian" }, { "ie", "Interlingue" }, { "ik", "Inupiak" }, { "in", "Indonesian" },
	{ "is", "Islenska" }, { "it", "Italiano" }, { "iu", "Inuktitut" }, { "iw", "Hebrew" }, { "ja", "Japanese" },
	{ "ji", "Yiddish" }, { "jw", "Javanese" }, { "ka", "Georgian" }, { "kk", "Kazakh" }, { "kl", "Greenlandic" },
	{ "km", "Cambodian" }, { "kn", "Kannada" }, { "ko", "Korean" }, { "ks", "Kashmiri" }, { "ku", "Kurdish" },
	{ "ky", "Kirghiz" }, { "la", "Latin" }, { "ln", "Lingala" }, { "lo", "Laothian" }, { "lt", "Lithuanian" },
	{ "lv", "Latvian, Lettish" }, { "mg", "Malagasy" }, { "mi", "Maori" }, { "mk", "Macedonian" }, { "ml", "Malayalam" },
	{ "mn", "Mongolian" }, { "mo", "Moldavian" }, { "mr", "Marathi" }, { "ms", "Malay" }, { "mt", "Maltese" },
	{ "my", "Burmese" }, { "na", "Nauru" }, { "ne", "Nepali" }, { "nl", "Nederlands" }, { "no", "Norsk" }, { "oc", "Occitan" },
	{ "om", "Oromo" }, { "or", "Oriya" }, { "pa", "Punjabi" }, { "pl", "Polish" }, { "ps", "Pashto, Pushto" },
	{ "pt", "Portugues" }, { "qu", "Quechua" }, { "rm", "Rhaeto-Romance" }, { "rn", "Kirundi" }, { "ro", "Romanian"  },
	{ "ru", "Russian" }, { "rw", "Kinyarwanda" }, { "sa", "Sanskrit" }, { "sd", "Sindhi" }, { "sg", "Sangho" },
	{ "sh", "Serbo-Croatian" }, { "si", "Sinhalese" }, { "sk", "Slovak" }, { "sl", "Slovenian" }, { "sm", "Samoan" },
 	{ "sn", "Shona"  }, { "so", "Somali" }, { "sq", "Albanian" }, { "sr", "Serbian" }, { "ss", "Siswati" },
	{ "st", "Sesotho" }, { "su", "Sundanese" }, { "sv", "Svenska" }, { "sw", "Swahili" }, { "ta", "Tamil" },
	{ "te", "Telugu" }, { "tg", "Tajik" }, { "th", "Thai" }, { "ti", "Tigrinya" }, { "tk", "Turkmen" }, { "tl", "Tagalog" },
	{ "tn", "Setswana" }, { "to", "Tonga" }, { "tr", "Turkish" }, { "ts", "Tsonga" }, { "tt", "Tatar" }, { "tw", "Twi" },
	{ "ug", "Uighur" }, { "uk", "Ukrainian" }, { "ur", "Urdu" }, { "uz", "Uzbek" }, { "vi", "Vietnamese" },
	{ "vo", "Volapuk" }, { "wo", "Wolof" }, { "xh", "Xhosa" }, { "yi", "Yiddish" }, { "yo", "Yoruba" }, { "za", "Zhuang" },
	{ "zh", "Chinese" }, { "zu", "Zulu" }, { "xx", "Unknown" }, { "\0", "Unknown" } };
char *video_format[2] = {"NTSC", "PAL"};
char *aspect_ratio[4] = {"4/3", "'?:?'", "'?:?'", "16/9"};
char *quantization[4] = {"16bit", "20bit", "24bit", "drc"};
char *mpeg_version[2] = {"mpeg1", "mpeg2"};
char *video_height[2] = {"480", "576"};
char *video_width[4]  = {"720", "704", "352", "352"};
char *permitted_df[4] = {"P&S + Letter", "Pan&Scan", "Letterbox", "?"};
char *audio_format[7] = {"ac3", "?", "mpeg1", "mpeg2", "lpcm ", "sdds ", "dts"};
int  audio_id[7]      = { 0x80, 0  , 0xC0   , 0xC0   , 0xA0   , 0      , 0x88};
char *sample_freq[2]  = {"48000", "'??kHz'"};
char *audio_type[5]   = {"Undefined", "Normal", "Impaired", "Comments1", "Comments2"};
char *subp_type[16]   = {"Undefined", "Normal", "Large", "Children", "reserved", "Normal_CC", "Large_CC", "Children_CC",
	"reserved", "Forced", "reserved", "reserved", "reserved", "Director", "Large_Director", "Children_Director"};
double frames_per_s[4] = {-1.0, 25.00, -1.0, 29.97};

char* program_name;

int dvdtime2msec(dvd_time_t *dt)
{
  double fps = frames_per_s[(dt->frame_u & 0xc0) >> 6];
  long   ms;
  ms  = (((dt->hour &   0xf0) >> 3) * 5 + (dt->hour   & 0x0f)) * 3600000;
  ms += (((dt->minute & 0xf0) >> 3) * 5 + (dt->minute & 0x0f)) * 60000;
  ms += (((dt->second & 0xf0) >> 3) * 5 + (dt->second & 0x0f)) * 1000;

  if(fps > 0)
  ms += ((dt->frame_u & 0x30) >> 3) * 5 + (dt->frame_u & 0x0f) * 1000.0 / fps;

  return ms;
}

/*
 *  The following method is based on code from vobcopy, by Robos, with thanks.
 */

int get_title_name(const char* dvd_device, char* title)
{
  FILE *filehandle = 0;
  int  i;

  if (! (filehandle = fopen(dvd_device, "r"))) {
    fprintf(stderr, "Couldn't open %s for title\n", dvd_device);
    strcpy(title, "unknown");
    return -1;
  }

  if ( fseek(filehandle, 32808, SEEK_SET )) {
    fclose(filehandle);
    fprintf(stderr, "Couldn't seek in %s for title\n", dvd_device);
    strcpy(title, "unknown");
    return -1;
  }

  if ( 32 != (i = fread(title, 1, 32, filehandle)) ) {
    fclose(filehandle);
    fprintf(stderr, "Couldn't read enough bytes for title.\n");
    strcpy(title, "unknown");
    return -1;
  }

  fclose (filehandle);

  title[32] = '\0';
  while(i-- > 2)
     if(title[i] == ' ') title[i] = '\0';
  return 0;
}

char* lang_name(char* code)
{
	int k=0;
	while (memcmp(language[k].code, code, 2) && language[k].name[0] ) { k++; }
	return language[k].name;
}

void version()
{
  fprintf(stderr, "lsdvd "VERSION" - GPL Copyright (c) 2002, 2003, \"Written\" by Chris Phillips <acid_kewpie@users.sf.net>\n");
}

void usage()
{
  version();
  fprintf(stderr, "Usage: %s [options] [-t track_number] [dvd path] \n\t Options: extra information on:\n", program_name);
  fprintf(stderr, "\t-a audio streams\n\t-d cells\n\t-n angles\n\t-c chapters\n\t-s subpictures\n");
  fprintf(stderr, "\t-v video\n\t-x all information\n\t-p use perl output\n\t-q quiet - no summary totals\n");
  fprintf(stderr, "\t-h this message\n\t-V version information\n");
}

int main(int argc, char *argv[])
{
        char title[33];
	dvd_reader_t *dvd;
	ifo_handle_t *ifo_zero, **ifo;
	pgcit_t *vts_pgcit;
	vtsi_mat_t *vtsi_mat;
	vmgi_mat_t *vmgi_mat;
	audio_attr_t *audio_attr;
	video_attr_t *video_attr;
	subp_attr_t *subp_attr;
	pgc_t *pgc;
	int i, j, c, titles, cell, vts_ttn, title_set_nr;
 	char lang_code[2];
	char *dvd_device = "/dev/dvd";
	int has_title = 0, ret = 0;
        int max_length = 0, max_track = 0;
	struct stat dvd_stat;

	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;

	program_name = argv[0];

	while ((c = getopt(argc, argv, "acnpqsdvt:xhV?")) != EOF) {
		switch (c) {
		case 'h':
		case '?':	usage();	return 0;
		case 'V':	version();	return 0;
		case 'a':	opt_a = 1;	break;
		case 'd':	opt_d = 1;	break;
		case 's':	opt_s = 1;	break;
		case 'q':	opt_q = 1;	break;
		case 'c':	opt_c = 1;	break;
		case 'n':	opt_n = 1;	break;
		case 'p':	opt_p = 1;	break;
		case 't':	opt_t = atoi(optarg);	break;
		case 'v':	opt_v = 1;	break;
		case 'x':	opt_x = 1;
		                opt_a = 1;
		                opt_c = 1;
                                opt_s = 1;
                                opt_d = 1;
                                opt_n = 1;
                                opt_v = 1;	break;
		}
	}

	if (argv[optind]) { dvd_device = argv[optind];	}
	
	ret = stat(dvd_device, &dvd_stat);
	if ( ret < 0 ) {
		fprintf(stderr, "Can't find device %s\n", dvd_device);
		return 1;
	}
	
	dvd = DVDOpen(dvd_device);
	if( !dvd ) {
		fprintf( stderr, "Can't open disc %s!\n", dvd_device);
		return 2;
	}
	ifo_zero = ifoOpen(dvd, 0);
	if ( !ifo_zero ) {
                fprintf( stderr, "Can't open main ifo!\n");
		return 3;
	}

	ifo = (ifo_handle_t **)malloc((ifo_zero->vts_atrt->nr_of_vtss + 1) * sizeof(ifo_handle_t *));

	for (i=1; i <= ifo_zero->vts_atrt->nr_of_vtss; i++) {
		ifo[i] = ifoOpen(dvd, i);
		if ( !ifo[i] ) {
			fprintf( stderr, "Can't open ifo %d!\n", i);
			return 4;
		}
	}

	titles = ifo_zero->tt_srpt->nr_of_srpts;

	if ( opt_t > titles || opt_t < 0) {
		fprintf (stderr, "Only %d titles on this disc!", titles);
		return 5;
	}

        has_title = get_title_name(dvd_device, title);

	vmgi_mat = ifo_zero->vmgi_mat;

	if (opt_p) {
		START;
		DEF("device", "'%s'", dvd_device);
		DEF("title", "'%s'", has_title ? "unknown" : title);
		DEF("vmg_id", "'%.12s'", vmgi_mat->vmg_identifier);
		DEF("provider_id", "'%.32s'", vmgi_mat->provider_identifier);
	
		ARRAY("track");
	}
	for (j=0; j < titles; j++)
	{
	if ( opt_t == j+1 || opt_t == 0 ) {
	// GENERAL
	if (ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vtsi_mat) {

		vtsi_mat   = ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vtsi_mat;
		vts_pgcit  = ifo[ifo_zero->tt_srpt->title[j].title_set_nr]->vts_pgcit;
		video_attr = &vtsi_mat->vts_video_attr;
		vts_ttn = ifo_zero->tt_srpt->title[j].vts_ttn;
		vmgi_mat = ifo_zero->vmgi_mat;
		title_set_nr = ifo_zero->tt_srpt->title[j].title_set_nr;
		//pgc = vts_pgcit->pgci_srp[ifo_zero->tt_srpt->title[j].vts_ttn - 1].pgc;
		//printf ("this pgcn = %i", ifo[title_set_nr]->vts_ptt_srpt->title[vts_ttn - 1].ptt[0].pgcn - 1);
		pgc = vts_pgcit->pgci_srp[ifo[title_set_nr]->vts_ptt_srpt->title[vts_ttn - 1].ptt[0].pgcn - 1].pgc;

		if (opt_p) {
 			HASH(0);
			DEF("ix", "%d", j+1);
			DEF("length", "%.3f", dvdtime2msec(&pgc->playback_time)/1000.0);
			DEF("vts_id", "'%.12s'", vtsi_mat->vts_identifier);
		} else {
			printf("Title: %02d, Length: %02x:%02x:%02x ", j+1, pgc->playback_time.hour, pgc->playback_time.minute, pgc->playback_time.second);
		}
		if (dvdtime2msec(&pgc->playback_time) > max_length) {
			max_length = dvdtime2msec(&pgc->playback_time);
			max_track = j+1;
		}
                
		if (! opt_q && ! opt_p) {
		printf("Chapters: %02d, Cells: %02d, ", ifo_zero->tt_srpt->title[j].nr_of_ptts, pgc->nr_of_cells);
		printf("Audio streams: %02d, Subpictures: %02d", vtsi_mat->nr_of_vts_audio_streams, vtsi_mat->nr_of_vts_subp_streams);
		}

		if (! opt_p) { printf("\n"); }

		if (opt_v) {
                if (opt_p) {
			DEF("vts", "%d", ifo_zero->tt_srpt->title[j].title_set_nr);
			DEF("ttn", "%d", ifo_zero->tt_srpt->title[j].vts_ttn);
			DEF("fps", "%.2f", frames_per_s[(pgc->playback_time.frame_u & 0xc0) >> 6]);
			DEF("format", "'%s'", video_format[video_attr->video_format]);
			DEF("aspect", "'%s'", aspect_ratio[video_attr->display_aspect_ratio]);
			DEF("width", "%s", video_width[video_attr->picture_size]);
			DEF("height", "%s", video_height[video_attr->video_format]);
			DEF("df", "'%s'", permitted_df[video_attr->permitted_df]);
                } else {
                	printf("\tVTS: %02d, TTN: %02d, ", ifo_zero->tt_srpt->title[j].title_set_nr, ifo_zero->tt_srpt->title[j].vts_ttn);
	                printf("FPS: %.2f, ", frames_per_s[(pgc->playback_time.frame_u & 0xc0) >> 6]);
        	        printf("Format: %s, Aspect ratio: %s, ", video_format[video_attr->video_format], aspect_ratio[video_attr->display_aspect_ratio]);
	                printf("Width: %s, Height: %s, ", video_width[video_attr->picture_size], video_height[video_attr->video_format]);
	                printf("DF: %s\n", permitted_df[video_attr->permitted_df]);
		}
		}

		// ANGLES
		if (opt_n) {
		if (opt_p) {	
			DEF("angles", "%d", ifo_zero->tt_srpt->title[j].nr_of_angles);
		} else {
	                printf("\tNumber of Angles: %d\n", ifo_zero->tt_srpt->title[j].nr_of_angles);
		}
		}

		// AUDIO
		if (opt_a) {
		if (opt_p) {
			ARRAY("audio");
			for (i=0; i<vtsi_mat->nr_of_vts_audio_streams; i++)
			{
				audio_attr = &vtsi_mat->vts_audio_attr[i];
				sprintf(lang_code, "%c%c", audio_attr->lang_code>>8, audio_attr->lang_code & 0xff);
				if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
				HASH(0);
				DEF("ix", "%d", i+1);
				DEF("langcode", "'%s'", lang_code);
				DEF("language", "'%s'", lang_name(lang_code));
				DEF("format", "'%s'", audio_format[audio_attr->audio_format]);
				DEF("frequency", "%s", sample_freq[audio_attr->sample_frequency]);
				DEF("quantization", "'%s'", quantization[audio_attr->quantization]);
				DEF("channels", "%d", audio_attr->channels+1);
				DEF("ap_mode", "%d", audio_attr->application_mode);
				DEF("content", "'%s'", audio_type[audio_attr->lang_extension]);
				DEF("streamid", "0x%x", audio_id[audio_attr->audio_format] + i);
				RETURN;
			}
			RETURN; 
		} else {
			for (i=0; i<vtsi_mat->nr_of_vts_audio_streams; i++)
                	{
                        	audio_attr = &vtsi_mat->vts_audio_attr[i];
                        	sprintf(lang_code, "%c%c", audio_attr->lang_code>>8, audio_attr->lang_code & 0xff);
	                        if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
		                printf("\tAudio: %d, Language: %s - %s, ", i +1 , lang_code, lang_name(lang_code));
       	                	printf("Format: %s, ", audio_format[audio_attr->audio_format]);
				printf("Frequency: %s, ", sample_freq[audio_attr->sample_frequency]);
                       		printf("Quantization: %s, ", quantization[audio_attr->quantization]);
                	        printf("Channels: %d, AP: %d, ", audio_attr->channels+1, audio_attr->application_mode);
                	        printf("Content: %s, ", audio_type[audio_attr->lang_extension]);
                                printf("StreamID: 0x%x", audio_id[audio_attr->audio_format] + i);
                        	printf("\n");
                	}
		}
		}

		// CHAPTERS
		cell = 0;
		if (opt_c) {
		if (opt_p) {
			ARRAY("chapter");
			for (i=0; i<pgc->nr_of_programs; i++)
			{
				int ms=0;
				int next = pgc->program_map[i+1];
				if (i == pgc->nr_of_programs - 1) next = pgc->nr_of_cells + 1;
				while (cell < next - 1)
				{
					ms = ms + dvdtime2msec(&pgc->cell_playback[cell].playback_time);
					cell++;
				}
				HASH(0);
				DEF("ix", "%d", i+1);
				DEF("length", "%.3f", ms/1000.0);
				DEF("startcell", "%d", pgc->program_map[i]);
				RETURN;
			}
			RETURN;
		} else {
			for (i=0; i<pgc->nr_of_programs; i++)
	                {
       				int second=0, minute=0, hour=0, tmp;
	                        char hex[2];
	                        int next = pgc->program_map[i+1];
	                        if (i == pgc->nr_of_programs - 1) next = pgc->nr_of_cells + 1;
	                        while (cell < next - 1)
	                        {
	                                sprintf(hex, "%02x", pgc->cell_playback[cell].playback_time.second);
	                                tmp = second + atoi(hex);
	                                minute = minute + (tmp / 60);
	                                second = tmp % 60;
	                                sprintf(hex, "%02x", pgc->cell_playback[cell].playback_time.minute);
	                                tmp = minute + atoi(hex);
        	                        hour = hour + (tmp / 60);
	                                minute = tmp % 60;
	                                cell++;
	                        }
                        printf("\tChapter: %02d, Length: %02d:%02d:%02d, Start Cell: %02d\n", i+1, hour, minute, second, pgc->program_map[i]);
        	        }
		}
		}

		// CELLS
		if (opt_d) {
		if (opt_p) {
			ARRAY("cell");
			for (i=0; i<pgc->nr_of_cells; i++)
			{
				HASH(0);
				DEF("ix", "%d", i+1);
				DEF("length", "%.3f", dvdtime2msec(&pgc->cell_playback[i].playback_time)/1000.0);
				RETURN;
			}
			RETURN;
		} else {
			for (i=0; i<pgc->nr_of_cells; i++)   
	                {
                        	printf("\tCell: %02d, Length: %02x:%02x:%02x\n", i+1, pgc->cell_playback[i].playback_time.hour,  
						pgc->cell_playback[i].playback_time.minute, pgc->cell_playback[i].playback_time.second);
	                }
		}
		}

		// SUBTITLES
		if (opt_s) {
		if (opt_p) {
			ARRAY("subp");
			for (i=0; i<vtsi_mat->nr_of_vts_subp_streams; i++)
			{
				subp_attr = &vtsi_mat->vts_subp_attr[i];
				sprintf(lang_code, "%c%c", subp_attr->lang_code>>8, subp_attr->lang_code & 0xff);
				if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
	
				HASH(0);
				DEF("ix", "%d", i+1);
				DEF("langcode", "'%s'", lang_code);
				DEF("language", "'%s'", lang_name(lang_code));
				DEF("content", "'%s'", subp_type[subp_attr->lang_extension]);
				DEF("streamid", "0x%x", 0x20+i);
				RETURN;
			}
			RETURN;
		} else {
			for (i=0; i<vtsi_mat->nr_of_vts_subp_streams; i++)
	                {
	                        subp_attr = &vtsi_mat->vts_subp_attr[i];
	                        sprintf(lang_code, "%c%c", subp_attr->lang_code>>8, subp_attr->lang_code & 0xff);
	                        if (!lang_code[0]) { lang_code[0] = 'x'; lang_code[1] = 'x'; }
	                        printf("\tSubtitle: %02d, Language: %s - %s, ", i+1, lang_code, lang_name(lang_code));
	                        printf("Content: %s, ", subp_type[subp_attr->lang_extension]);
				printf("StreamID: 0x%x", 0x20+i);
	                        printf("\n");  
	                }
		}
		}
	RETURN;	
	}
	if ( ! opt_p && (opt_a || opt_c || opt_p || opt_s || opt_n)) printf("\n");
	}
	}
	RETURN;
	if (opt_p) {
		DEF("longest_track", "%d", max_track);
	} else {
		printf("Longest track: %d\n", max_track);
	}

        for (i=1; i <= ifo_zero->vts_atrt->nr_of_vtss; i++) {
		ifoClose(ifo[i]);
	}
	ifoClose(ifo_zero);
	DVDClose(dvd);

	STOP;
	return 0;
}
