/* * dvd_reader.c * * Copyright (C) Thomas Oestreich - June 2001 * Copyright (C) 2001 Billy Biggs . * * This file is part of transcode, a video stream processing tool * * transcode is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * transcode is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with GNU Make; see the file COPYING. If not, write to * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "transcode.h" #include "libtc/libtc.h" #include "probe.h" #include "magic.h" #include #include "dvd_reader.h" #ifdef HAVE_LIBDVDREAD #ifdef HAVE_LIBDVDREAD_INC #include #include #include #include #include #else #include #include #include #include #include #endif static char lock_file[] = "/tmp/LCK..dvd"; /* * lock - create a lock file for the device */ static int lock(void) { char lock_buffer[12]; int fd, pid, n; while ((fd = open(lock_file, O_EXCL | O_CREAT | O_RDWR, 0644)) < 0) { if (errno != EEXIST) { tc_log_warn(__FILE__, "Can't create lock file %s: %s", lock_file, strerror(errno)); break; } /* Read the lock file to find out who has the device locked. */ fd = open(lock_file, O_RDONLY, 0); if (fd < 0) { if (errno == ENOENT) /* This is just a timing problem. */ continue; tc_log_warn(__FILE__, "Can't open existing lock file %s: %s", lock_file, strerror(errno)); break; } n = read(fd, lock_buffer, 11); close(fd); fd = -1; if (n <= 0) { tc_log_warn(__FILE__, "Can't read pid from lock file %s", lock_file); break; } /* See if the process still exists. */ lock_buffer[n] = 0; pid = atoi(lock_buffer); if (pid == getpid()) return 0; /* somebody else locked it for us */ if (pid == 0 || (kill(pid, 0) == -1 && errno == ESRCH)) { if (unlink (lock_file) == 0) { tc_log_warn(__FILE__, "Removed stale lock (pid %d)", pid); continue; } tc_log_warn(__FILE__, "Couldn't remove stale lock"); } break; } if (fd < 0) { return 1; } pid = getpid(); tc_snprintf(lock_buffer, sizeof(lock_buffer), "%10d", pid); write(fd, lock_buffer, 11); close(fd); return 0; } /* * unlock - remove our lockfile */ static void unlock(void) { unlink(lock_file); } /** * Returns true if the pack is a NAV pack. This check is clearly insufficient, * and sometimes we incorrectly think that valid other packs are NAV packs. I * need to make this stronger. */ static int is_nav_pack(unsigned char *buffer) { return buffer[41] == 0xbf && buffer[1027] == 0xbf; } static dvd_reader_t *dvd=NULL; static unsigned char *data=NULL; static const char *ifoPrint_time(const dvd_time_t *time, long *playtime_ret) { long playtime; const char *rate; int i; static char outbuf[TC_BUF_MIN]; char *outptr; assert((time->hour>>4) < 0xa && (time->hour&0xf) < 0xa); assert((time->minute>>4) < 0x7 && (time->minute&0xf) < 0xa); assert((time->second>>4) < 0x7 && (time->second&0xf) < 0xa); assert((time->frame_u&0xf) < 0xa); i = tc_snprintf(outbuf, sizeof(outbuf), "%02x:%02x:%02x.%02x", time->hour, time->minute, time->second, time->frame_u & 0x3f); if (i > 0) outptr = outbuf+i; i=time->hour>>4; playtime = (i*10 + time->hour-(i<<4))*60*60; i=(time->minute>>4); playtime += (i*10 + time->minute-(i<<4))*60; i=(time->second>>4); playtime += i*10 + time->second-(i<<4); playtime++; switch((time->frame_u & 0xc0) >> 6) { case 1: rate = "25.00"; break; case 3: rate = "29.97"; break; default: if(time->hour == 0 && time->minute == 0 && time->second == 0 && time->frame_u == 0) { rate = "no"; } else rate = "(please send a bug report)"; break; } //tc_snprintf(outptr, sizeof(outbuf) - (outptr-outbuf), " @ %s fps", rate); if (playtime_ret) *playtime_ret = playtime; return outbuf; } static void stats_video_attributes(video_attr_t *attr, ProbeInfo *probe_info) { const char *version, *display, *dar, *wide, *ntsc_cc, *lbox, *mode; char unknown1[50], size[50]; /* The following test is shorter but not correct ISO C, memcmp(attr,my_friendly_zeros, sizeof(video_attr_t)) */ if(attr->mpeg_version == 0 && attr->video_format == 0 && attr->display_aspect_ratio == 0 && attr->permitted_df == 0 && attr->unknown1 == 0 && attr->line21_cc_1 == 0 && attr->line21_cc_2 == 0 && attr->video_format == 0 && attr->letterboxed == 0 && attr->film_mode == 0) { tc_log_info(__FILE__, "-- Unspecified Video --"); return; } switch(attr->mpeg_version) { case 0: version = "mpeg1 "; probe_info->codec=TC_CODEC_MPEG1; break; case 1: version = "mpeg2 "; probe_info->codec=TC_CODEC_MPEG2; break; default: version = "(please send a bug report)"; } switch(attr->video_format) { case 0: display = "ntsc "; probe_info->magic=TC_MAGIC_NTSC; break; case 1: display = "pal "; probe_info->magic=TC_MAGIC_PAL; break; default: display = "(please send a bug report) "; } switch(attr->display_aspect_ratio) { case 0: dar = "4:3 "; probe_info->asr=2; break; case 3: dar = "16:9 "; probe_info->asr=3; break; default: dar = "(please send a bug report) "; } // Wide is allways allowed..!!! switch(attr->permitted_df) { case 0: wide = "pan&scan+letterboxed "; break; case 1: wide = "only pan&scan "; //??? break; case 2: wide = "only letterboxed "; break; case 3: wide = ""; // not specified break; default: wide = "(please send a bug report) "; } tc_snprintf(unknown1, sizeof(unknown1), "U%x ", attr->unknown1); assert(!attr->unknown1); if(attr->line21_cc_1 && attr->line21_cc_2) { ntsc_cc = "NTSC CC 1 2 "; } else if(attr->line21_cc_1) { ntsc_cc = "NTSC CC 1 "; } else if(attr->line21_cc_2) { ntsc_cc = "NTSC CC 2 "; } else { ntsc_cc = ""; } { int height = 480; if(attr->video_format != 0) height = 576; switch(attr->picture_size) { case 0: tc_snprintf(size, sizeof(size), "720x%d ", height); probe_info->width=720; probe_info->height = height; break; case 1: tc_snprintf(size, sizeof(size), "704x%d ", height); probe_info->width=704; probe_info->height = height; break; case 2: tc_snprintf(size, sizeof(size), "352x%d ", height); probe_info->width=352; probe_info->height = height; break; case 3: tc_snprintf(size, sizeof(size), "352x%d ", height/2); probe_info->width=352; probe_info->height = height/2; break; default: tc_snprintf(size, sizeof(size), "(please send a bug report) "); } } if(attr->letterboxed) { lbox = "letterboxed "; } else { lbox = ""; } if(attr->film_mode) { mode = "film"; } else { mode = "video"; //camera } if (verbose >= TC_INFO) { tc_log_info(__FILE__, "%s%s%s%s%s%s%s%s%s", version, display, dar, wide, unknown1, ntsc_cc, size, lbox, mode); } } static void stats_audio_attributes(audio_attr_t *attr, int track, ProbeInfo *probe_info) { const char *format, *mcext, *lang, *appmode, *quant, *freq, *langext; char langbuf[4], channels[50]; if(attr->audio_format == 0 && attr->multichannel_extension == 0 && attr->lang_type == 0 && attr->application_mode == 0 && attr->quantization == 0 && attr->sample_frequency == 0 && attr->channels == 0 && attr->lang_extension == 0 && attr->unknown1 == 0 && attr->unknown1 == 0) { tc_log_info(__FILE__, "-- Unspecified Audio --"); return; } //defaults for all tracks: ++probe_info->num_tracks; probe_info->track[track].chan = 2; probe_info->track[track].bits = 16; probe_info->track[track].tid = track; switch(attr->audio_format) { case 0: format = "ac3 "; probe_info->track[track].format = CODEC_AC3; break; case 1: format = "(please send a bug report) "; break; case 2: format = "mpeg1 "; probe_info->track[track].format = CODEC_MP2; break; case 3: format = "mpeg2ext "; break; case 4: format = "lpcm "; probe_info->track[track].format = CODEC_LPCM; break; case 5: format = "(please send a bug report) "; break; case 6: format = "dts "; probe_info->track[track].format = CODEC_DTS; break; default: format = "(please send a bug report) "; } if(attr->multichannel_extension) { mcext = "multichannel_extension "; } else { mcext = ""; } switch(attr->lang_type) { case 0: lang = ""; // not specified probe_info->track[track].lang=0; break; case 1: langbuf[0] = attr->lang_code>>8; langbuf[1] = attr->lang_code & 0xff; langbuf[2] = ' '; langbuf[3] = 0; lang = langbuf; probe_info->track[track].lang=attr->lang_code; break; default: lang = "(please send a bug report) "; } switch(attr->application_mode) { case 0: appmode = ""; // not specified break; case 1: appmode = "karaoke mode "; break; case 2: appmode = "surround sound mode "; break; default: appmode = "(please send a bug report) "; } switch(attr->quantization) { case 0: quant = "16bit "; probe_info->track[track].bits = 16; break; case 1: quant = "20bit "; probe_info->track[track].bits = 20; break; case 2: quant = "24bit "; probe_info->track[track].bits = 24; break; case 3: quant = "drc "; break; default: quant = "(please send a bug report) "; } switch(attr->sample_frequency) { case 0: freq = "48kHz "; probe_info->track[track].samplerate = 48000; break; case 1: freq = "96kHz "; probe_info->track[track].samplerate = 96000; break; case 2: freq = "44.1kHz "; probe_info->track[track].samplerate = 44100; break; case 3: freq = "32kHz "; probe_info->track[track].samplerate = 32000; break; default: freq = "(please send a bug report) "; } tc_snprintf(channels, sizeof(channels), "%dCh ", attr->channels + 1); switch(attr->lang_extension) { case 0: langext = ""; // "Not specified "; break; case 1: // Normal audio langext = "Normal Caption "; break; case 2: // visually imparied langext = "Audio for visually impaired "; break; case 3: // Directors 1 langext = "Director's comments #1 "; break; case 4: // Directors 2 langext = "Director's comments #2 "; break; //case 4: // Music score ? default: langext = "(please send a bug report) "; } if (verbose >= TC_INFO) { tc_log_info(__FILE__, "%s%s%s%s%s%s%s%s", format, mcext, lang, appmode, quant, freq, channels, langext); } } static void stats_subp_attributes(subp_attr_t *attr, int track, ProbeInfo *probe_info) { char buf1[50] = {0}, buf2[50] = {0}; if (attr->type == 0 && attr->zero1 == 0 && attr->lang_code == 0 && attr->lang_extension == 0 && attr->zero2 == 0) { tc_log_info(__FILE__, "-- Unspecified Subs --"); return; } if (attr->type) { tc_snprintf(buf1, sizeof(buf1), "subtitle %02d=<%c%c> ", track, attr->lang_code>>8, attr->lang_code & 0xff); if (attr->lang_extension) tc_snprintf(buf2, sizeof(buf2), "ext=%d", attr->lang_extension); } if (verbose >= TC_DEBUG) { tc_log_info(__FILE__, "%s%s", buf1, buf2); } } int dvd_query(int title, int *arg_chapter, int *arg_angle) { int ttn, pgc_id, titleid; tt_srpt_t *tt_srpt; ifo_handle_t *vmg_file; pgc_t *cur_pgc; ifo_handle_t *vts_file; vts_ptt_srpt_t *vts_ptt_srpt; vmg_file = ifoOpen(dvd, 0); if (!vmg_file) { tc_log_error(__FILE__, "Can't open VMG info."); return -1; } tt_srpt = vmg_file->tt_srpt; /** * Make sure our title number is valid. */ titleid = title-1; if (titleid < 0 || titleid >= tt_srpt->nr_of_srpts) { tc_log_error(__FILE__, "Invalid title %d.", titleid + 1); goto bad_title; } // display title infos if (verbose & TC_DEBUG) tc_log_msg(__FILE__, "DVD title %d: %d chapter(s), %d angle(s)", title, tt_srpt->title[ titleid ].nr_of_ptts, tt_srpt->title[ titleid ].nr_of_angles); /** * Load the VTS information for the title set our title is in. */ vts_file = ifoOpen( dvd, tt_srpt->title[ titleid ].title_set_nr ); if (!vts_file) { tc_log_error(__FILE__, "Can't open the title %d info file.", tt_srpt->title[ titleid ].title_set_nr); goto bad_title; } ttn = tt_srpt->title[ titleid ].vts_ttn; vts_ptt_srpt = vts_file->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ ttn - 1 ].ptt[0].pgcn; cur_pgc = vts_file->vts_pgcit->pgci_srp[ pgc_id - 1 ].pgc; if(verbose & TC_DEBUG) { tc_log_msg(__FILE__, "DVD playback time: %s", ifoPrint_time(&cur_pgc->playback_time, NULL)); } //return info *arg_chapter = tt_srpt->title[ titleid ].nr_of_ptts; *arg_angle = tt_srpt->title[ titleid ].nr_of_angles; return 0; bad_title: ifoClose(vmg_file); return -1; } int dvd_probe(int title, ProbeInfo *info) { int ttn, pgn, pgc_id, titleid, start_cell, end_cell, i, j; tt_srpt_t *tt_srpt; ifo_handle_t *vmg_file; pgc_t *cur_pgc; ifo_handle_t *vts_file; vts_ptt_srpt_t *vts_ptt_srpt; video_attr_t *v_attr; audio_attr_t *a_attr; subp_attr_t *s_attr; dvd_time_t *dt; double fps; long hour, minute, second, ms, overall_time, cur_time, playtime; const char *s; vmg_file = ifoOpen( dvd, 0 ); if (!vmg_file) { return -1; } tt_srpt = vmg_file->tt_srpt; // Make sure our title number is valid titleid = title-1; if (titleid < 0 || titleid >= tt_srpt->nr_of_srpts) { tc_log_error(__FILE__, "Invalid title %d.", titleid + 1); goto bad_title; } vts_file = ifoOpen(dvd, tt_srpt->title[ titleid ].title_set_nr); if (!vts_file) { tc_log_error(__FILE__, "Can't open the title %d info file.", tt_srpt->title[ titleid ].title_set_nr ); goto bad_title; } if (vts_file->vtsi_mat) { v_attr = &vts_file->vtsi_mat->vts_video_attr; stats_video_attributes(v_attr, info); for (i = 0; i < vts_file->vtsi_mat->nr_of_vts_audio_streams; i++) { a_attr = &vts_file->vtsi_mat->vts_audio_attr[i]; stats_audio_attributes(a_attr, i, info); } for (i = 0; i < vts_file->vtsi_mat->nr_of_vts_subp_streams; i++) { s_attr = &vts_file->vtsi_mat->vts_subp_attr[i]; stats_subp_attributes(s_attr, i, info); } } else { tc_log_error(__FILE__, "failed to probe DVD title information"); goto bad_title; } vts_file = NULL; /* uh?!? -- FR */ vts_file = ifoOpen(dvd, tt_srpt->title[ titleid ].title_set_nr); if (!vts_file) { tc_log_error(__FILE__, "Can't open the title %d info file.", tt_srpt->title[ titleid ].title_set_nr); goto bad_title; } ttn = tt_srpt->title[ titleid ].vts_ttn; vts_ptt_srpt = vts_file->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ ttn - 1 ].ptt[0].pgcn; cur_pgc = vts_file->vts_pgcit->pgci_srp[ pgc_id - 1 ].pgc; switch (((cur_pgc->playback_time).frame_u & 0xc0) >> 6) { case 1: info->fps = PAL_FPS; info->frc = 3; info->magic = TC_MAGIC_DVD_PAL; break; case 3: info->fps = NTSC_FILM; info->frc = 1; info->magic = TC_MAGIC_DVD_NTSC; break; } tc_log_info(__FILE__, "DVD title %d/%d: %d chapter(s), %d angle(s), title set %d", title, tt_srpt->nr_of_srpts, tt_srpt->title[ titleid ].nr_of_ptts, tt_srpt->title[ titleid ].nr_of_angles, tt_srpt->title[ titleid].title_set_nr); s = ifoPrint_time(&cur_pgc->playback_time, &playtime); tc_log_info(__FILE__, "title playback time: %s %ld sec", s, playtime); info->time = playtime; // stolen from ogmtools-1.0.2 dvdxchap -- tibit ttn = tt_srpt->title[titleid].vts_ttn; vts_ptt_srpt = vts_file->vts_ptt_srpt; overall_time = 0; for (i = 0; i < tt_srpt->title[titleid].nr_of_ptts - 1; i++) { pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[i].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[i].pgn; cur_pgc = vts_file->vts_pgcit->pgci_srp[pgc_id - 1].pgc; start_cell = cur_pgc->program_map[pgn - 1] - 1; pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[i + 1].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[i + 1].pgn; if (pgn < 1) continue; cur_pgc = vts_file->vts_pgcit->pgci_srp[pgc_id - 1].pgc; end_cell = cur_pgc->program_map[pgn - 1] - 2; cur_time = 0; for (j = start_cell; j <= end_cell; j++) { dt = &cur_pgc->cell_playback[j].playback_time; hour = ((dt->hour & 0xf0) >> 4) * 10 + (dt->hour & 0x0f); minute = ((dt->minute & 0xf0) >> 4) * 10 + (dt->minute & 0x0f); second = ((dt->second & 0xf0) >> 4) * 10 + (dt->second & 0x0f); if (((dt->frame_u & 0xc0) >> 6) == 1) fps = 25.00; else fps = 29.97; dt->frame_u &= 0x3f; dt->frame_u = ((dt->frame_u & 0xf0) >> 4) * 10 + (dt->frame_u & 0x0f); ms = (double)dt->frame_u * 1000.0 / fps; cur_time += (hour * 60 * 60 * 1000 + minute * 60 * 1000 + second * 1000 + ms); } if (verbose >= TC_DEBUG) { tc_log_info(__FILE__, "[Chapter %02d] %02ld:%02ld:%02ld.%03ld , block from %d to %d", i + 1, overall_time / 60 / 60 / 1000, (overall_time / 60 / 1000) % 60, (overall_time / 1000) % 60, overall_time % 1000, cur_pgc->cell_playback[i].first_sector, cur_pgc->cell_playback[i].last_sector); } overall_time += cur_time; } if (verbose >= TC_DEBUG) { tc_log_info(__FILE__, "[Chapter %02d] %02ld:%02ld:%02ld.%03ld , block from %d to %d", i + 1, overall_time / 60 / 60 / 1000, (overall_time / 60 / 1000) % 60, (overall_time / 1000) % 60, overall_time % 1000, cur_pgc->cell_playback[i].first_sector, cur_pgc->cell_playback[i].last_sector); } return 0; bad_title: ifoClose(vmg_file); return -1; } int dvd_is_valid(const char *dvd_path) { dvd_reader_t *_dvd = NULL; ifo_handle_t *vmg_file = NULL; _dvd = DVDOpen(dvd_path); if (_dvd == NULL) { return TC_FALSE; } vmg_file = ifoOpen( _dvd, 0); if (vmg_file == NULL) { DVDClose(_dvd); return TC_FALSE; } DVDClose(_dvd); return TC_TRUE; } int dvd_init(const char *dvd_path, int *titles, int verb) { tt_srpt_t *tt_srpt; ifo_handle_t *vmg_file; // copy verbosity flag verbose = verb; /** * Open the disc. */ if (dvd == NULL) { dvd = DVDOpen(dvd_path); if (!dvd) return -1; } //workspace if (data == NULL) { data = tc_malloc(1024 * DVD_VIDEO_LB_LEN); if (data == NULL) { tc_log_error(__FILE__, "out of memory"); DVDClose(dvd); return -1; } } vmg_file = ifoOpen(dvd, 0); if (!vmg_file) { tc_log_error(__FILE__, "Can't open VMG info."); DVDClose(dvd); tc_free(data); return -1; } tt_srpt = vmg_file->tt_srpt; *titles = tt_srpt->nr_of_srpts; return 0; } int dvd_close(void) { if (data != NULL) { tc_free(data); data = NULL; } if (dvd != NULL) { DVDClose(dvd); dvd = NULL; } return 0; } int dvd_read(int arg_title, int arg_chapter, int arg_angle) { int pgc_id, len, start_cell, cur_cell, last_cell, next_cell; unsigned int cur_pack; int ttn, pgn; int lockretries; dvd_file_t *title; ifo_handle_t *vmg_file; tt_srpt_t *tt_srpt; ifo_handle_t *vts_file; vts_ptt_srpt_t *vts_ptt_srpt; pgc_t *cur_pgc; int titleid, angle, chapid; chapid = arg_chapter - 1; titleid = arg_title - 1; angle = arg_angle - 1; /** * Load the video manager to find out the information about the titles on * this disc. */ vmg_file = ifoOpen( dvd, 0 ); if( !vmg_file ) { tc_log_error(__FILE__, "Can't open VMG info."); return -1; } tt_srpt = vmg_file->tt_srpt; /** * Make sure our title number is valid. */ if( titleid < 0 || titleid >= tt_srpt->nr_of_srpts ) { tc_log_error(__FILE__, "Invalid title %d.", titleid + 1); ifoClose( vmg_file ); return -1; } /** * Make sure the chapter number is valid for this title. */ if( chapid < 0 || chapid >= tt_srpt->title[ titleid ].nr_of_ptts ) { tc_log_error(__FILE__, "Invalid chapter %d.", chapid + 1); ifoClose( vmg_file ); return -1; } /** * Make sure the angle number is valid for this title. */ if( angle < 0 || angle >= tt_srpt->title[ titleid ].nr_of_angles ) { tc_log_error(__FILE__, "Invalid angle %d.", angle + 1); ifoClose( vmg_file ); return -1; } /** * Load the VTS information for the title set our title is in. */ vts_file = ifoOpen( dvd, tt_srpt->title[ titleid ].title_set_nr ); if( !vts_file ) { tc_log_error(__FILE__, "Can't open the title %d info file.", tt_srpt->title[ titleid ].title_set_nr ); ifoClose( vmg_file ); return -1; } /** * Determine which program chain we want to watch. This is based on the * chapter number. */ ttn = tt_srpt->title[ titleid ].vts_ttn; vts_ptt_srpt = vts_file->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid ].pgcn; pgn = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid ].pgn; cur_pgc = vts_file->vts_pgcit->pgci_srp[ pgc_id - 1 ].pgc; start_cell = cur_pgc->program_map[ pgn - 1 ] - 1; //ThOe if (chapid+1 == tt_srpt->title[ titleid ].nr_of_ptts) { last_cell = cur_pgc->nr_of_cells; } else { last_cell = cur_pgc->program_map[ (vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid+1 ].pgn) - 1 ] - 1; } /** * We've got enough info, time to open the title set data. */ for (lockretries=0; lock() && lockretries < 180; lockretries++ ) { sleep(1); } if( lockretries >= 180 ) { tc_log_error(__FILE__, "Can't acquire lock."); } title = DVDOpenFile( dvd, tt_srpt->title[ titleid ].title_set_nr, DVD_READ_TITLE_VOBS); unlock(); if( !title ) { tc_log_error(__FILE__, "Can't open title VOBS (VTS_%02d_1.VOB).", tt_srpt->title[ titleid ].title_set_nr ); ifoClose( vts_file ); ifoClose( vmg_file ); return -1; } /** * Playback the cells for our chapter. */ next_cell = start_cell; for( cur_cell = start_cell; next_cell < last_cell; ) { cur_cell = next_cell; /* Check if we're entering an angle block. */ if( cur_pgc->cell_playback[ cur_cell ].block_type == BLOCK_TYPE_ANGLE_BLOCK ) { int i; cur_cell += angle; for( i = 0;; ++i ) { if( cur_pgc->cell_playback[ cur_cell + i ].block_mode == BLOCK_MODE_LAST_CELL ) { next_cell = cur_cell + i + 1; break; } } } else { next_cell = cur_cell + 1; } /** * We loop until we're out of this cell. */ for( cur_pack = cur_pgc->cell_playback[ cur_cell ].first_sector; cur_pack < cur_pgc->cell_playback[ cur_cell ].last_sector; ) { dsi_t dsi_pack; unsigned int next_vobu, next_ilvu_start, cur_output_size; /** * Read NAV packet. */ nav_retry: len = DVDReadBlocks( title, (int) cur_pack, 1, data ); if( len != 1 ) { tc_log_error(__FILE__, "Read failed for block %d", cur_pack); ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return -1; } //assert( is_nav_pack( data ) ); if(!is_nav_pack(data)) { cur_pack++; goto nav_retry; } /** * Parse the contained dsi packet. */ navRead_DSI( &dsi_pack, &(data[ DSI_START_BYTE ])); if(!( cur_pack == dsi_pack.dsi_gi.nv_pck_lbn)) { cur_output_size = 0; dsi_pack.vobu_sri.next_vobu = SRI_END_OF_CELL; } /** * Determine where we go next. These values are the ones we mostly * care about. */ next_ilvu_start = cur_pack + dsi_pack.sml_agli.data[ angle ].address; cur_output_size = dsi_pack.dsi_gi.vobu_ea; /** * If we're not at the end of this cell, we can determine the next * VOBU to display using the VOBU_SRI information section of the * DSI. Using this value correctly follows the current angle, * avoiding the doubled scenes in The Matrix, and makes our life * really happy. * * Otherwise, we set our next address past the end of this cell to * force the code above to go to the next cell in the program. */ if( dsi_pack.vobu_sri.next_vobu != SRI_END_OF_CELL ) { next_vobu = cur_pack + ( dsi_pack.vobu_sri.next_vobu & 0x7fffffff ); } else { next_vobu = cur_pack + cur_output_size + 1; } assert( cur_output_size < 1024 ); cur_pack++; /** * Read in and output cursize packs. */ len = DVDReadBlocks( title, (int) cur_pack, cur_output_size, data ); if( len != (int) cur_output_size ) { tc_log_error(__FILE__, "Read failed for %d blocks at %d", cur_output_size, cur_pack ); ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return -1; } fwrite( data, cur_output_size, DVD_VIDEO_LB_LEN, stdout ); if(verbose & TC_STATS) tc_log_msg(__FILE__, "%d %d", cur_pack, cur_output_size); cur_pack = next_vobu; } } ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return 0; } static long startsec; static long startusec; static void rip_counter_init(long int *t1, long int *t2) { #ifdef HAVE_GETTIMEOFDAY struct timeval tv; struct timezone tz={0,0}; gettimeofday(&tv,&tz); *t1=tv.tv_sec; *t2=tv.tv_usec; #else *t1 = time(NULL); *t2 = 0; #endif } static void rip_counter_close(void) { fprintf(stderr,"\n"); } static long range_a = -1, range_b = -1; static long range_starttime = -1; static void rip_counter_set_range(long from, long to) { range_a = from; range_b = to-1; } static void counter_print(long int pida, long int pidn, long int t1, long int t2) { double fps; #ifdef HAVE_GETTIMEOFDAY struct timeval tv; struct timezone tz={0,0}; if(gettimeofday(&tv,&tz)<0) return; #else struct {long tv_sec, tv_usec;} tv; tv.tv_sec = time(NULL); tv.tv_usec = 0; #endif fps=(pidn-pida)/((tv.tv_sec+tv.tv_usec/1000000.0)-(t1+t2/1000000.0)); fps = (2048 * fps) / (1<<20); if(fps>0) { if(range_b != -1 && pidn>=range_a) { double done; long secleft; if(range_starttime == -1) range_starttime = tv.tv_sec; done = (double)(pidn-range_a)/(range_b-range_a); secleft = (long)((1-done)*(double)(tv.tv_sec-range_starttime)/done); fprintf(stderr, "extracting blocks [%08ld], %4.1f MB/s, %4.1f%%, ETA: %ld:%02ld:%02ld \r", pidn-pida, fps, 100*done, secleft/3600, (secleft/60) % 60, secleft % 60); } } } int dvd_stream(int arg_title,int arg_chapid) { int pgc_id, len, start_cell; unsigned long cur_pack=0, max_sectors=0, blocks_left=0, blocks_written=0, first_block=0; int ttn, pgn; int end_cell; int e_pgc_id, e_pgn; pgc_t *e_cur_pgc; dvd_file_t *title; ifo_handle_t *vmg_file; tt_srpt_t *tt_srpt; ifo_handle_t *vts_file; vts_ptt_srpt_t *vts_ptt_srpt; pgc_t *cur_pgc; int titleid, angle, chapid; unsigned int cur_output_size=1024, blocks=0; chapid = arg_chapid - 1; titleid = arg_title - 1; angle = 0; /** * Load the video manager to find out the information about the titles on * this disc. */ vmg_file = ifoOpen( dvd, 0 ); if( !vmg_file ) { tc_log_error(__FILE__, "Can't open VMG info."); return -1; } tt_srpt = vmg_file->tt_srpt; /** * Make sure our title number is valid. */ if( titleid < 0 || titleid >= tt_srpt->nr_of_srpts ) { tc_log_error(__FILE__, "Invalid title %d.", titleid + 1); ifoClose( vmg_file ); return -1; } /** * Make sure the chapter number is valid for this title. */ if( chapid < 0 || chapid >= tt_srpt->title[ titleid ].nr_of_ptts ) { tc_log_error(__FILE__, "Invalid chapter %d.", chapid + 1); ifoClose( vmg_file ); return -1; } /** * Make sure the angle number is valid for this title. */ if( angle < 0 || angle >= tt_srpt->title[ titleid ].nr_of_angles ) { tc_log_error(__FILE__, "Invalid angle %d.", angle + 1); ifoClose( vmg_file ); return -1; } /** * Load the VTS information for the title set our title is in. */ vts_file = ifoOpen( dvd, tt_srpt->title[ titleid ].title_set_nr ); if( !vts_file ) { tc_log_error(__FILE__, "Can't open the title %d info file.", tt_srpt->title[ titleid ].title_set_nr); ifoClose( vmg_file ); return -1; } /** * Determine which program chain we want to watch. This is based on the * chapter number. */ ttn = tt_srpt->title[ titleid ].vts_ttn; vts_ptt_srpt = vts_file->vts_ptt_srpt; pgc_id = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid ].pgcn; pgn = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid ].pgn; cur_pgc = vts_file->vts_pgcit->pgci_srp[ pgc_id - 1 ].pgc; start_cell = cur_pgc->program_map[ pgn - 1 ] - 1; if ( chapid+1 >= tt_srpt->title[ titleid ].nr_of_ptts ) { end_cell = cur_pgc->nr_of_cells - 1; } else { e_pgc_id = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid+1 ].pgcn; e_pgn = vts_ptt_srpt->title[ ttn - 1 ].ptt[ chapid+1 ].pgn; e_cur_pgc = vts_file->vts_pgcit->pgci_srp[ e_pgc_id - 1 ].pgc; end_cell = ( e_cur_pgc->program_map[ e_pgn - 1 ] - 1 ) -1; } /** * We've got enough info, time to open the title set data. */ title = DVDOpenFile( dvd, tt_srpt->title[ titleid ].title_set_nr, DVD_READ_TITLE_VOBS); if( !title ) { tc_log_error(__FILE__, "Can't open title VOBS (VTS_%02d_1.VOB).", tt_srpt->title[ titleid ].title_set_nr); ifoClose( vts_file ); ifoClose( vmg_file ); return -1; } /** * Playback the cells for our title */ if (start_cell==end_cell) tc_log_msg(__FILE__, "Title %d in VTS %02d is defined by PGC %d with %d cells, exporting cell %d", titleid+1,tt_srpt->title[ titleid ].title_set_nr,pgc_id, cur_pgc->nr_of_cells,start_cell+1); else tc_log_msg(__FILE__, "Title %d in VTS %02d is defined by PGC %d with %d cells, exporting from cell %d to cell %d", titleid+1,tt_srpt->title[ titleid ].title_set_nr,pgc_id, cur_pgc->nr_of_cells,start_cell+1,end_cell+1); cur_pack = cur_pgc->cell_playback[start_cell].first_sector; max_sectors = (long) cur_pgc->cell_playback[end_cell].last_sector; tc_log_msg(__FILE__, "From block %ld to block %ld", (long)cur_pack,(long)max_sectors); first_block = cur_pack; //tc_log_msg(__FILE__, "title %02d, %ld blocks (%ld-%ld)", tt_srpt->title[ titleid ].title_set_nr, (long) DVDFileSize(title), (long) cur_pack, (long) max_sectors); if((long) DVDFileSize(title) < max_sectors || cur_pack < 0) tc_log_error(__FILE__, "internal error"); //sanity check if(max_sectors <= cur_pack) max_sectors = (long) DVDFileSize(title); /** * Read NAV packet. */ len = DVDReadBlocks( title, (int) cur_pack, 1, data ); if( len != 1 ) { tc_log_error(__FILE__, "Read failed for block %ld", cur_pack); ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return -1; } //write NAV packet fwrite(data, 1, DVD_VIDEO_LB_LEN, stdout); if(data[38]==0 && data[39]==0 && data[40]==1 && data[41]==0xBF && data[1024]==0 && data[1025]==0 && data[1026]==1 && data[1027]==0xBF) { tc_log_msg(__FILE__, "navigation packet at offset %d", (int) cur_pack); } // loop until all packs of title are written blocks_left = max_sectors-cur_pack+1; rip_counter_set_range(1, blocks_left); rip_counter_init(&startsec, &startusec); while(blocks_left > 0) { blocks = (blocks_left>cur_output_size) ? cur_output_size:blocks_left; len = DVDReadBlocks( title, (int) cur_pack, blocks, data ); if( len != (int) blocks) { rip_counter_close(); if(len>=0) { if(len>0)fwrite(data, len, DVD_VIDEO_LB_LEN, stdout); tc_log_msg(__FILE__, "%ld blocks written", blocks_written+len); } ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return -1; } fwrite(data, blocks, DVD_VIDEO_LB_LEN, stdout); blocks_written += blocks; counter_print(1, blocks_written, startsec, startusec); cur_pack += blocks; blocks_left -= blocks; if(verbose & TC_STATS) tc_log_msg(__FILE__, "%ld %d", cur_pack, cur_output_size); } rip_counter_close(); tc_log_msg(__FILE__, "%ld blocks written", blocks_written); ifoClose( vts_file ); ifoClose( vmg_file ); DVDCloseFile( title ); return 0; } #else int dvd_query(int arg_title, int *arg_chapter, int *arg_angle) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_init(const char *dvd_path, int *arg_title, int verb) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_read(int arg_title, int arg_chapter, int arg_angle) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_stream(int arg_title, int arg_chapid) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_close(void) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_verify(const char *name) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_probe(int title, ProbeInfo *info) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return -1; } int dvd_is_valid(const char *dvd_path) { tc_log_error(__FILE__, "no support for DVD reading configured - exit."); return TC_FALSE; } #endif