You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1395 lines
36 KiB

/*
* dvd_reader.c
*
* Copyright (C) Thomas Oestreich - June 2001
* Copyright (C) 2001 Billy Biggs <vektor@dumbterm.net>.
*
* 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 <assert.h>
#include "dvd_reader.h"
#ifdef HAVE_LIBDVDREAD
#ifdef HAVE_LIBDVDREAD_INC
#include <dvdread/dvd_reader.h>
#include <dvdread/ifo_types.h>
#include <dvdread/ifo_read.h>
#include <dvdread/nav_read.h>
#include <dvdread/nav_print.h>
#else
#include <dvd_reader.h>
#include <ifo_types.h>
#include <ifo_read.h>
#include <nav_read.h>
#include <nav_print.h>
#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