/* * 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. */ #define VERSION "0.10" #include #include #include #include #include #include /* 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"}; /* 28.9.2003: Chicken run's aspect ratio is 16:9 or 1.85:1, at index 1. Addionaly using ' in the quoting makes the perl output not parse so changed to " */ char *aspect_ratio[4] = {"4/3", "16/9", "\"?:?\"", "16/9"}; char *quantization[4] = {"16bit", "20bit", "24bit", "drc"}; char *mpeg_version[2] = {"mpeg1", "mpeg2"}; /* 28.9.2003: The European chicken run title has height index 3, and 576 lines seems right, mplayer -vop cropdetect shows from 552 to 576 lines. What the correct value is for index 2 is harder to say */ char *video_height[4] = {"480", "576", "???", "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"}; /* 28.9.2003: Chicken run again, it has frequency index of 1. According to dvd::rip the frequency is 48000 */ char *sample_freq[2] = {"48000", "48000"}; 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; } // Funktion aus lsdvd uebernommen // berechnet Zeit fuer Chapterunterteilung int millisekunden(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, 32768, SEEK_SET )) { fclose(filehandle); fprintf(stderr, "Couldn't seek in %s for title\n", dvd_device); strcpy(title, "unknown"); return -1; } { #define DVD_SEC_SIZ 2048 char tempBuf[ DVD_SEC_SIZ ]; if ( DVD_SEC_SIZ != fread(tempBuf, 1, DVD_SEC_SIZ, filehandle) ) { fclose(filehandle); fprintf(stderr, "Couldn't read enough bytes for title.\n"); strcpy(title, "unknown"); return -1; } snprintf( title, 32, "%s", tempBuf + 40 ); i=32; } 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 \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"); } else { printf("Disc Title|%s\n", has_title ? "unknown" : title); } 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("%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]); ARRAY("palette"); for (i=0; i<16; i++) { HASH(0); DEF("y ", "%d", (pgc->palette[i]>>16)&0xff); DEF("cr", "%d", (pgc->palette[i]>> 8)&0xff); DEF("cb", "%d", pgc->palette[i] &0xff); RETURN; } RETURN; } else { printf("%02d|VTS|%02d|TTN|%02d", j+1, 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", permitted_df[video_attr->permitted_df]); printf("|Palette|"); for (i=0; i<16; i++) printf(" %06x", pgc->palette[i]); printf("\n"); } } // ANGLES if (opt_n) { if (opt_p) { DEF("angles", "%d", ifo_zero->tt_srpt->title[j].nr_of_angles); } else { printf("%02d|Angles|%d\n", j+1, ifo_zero->tt_srpt->title[j].nr_of_angles); } } // AUDIO if (opt_a) { if (opt_p) { ARRAY("audio"); for (i=0; inr_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]); RETURN; } RETURN; } else { for (i=0; inr_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("%02d|Audio|%d|Language|%s|%s|", j+1, 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("\n"); } } } // CHAPTERS cell = 0; if (opt_c) { if (opt_p) { unsigned long total_sectors = 0; ARRAY("chapter"); for (i=0; inr_of_programs; i++) { int ms=0; int next = pgc->program_map[i+1]; unsigned long sectors = 0; 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); sectors += pgc->cell_playback[cell].last_sector - pgc->cell_playback[cell].first_sector + 1; cell++; } total_sectors += sectors; HASH(0); DEF("ix", "%d", i+1); DEF("length", "%.3f", ms/1000.0); DEF("startcell", "%d", pgc->program_map[i]); DEF("sectors", "%lu", sectors); RETURN; } RETURN; DEF("sectors", "%lu", total_sectors); } else { unsigned long total_sectors = 0; long long lMilliSekunden=0; for (i=0; inr_of_programs; i++) { int second=0, minute=0, hour=0, tmp; char hex[2]; int next = pgc->program_map[i+1]; unsigned long sectors = 0; if (i == pgc->nr_of_programs - 1) next = pgc->nr_of_cells + 1; while (cell < next - 1) { lMilliSekunden += millisekunden(&pgc->cell_playback[cell].playback_time); 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; sectors += pgc->cell_playback[cell].last_sector - pgc->cell_playback[cell].first_sector + 1; cell++; } total_sectors += sectors; printf("%02d|Chapter|%02d|Length|%02lld:%02lld:%02lld.%03lld|Start Cell|%02d|Sectors|%7lu\n", j+1, i+1, lMilliSekunden/3600000, (lMilliSekunden/60000)%60, (lMilliSekunden/1000)%60, lMilliSekunden%1000, pgc->program_map[i], sectors); } printf("%02d|Total Sectors|%lu\n", j+1, total_sectors); } } // CELLS if (opt_d) { if (opt_p) { ARRAY("cell"); for (i=0; inr_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; inr_of_cells; i++) { printf("%02d: Cell: %02d, Length: %02x:%02x:%02x\n", j+1, 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; inr_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]); RETURN; } RETURN; } else { for (i=0; inr_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("%02d|Subtitle|%02d|Language|%s|%s|", j+1, i+1, lang_code, lang_name(lang_code)); printf("Content|%s", subp_type[subp_attr->lang_extension]); printf("\n"); } } } RETURN; } if ( ! opt_p && (opt_a || opt_c || opt_p || opt_s || opt_n)) printf("\n"); } } RETURN; if (! opt_t) { 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; }