/* * a dvd copy tool * * 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; * * * Some parts of this source code were taken from the following projects: * transcode by Dr. Thomas Oestreich * lsdvd by Chris Phillips * mplex by mjpeg-tools developers group * */ #include #include #include #include #include #include #include #include #define VERSION "0.4" #define MAXSTREAMS 50 #define PROBEBLOCKS 30720 #define MPEG_VIDEO 0 #define MPEG_AUDIO 1 #define AC3_AUDIO 2 #define DTS_AUDIO 3 #define LPCM_AUDIO 4 #define SUBPICTURE 5 int debug = 0; double dvd_size_bytes = 4700000000.; char *stream_type_names[6] = { "MPEG Video", "MPEG Audio", "AC3 Audio", "DTS Audio", "LPCM-Audio", "Subpicture" }; double frames_per_s[4] = {-1.0, 25.00, -1.0, 29.97}; // bitrate tables stolen from mplex unsigned int ac3_bitrate_index[32] = { 32,40,48,56,64,80,96,112,128,160,192,224,256,320,384,448,512,576,640,0,0,0,0,0,0,0,0,0,0,0,0,0 }; unsigned int dts_bitrate_index[32] = { 32,56,64,96,112,128,192,224,256,320,384,448,512,576,640,768,960,1024,1152,1280,1344,1408,1411,1472,1536,1920,2048,3072,3840,0,0,0 }; unsigned int mpa_bitrate_index [2][4][16] = { { /* MPEG audio V2 */ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, {0,32,48,56,64 ,80 ,96 ,112,128,160,192,224,256,320,384,0}, {0,8 ,16,24,32 ,64 ,80 ,56 ,64 ,128,160,112,128,256,320,0} }, { /* MPEG audio V1 */ {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}, {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0}, {0,32,48,56,64 ,80 ,96 ,112,128,160,192,224,256,320,384,0}, {0,32,40,48,56 ,64 ,80 ,96 ,112,128,160,192,224,256,320,0} } }; struct streamdata { // data for 1 stream unsigned char id; // stream id long bitrate; // bitrate int type; // type of stream double size; // size of stream in bytes }; /* * calc playback length in ms - stolen from lsdvd */ 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; } /* * Extract the real start and stop blocks to read from the ifo structure * If start and stop chapters are 0, they'll be set to the right values (start=1, stop=lastchapter) */ int get_start_stop_blocks (dvd_reader_t *dvd, int title, uint8_t *ts_nr, int *chapstart, int *chapstop, uint32_t *startblock, uint32_t *lastblock, int* runtime ) { int ok = 0; ifo_handle_t *ifo_title; ifo_handle_t *ifo_main; vts_ptt_srpt_t *vts_ptt_srpt; tt_srpt_t *tt_srpt; pgc_t *pgc, *full_pgc; int titleid = title -1, ttn, pgc_id, pgn, start_cell, end_cell, i; if( title == 0 ) { // don't rip title 0 fprintf(stderr, "Invalid title number %d\n", title); return 0; } ifo_main = ifoOpen(dvd, 0); // open main ifo file if( !ifo_main ) { fprintf(stderr, "Error opening main ifo file\n"); return(0); } if( titleid > ifo_main->tt_srpt->nr_of_srpts ) { // title nr. < max num. of titles ? fprintf(stderr, "Invalid title number %d\n", title); return(0); } tt_srpt = ifo_main->tt_srpt; *ts_nr = tt_srpt->title[ titleid ].title_set_nr; // save title set nr ifo_title = ifoOpen(dvd, tt_srpt->title[ titleid ].title_set_nr); // open title ifo file if( !ifo_title ) { fprintf(stderr, "Error opening ifo file for title %d\n", title); ifoClose(ifo_main); return(0); } if( ifo_title->vtsi_mat ) { // get start/stop block for selected chapters if( *chapstart == 0 ) *chapstart = 1; // set first chapter if unset if( *chapstop == 0 ) *chapstop = tt_srpt->title[ titleid ].nr_of_ptts; // set last chapter if unset ttn = tt_srpt->title[ titleid ].vts_ttn; // mostly stolen from transcode vts_ptt_srpt = ifo_title->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[*chapstart-1].pgn; pgc = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc; full_pgc = pgc; start_cell = pgc->program_map[pgn - 1] - 1; *startblock = pgc->cell_playback[start_cell].first_sector; // first block of start cell if( *chapstop == tt_srpt->title[ titleid ].nr_of_ptts ) { // if stop chapter = last chapter end_cell = pgc->nr_of_cells -1; *lastblock = pgc->cell_playback[end_cell].last_sector; } else { pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[*chapstop].pgn; pgc = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc; end_cell = pgc->program_map[pgn - 1] - 2; *lastblock = pgc->cell_playback[end_cell].last_sector; // last block of end cell } if( (*chapstart == 1) && (*chapstop == tt_srpt->title[ titleid ].nr_of_ptts) ) { // all chapters selected ? *runtime = dvdtime2msec(&full_pgc->playback_time) / 1000; } // runtime = movie runtime else { // only some chapters selected ? *runtime = 0; for( i=start_cell ; i<=end_cell ; i++ ) { // sum runtime of selected cells *runtime += dvdtime2msec(&pgc->cell_playback[i].playback_time) / 1000; } } ok = 1; } ifoClose(ifo_title); ifoClose(ifo_main); return(ok); } int probe_dvd ( dvd_file_t *vob, struct streamdata **list, uint32_t start_block, uint32_t stop_block ) { int x, y, z, found, count = 0; unsigned char buffer[DVD_VIDEO_LB_LEN], *searchptr; uint8_t packet_type; struct streamdata *new_data; uint32_t maxblocks = stop_block, i; if( (stop_block-start_block) > PROBEBLOCKS ) { maxblocks = start_block + PROBEBLOCKS; } for( i=start_block ; i= 0xE0) && (packet_type <= 0xEF)) || ((packet_type >= 0x20) && (packet_type <= 0x3F)) || ((packet_type >= 0xC0) && (packet_type <= 0xCF)) || ((packet_type >= 0xA0) && (packet_type <= 0xAF)) || ((packet_type >= 0x80) && (packet_type <= 0x8F)) ) { for( x=0 ; xid == packet_type ) { // makes sure, we only get headers at stream start found = 1; break; } } if( !found ) { // unknown stream, add to list new_data = malloc(sizeof(struct streamdata)); if( new_data == NULL ) { fprintf(stderr, "Out of memory\n"); exit(1); } new_data->id = packet_type; if( (packet_type >= 0xE0) && (packet_type <= 0xEF) ) { new_data->type = MPEG_VIDEO; new_data->bitrate = 0; // dummy entry for video } else if( (packet_type >= 0xC0) && (packet_type <= 0xCF) ) { new_data->type = MPEG_AUDIO; new_data->bitrate = 0; searchptr = buffer +17; searchptr = (unsigned char*)memchr(searchptr, 0xFF, searchptr-buffer); // search for 1st byte of MPA Syncword while( searchptr != NULL ) { if( searchptr <= (buffer+2045) ) { // check for search hit at end of buffer if( (searchptr[1] & 0xF0) == 0xF0 ) { // if next 4 bits set, we found the sync word x = (searchptr[1] & 0x08) >> 3; // version id y = (searchptr[1] & 0x06) >> 1; // layer code z = (searchptr[2] & 0xF0) >> 4; // bitrate code new_data->bitrate = mpa_bitrate_index[x][y][z] * 1024; if( debug) fprintf(stderr, "MPEG_AUDIO bitrate: %d\n", mpa_bitrate_index[x][y][z]); break; } } searchptr++; searchptr = (unsigned char*)memchr(searchptr, 0xFF, searchptr-buffer); } if( new_data->bitrate == 0 ) { // no syncword found, try next packet fprintf(stderr, "unable to get MPEG-Audio bitrate\n"); continue; } } else if( (packet_type >= 0x80) && (packet_type <= 0x87) ) { y = buffer[22] + buffer[buffer[22]+26] + (unsigned char)26; // calc start offsets if( (buffer[y] != 0x0B) || (buffer[y+1] != 0x77) ) { // check for AC3 Syncword continue; } new_data->type = AC3_AUDIO; x = buffer[y + 4] & 0x3F; // 6 lowest bits new_data->bitrate = ac3_bitrate_index[ x>>1 ] * 1024; // shift index >> 1 (same as /2 ) if( debug) fprintf(stderr, "AC3_AUDIO bitrate: %d\n", ac3_bitrate_index[ x>>1 ]); } else if( (packet_type >= 0x88) && (packet_type <= 0x8F) ) { new_data->type = DTS_AUDIO; x = (buffer[buffer[22] + 35] & 0x3) << 3; // 3 lowest bits of byte x = x | ((buffer[buffer[22] + 36] & 0xE0) >> 5); // 2 highest bits of next byte new_data->bitrate = dts_bitrate_index[x] * 1000; // kbit * 1000 fits better to filesize, so what ... if( debug) fprintf(stderr, "DTS_AUDIO bitrate: %d\n", dts_bitrate_index[x]); } else if( (packet_type >= 0xA0) && (packet_type <= 0xAF) ) { new_data->type = LPCM_AUDIO; new_data->bitrate = 2000000; // FIXME - hard coded, no detection yet } else if( (packet_type >= 0x20) && (packet_type <= 0x3F) ) { new_data->type = SUBPICTURE; new_data->bitrate = 0; // dummy entry for subpicture } list[count] = new_data; count++; } } } return(count); } void usage (char *progname, int exitcode) { fprintf(stderr, "usage: %s -i device -t title [-c chapter|chapter range] [-s stream(s)] [-v] [-h]\n", progname); fprintf(stderr, " device : dvd device or directory containing dvd structure\n"); fprintf(stderr, " title : title number\n"); fprintf(stderr, " chapter: chapter number or range (default: all chapter)\n"); fprintf(stderr, " streams: video/audio/subtitle stream(s) (default: all streams)\n"); fprintf(stderr, "\n"); exit(exitcode); } void version ( void ) { fprintf(stderr, " streamanalize v%s - (C) 2003 by Reinhardt Wolf\n", VERSION); exit(0); } int main(int argc, char *argv[]) { dvd_reader_t *dvd; dvd_file_t *vob; char *opt_inputname = NULL; int opt_title = 0; int opt_chapter_start = 0; int opt_chapter_stop = 0; int opt_streamlist[MAXSTREAMS]; int opt_stream_count = 0; struct streamdata *streamdata_list[MAXSTREAMS]; int stream_count = 0; uint8_t ts_nr; int i, c, x; int *b; int runtime; double video_size, overhead_size, nonvideo_size = 0, selected_size = 0, temp; uint32_t start_block = 0, stop_block = 0; while( (c = getopt(argc, argv, "i:t:c:s:d:hv")) != -1 ) { // process options switch(c) { case 'h': usage(argv[0], 0); break; case 'v': version(); break; case 'i': opt_inputname = optarg; break; case 't': opt_title = atoi(optarg); break; case 's': b = opt_streamlist; if( (x = sscanf(optarg, "%x,%x,%x,%x,%x,%x,%x,%x,%x,%x", &b[0],&b[1],&b[2],&b[3],&b[4],&b[5],&b[6],&b[7],&b[8],&b[9])) > 0 ) { opt_stream_count = opt_stream_count + x; } break; case 'c': x = sscanf(optarg,"%d-%d", &opt_chapter_start, &opt_chapter_stop); if( x != 2 ) { x = sscanf(optarg,"%d", &opt_chapter_start); if( x != 1 ) { fprintf(stderr, "invalid parameter for option -c\n"); exit(1); } opt_chapter_stop = opt_chapter_start; } break; default: usage(argv[0], 1); } } if( (opt_inputname == NULL) || (opt_title == 0) ) usage(argv[0], 1); dvd = DVDOpen(opt_inputname); if( !dvd ) { fprintf(stderr, "error opening dvd at %s\n", opt_inputname); exit(1); } if( get_start_stop_blocks(dvd, opt_title, &ts_nr, &opt_chapter_start, &opt_chapter_stop, &start_block, &stop_block, &runtime) ) { vob = DVDOpenFile(dvd, ts_nr, DVD_READ_TITLE_VOBS); if( !vob ) { fprintf(stderr, "ERROR: opening vobs for title %d failed\n", ts_nr); DVDClose(dvd); exit(1); } stream_count = probe_dvd(vob, streamdata_list, start_block, stop_block); // probe some blocks to find all streams DVDCloseFile(vob); if( !opt_stream_count ) { // if no streamlist given as parameter opt_stream_count = stream_count; // write found streams to streamlist for( i=0 ; iid; } } fprintf(stderr, "\n\n"); fprintf(stderr, "Title %d - ", opt_title); fprintf(stderr, "%d Chapters (%d Blocks / %11.0f Bytes) - ", opt_chapter_stop-opt_chapter_start+1, stop_block-start_block, (double)(stop_block-start_block) * 2048); fprintf(stderr, "Runtime %d sec.\n\n", runtime); fprintf(stderr, "Track List:\n"); for( i=0 ; itype ) { case MPEG_VIDEO : streamdata_list[i]->size = 0; break; case SUBPICTURE : streamdata_list[i]->size = (double)(stop_block-start_block) * 2048 / 1450; break; case LPCM_AUDIO : case MPEG_AUDIO : case AC3_AUDIO : case DTS_AUDIO : streamdata_list[i]->size = streamdata_list[i]->bitrate / 8 * runtime; break; } nonvideo_size += streamdata_list[i]->size; } overhead_size = (stop_block-start_block) * 2048 / 50; // rough guess about space wasted by padding, pack headers, ... nonvideo_size += overhead_size; // sum of all non video stuff video_size = ((double)(stop_block-start_block) * 2048) - nonvideo_size; // video size = vob size - non video stuff for( i=0 ; itype == MPEG_VIDEO ) streamdata_list[i]->size = video_size; // add video size for( x=0 ; xid ) { fprintf(stderr, "X"); selected_size += streamdata_list[i]->size; } } fprintf(stderr, " 0x%x %10s", streamdata_list[i]->id, stream_type_names[streamdata_list[i]->type]); fprintf(stderr, " %10.0f Bytes\n", streamdata_list[i]->size); } fprintf(stderr, "\n"); fprintf(stderr, "Size of selected streams: %11.0f Bytes\n", selected_size+overhead_size); fprintf(stderr, "Max. target size : %11.0f Bytes\n", dvd_size_bytes); if( (selected_size+overhead_size) <= dvd_size_bytes ) { fprintf(stderr, "Factor : not needed\n"); } else { temp = (selected_size+overhead_size) - dvd_size_bytes; temp = video_size - temp; fprintf(stderr, "Factor : %5.3f\n", video_size / temp); } } DVDClose(dvd); return 0; }