//vim:fileencoding=utf8
/*
dvd-vr.c Identify and optionally copy the individual programs
from a DVD-VR format disc
Copyright © 2007-2010 Pádraig Brady
This program 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 of the License, or
(at your option) any later version.
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/*
Notes:
Individual recordings (programs) are extracted,
honouring any splits and/or deletes.
Merged programs are not handled yet though as
I would need to fully parse the higher level program set info.
Note the VOBs output from this program can be trivially
concatenated with the unix cat command for example
(note there will be timestamp jumps which may be problematic).
While extracting the DVD data, this program instructs the system
to not cache the data so that existing cached data is not affected.
We output the data from this program rather than just outputting offsets
for use with dd for example, because we may support disjoint VOBUs
(merged programs) in future. Also in future we may transform the NAV info
slightly in the VOBs? Anyway it gives us greater control over the system cache
as described above.
It might be useful to provide a FUSE module using this logic,
to present the logical structure of a DVD-VR, maybe even present as DVD-Video?
Doesn't parse play list index
Doesn't parse still image info
Doesn't parse chapters
Doesn't fixup MPEG time data
Requirements:
gcc >= 2.95
glibc >= 2.3.3 on linux
Tested on linux, CYGWIN and Mac OS X
*/
#define _GNU_SOURCE /* for posix_fadvise(), futimes() */
#define _FILE_OFFSET_BITS 64 /* for implicit large file support */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#if !defined(MB_LEN_MAX) || MB_LEN_MAX<16
/* 1 char could be converted to 2 multibyte chars
* (for example combining accents), with each taking up to
* 6 bytes in UTF-8 for example */
# undef MB_LEN_MAX
# define MB_LEN_MAX 16
#endif
#define TYPE_SIGNED(t) (! ((t) 0 < (t) -1))
#define TYPE_WIDTH(t) (sizeof (t) * CHAR_BIT)
#define TYPE_MAX(t) \
((t) (! TYPE_SIGNED (t) \
? (t) -1 \
: ((((t) 1 << (TYPE_WIDTH (t) - 2)) - 1) * 2 + 1)))
#define OFF_T_MAX TYPE_MAX(off_t)
#define STREQ(x,y) (strcmp(x,y)==0)
#ifndef MAX
# define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifndef MIN
# define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif
/* For a discussion of this macro see:
* http://www.pixelbeat.org/programming/gcc/static_assert.html */
#define ASSERT_CONCAT_(a, b) a##b
#define ASSERT_CONCAT(a, b) ASSERT_CONCAT_(a, b)
#define STATIC_ASSERT(e,m) enum { ASSERT_CONCAT(assert_line_, __LINE__) = 1/(!!(e)) }
#if defined(__CYGWIN__) || defined(_WIN32) /* windos doesn't like : in filenames */
#define TIMESTAMP_FMT "%F_%H-%M-%S"
#else
#define TIMESTAMP_FMT "%F_%T" /* keep : in filenames for backward compat */
#endif
const char* base_name = TIMESTAMP_FMT;
FILE* stdinfo; /* Where we write disc info */
#include
#ifdef HAVE_ICONV
#include
#endif
const char* disc_charset;
const char* sys_charset;
/*********************************************************************************
* support routines
*********************************************************************************/
static size_t my_strnlen(const char* s, size_t n)
{
size_t len = 0;
while (n-- && *s++) len++;
return len;
}
/* Mac OS X doesn't provide strndup :( */
static char* my_strndup(const char *s, size_t n)
{
size_t len = my_strnlen(s, n);
char* ret = malloc(len+1);
if (ret) {
memcpy(ret, s, len);
ret[len] = '\0';
}
return ret;
}
#ifndef NDEBUG
void hexdump(const void* data, int len)
{
int i;
const unsigned char* bytes=data;
for (i=0; i= bytes) {
int ret = posix_fadvise(src_fd, offset-bytes, bytes, POSIX_FADV_DONTNEED);
if (ret) {
fprintf(stderr, "Warning: posix_fadvise failed [%s]\n", strerror(ret));
}
}
/* Don't fill cache with DST.
Note this slows the operation down by 20% when both source
and dest are on the same hard disk at least. I guess
this is due to implicit syncing in posix_fadvise()? */
offset = lseek(dst_fd, 0, SEEK_CUR);
if (offset != (off_t)-1) { /* seekable */
int ret = posix_fadvise(dst_fd, 0, 0, POSIX_FADV_DONTNEED);
if (ret) {
fprintf(stderr, "Warning: posix_fadvise failed [%s]\n", strerror(ret));
}
}
#endif //POSIX_FADV_DONTNEED
return 0;
}
#ifdef MMAP_WRITE
static int stream_data(int src_fd, int dst_fd, uint32_t blocks, uint16_t block_size)
{
int8_t* buf;
off_t offset = lseek(src_fd, 0, SEEK_CUR);
off_t pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1); /* 4097 -> 4096 */
off_t offset_align = offset - pa_offset;;
buf = mmap(NULL, block_size*blocks+offset_align,
PROT_READ, MAP_PRIVATE, src_fd, pa_offset);
if (buf == MAP_FAILED) {
fprintf(stderr, "Error mmaping file [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
#ifdef MADV_SEQUENTIAL
if (madvise(buf, block_size*blocks+offset_align, MADV_SEQUENTIAL)) {
fprintf(stderr, "Warning: madvise failed [%s]\n", strerror(errno));
}
#endif
if (write(dst_fd,buf+offset_align,blocks*block_size) != blocks*block_size) {
fprintf(stderr, "Error writing to DST [%s]\n", strerror(errno));
return -2;
}
offset = lseek(src_fd, blocks*block_size, SEEK_CUR); /* This won't seek head I presume */
if (offset == (off_t)-1) {
fprintf(stderr, "Error seeking in src [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
#ifdef MADV_DONTNEED
if (madvise(buf, blocks*block_size, MADV_DONTNEED)) {
fprintf(stderr, "Warning: madvise failed [%s]\n", strerror(errno));
}
#endif
return 0;
}
#endif //MMAP_WRITE
static const char* get_charset(void)
{
const char* codeset = nl_langinfo(CODESET);
#ifdef __CYGWIN__
/* Cygwin 1.5 does not support locales and nl_langinfo (CODESET)
always returns "US-ASCII". This is fixed in v1.7 I think? */
if (codeset && STREQ(codeset, "US-ASCII")) {
/* parse LANG=ja_JP.SJIS -> SJIS */
const char* locale = getenv ("LANG");
if (locale && *locale) {
const char *dot = strchr (locale, '.');
if (dot) {
const char *modifier;
dot++;
if (!(modifier = strchr (dot, '@'))) {
return dot;
} else {
static char buf[32];
size_t len = modifier - dot;
if (len < sizeof (buf)) {
memcpy (buf, dot, len);
*(buf+len) = '\0';
return buf;
}
}
}
}
return "UTF-8";
}
#endif
return codeset;
}
static bool text_convert(const char *src, size_t srclen, char *dst, size_t dstlen)
{
bool ret=false;
#ifdef HAVE_ICONV
iconv_t cd = iconv_open (sys_charset, disc_charset);
if (cd != (iconv_t)-1) {
if (iconv (cd, (ICONV_CONST char**)&src, &srclen, &dst, &dstlen) != (size_t)-1) {
if (iconv (cd, NULL, NULL, &dst, &dstlen) != (size_t)-1) { /* terminate string */
ret=true;
}
} else {
fprintf(stderr, "Error converting text from %s to %s\n",
disc_charset, sys_charset);
}
iconv_close (cd);
} else {
fprintf(stderr, "Error converting text from %s to %s. Not supported\n",
disc_charset, sys_charset);
}
#else
/* avoid warnings (__attribute__ ((unused)) is too verbose/non standard) */
(void)src; (void)dst; (void)srclen; (void)dstlen;
fprintf(stderr, "Error converting text. libiconv missing\n");
#endif
return ret;
}
/*********************************************************************************
* Internal structures
*********************************************************************************/
typedef struct {
int aspect;
int width;
int height;
} p_video_attr_t;
p_video_attr_t* ifo_video_attrs;
typedef enum {
SCRAMBLED_UNSET=-1,
UNSCRAMBLED=0,
SCRAMBLED=1,
PARTIALLY_SCRAMBLED=2
} scrambled_t;
typedef struct {
int video_attr;
scrambled_t scrambled;
} p_program_attr_t;
p_program_attr_t* ifo_program_attrs;
/*********************************************************************************
* The DVD-VR structures
*********************************************************************************/
#undef PACKED
#if defined(__GNUC__)
# if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95)
# define PACKED __attribute__ ((packed))
# endif
#endif
#if !defined(PACKED)
# error "Your compiler doesn't support __attribute__ ((packed))"
#endif
/* DVD structures are in network byte order (big endian) */
#include
#undef NTOHS
#undef NTOHL
#define NTOHS(x) x=ntohs(x) /* 16 bit */
#define NTOHL(x) x=ntohl(x) /* 32 bit */
#define DVD_SECTOR_SIZE 2048
typedef struct {
struct {
/* Suffix numbers are decimal offsets */
/* 0 */
char id[12];
uint32_t vmg_ea; /* end address */
uint8_t zero_16[12];
uint32_t vmgi_ea; /* includes playlist info after this structure */
uint16_t version; /* specification version */
/* 34 */ /* Different from DVD-Video from here */
uint8_t zero_34[30];
uint8_t data_64[3];
uint8_t txt_encoding; /* as per VideoTextDataUsage.pdf */
uint8_t data_68[30];
/* 98 */
char disc_info1[64]; /* format name, or copy of disc_info2. */
char disc_info2[64]; /* format name, time or user label.. */
uint8_t zero_226[30];
/* 256 */
uint32_t pgit_sa; /* program info table start address */
uint32_t info_260_sa; /* ? start address */
uint8_t zero_264[3];
struct {
uint8_t supported; /* Encrypted Title Key Status */
uint8_t title_key[8];/* This needs to be decrypted using media key */
} cprm;
uint8_t zero_276[28];
/* 304 */
uint32_t def_psi_sa; /* default program set info start address */
uint32_t info_308_sa; /* ? start address */
uint32_t info_312_sa; /* user defined program set info start address? */
uint32_t info_316_sa; /* ? start address */
uint8_t zero_320[32];
uint32_t txt_attr_sa; /* extra attributes for programs (chan id etc.) */
uint32_t info_356_sa; /* ? start address */
uint8_t zero_360[152];
} PACKED mat;
} PACKED rtav_vmgi_t; /*Real Time AV (from DVD_RTAV dir)*/
STATIC_ASSERT(sizeof(rtav_vmgi_t) == 512,""); /* catch any miscounting above */
typedef struct {
uint8_t audio_attr[3];
} PACKED audio_attr_t;
typedef struct {
uint8_t pgtm[5];
} PACKED pgtm_t;
typedef struct {
uint32_t ptm;
uint16_t ptm_extra; /* extra to DSI pkts */
} PACKED ptm_t;
typedef struct {
uint16_t vob_attr;
pgtm_t vob_timestamp;
uint8_t data1;
uint8_t vob_format_id;
ptm_t vob_v_s_ptm;
ptm_t vob_v_e_ptm;
} PACKED vvob_t; /* Virtual VOB */
typedef struct {
uint8_t data[12];
} PACKED adj_vob_t;
typedef struct {
uint16_t nr_of_time_info;
uint16_t nr_of_vobu_info;
uint16_t time_offset;
uint32_t vob_offset;
} PACKED vobu_map_t;
typedef struct {
uint8_t data[7];
} PACKED time_info_t;
typedef struct {
uint8_t data1;
/* only 14 bits are used for size,
* but use full 16 for easy access. */
uint16_t vobu_size;
} PACKED vobu_info_t;
typedef struct {
uint16_t zero1;
uint8_t nr_of_pgi;
uint8_t nr_of_vob_formats;
uint32_t pgit_ea;
} PACKED pgiti_t; /* info for ProGram Info Table */
typedef struct {
uint16_t video_attr;
uint8_t nr_of_audio_streams;
uint8_t data1;
audio_attr_t audio_attr0;
audio_attr_t audio_attr1;
uint8_t data2[50];
} PACKED vob_format_t;
typedef struct {
uint16_t nr_of_programs;
} PACKED pgi_gi_t; /* global info for ProGram Info */
typedef struct {
uint8_t data1;
uint8_t nr_of_psi;
uint16_t nr_of_programs; /* Num programs on disc */
} PACKED psi_gi_t; /* global info for Program Set Info */
typedef struct {
uint8_t data1;
uint8_t data2;
uint16_t nr_of_programs; /* Num programs in program set */
char label[64]; /* ASCII. Might not be NUL terminated */
char title[64]; /* Could be same as label, NUL, or another charset */
uint16_t prog_set_id; /* On LG V1.1 discs this is program set ID */
uint16_t first_prog_id; /* ID of first program in this program set */
char data3[6];
} PACKED psi_t;
static const char* parse_txt_encoding(uint8_t txt_encoding)
{
/* from the VideoTextDataUsage.pdf available at dvdforum.org we have:
01h : ISO 646
10h : JIS Roman[14]*and JIS Kanji1990[168]*
11h : ISO 8859-1
12h : JIS Roman[14]*and JIS Katakana[13]*including Shift JIS Kanji
Also Nero generates discs with 00h, so I'll assume this is ASCII.
*/
const char* charset="Unknown";
switch (txt_encoding) {
case 0x00: charset="ASCII"; break;
case 0x01: charset="ISO646-JP"; break; /* ?? */
case 0x10: charset="JIS_C6220-1969-RO"; break; /* ?? */
case 0x11: charset="ISO_8859-1"; break;
case 0x12: charset="SHIFT_JIS"; break;
}
if (STREQ("Unknown", charset)) {
fprintf(stdinfo, "text encoding: %s", charset);
fprintf(stdinfo, ". (%02X). Please report this number and actual text encoding.\n", txt_encoding );
charset="ISO_8859-15"; /* Shouldn't give an error at least */
}
return charset;
}
static bool parse_audio_attr(audio_attr_t audio_attr0)
{
int coding = (audio_attr0.audio_attr[0] & 0xE0)>>5;
int channels = (audio_attr0.audio_attr[1] & 0x0F);
/* audio_attr0.audio_attr[2] = 7 for my camcorder. Is this 192Kbit? */
/* audio_attr0.audio_attr[2] = 9 for Masato Nunokawa's disc? */
if (channels < 8) {
fprintf(stdinfo, "audio_channs: %d\n",channels+1);
} else if (channels == 9) {
/* According to Masato Nunokawa's disc */
fprintf(stdinfo, "audio_channs: 2 (mono)\n");
} else {
return false;
}
const char* coding_name="Unknown";
switch (coding) {
case 0: coding_name="Dolby AC-3"; break;
case 2: coding_name="MPEG-1"; break;
case 3: coding_name="MPEG-2ext"; break;
case 4: coding_name="Linear PCM"; break;
}
fprintf(stdinfo, "audio_coding: %s",coding_name);
if (STREQ("Unknown", coding_name)) {
fprintf(stdinfo, ". (%d). Please report this number and actual audio encoding.\n", coding );
} else {
putc('\n', stdinfo);
}
return true;
}
static bool parse_video_attr(uint16_t video_attr, p_video_attr_t* p_video_attr)
{
int resolution = (video_attr & 0x0038) >> 3;
int aspect = (video_attr & 0x0C00) >> 10;
int tv_sys = (video_attr & 0x3000) >> 12;
int compression = (video_attr & 0xC000) >> 14;
p_video_attr->aspect = p_video_attr->width = p_video_attr->height = -1;
int vert_resolution = 0;
int horiz_resolution = 0;
const char* tv_system = "Unknown";
switch (tv_sys) {
case 0:
tv_system = "NTSC";
vert_resolution=480;
break;
case 1:
tv_system = "PAL";
vert_resolution=576;
break;
}
fprintf(stdinfo, "tv_system : %s", tv_system);
if (STREQ("Unknown", tv_system)) {
fprintf(stdinfo, ". (%d). Please report this number and actual TV system.\n", tv_sys );
} else {
putc('\n', stdinfo);
}
switch (resolution) {
case 0: horiz_resolution=720; break;
case 1: horiz_resolution=704; break;
case 2: horiz_resolution=352; break;
case 3: horiz_resolution=352; vert_resolution/=2; break;
case 4: horiz_resolution=544; break; /* this is a google inspired guess. */
case 5: horiz_resolution=480; break; /* from Aaron Binns' disc */
}
if (horiz_resolution && vert_resolution) {
fprintf(stdinfo, "resolution : %dx%d\n", horiz_resolution, vert_resolution);
p_video_attr->width = horiz_resolution;
p_video_attr->height = vert_resolution;
} else if (!horiz_resolution) {
fprintf(stdinfo, "resolution : Unknown (%d). Please report this number and actual resolution.\n", resolution );
}
const char* aspect_ratio = "Unknown";
switch (aspect) {
case 0: aspect_ratio="4:3"; break;
case 1: aspect_ratio="16:9"; break;
}
fprintf(stdinfo, "aspect_ratio: %s", aspect_ratio );
if (STREQ("Unknown", aspect_ratio)) {
fprintf(stdinfo, ". (%d). Please report this number and actual aspect ratio.\n", aspect );
} else {
putc('\n', stdinfo);
p_video_attr->aspect = aspect + 2; /* DVD-Video aspect encoding */
}
const char* mode = "Unknown";
switch (compression) {
case 0: mode="MPEG1"; break;
case 1: mode="MPEG2"; break;
}
fprintf(stdinfo, "video_format: %s", mode );
if (STREQ("Unknown", mode)) {
p_video_attr->aspect = -1; /* Don't adjust aspect later for unknown formats */
fprintf(stdinfo, ". (%d). Please report this number and actual compression format.\n", compression );
} else {
putc('\n', stdinfo);
}
return true;
}
static bool parse_pgtm(pgtm_t pgtm, struct tm* tm)
{
bool ret=false;
uint16_t year = ((pgtm.pgtm[0] ) <<8 | (pgtm.pgtm[1] )) >> 2;
uint8_t month = (pgtm.pgtm[1] & 0x03) <<2 | (pgtm.pgtm[2] >> 6);
uint8_t day = (pgtm.pgtm[2] & 0x3E) >>1;
uint8_t hour = (pgtm.pgtm[2] & 0x01) <<4 | (pgtm.pgtm[3] >> 4);
uint8_t min = (pgtm.pgtm[3] & 0x0F) <<2 | (pgtm.pgtm[4] >> 6);
uint8_t sec = (pgtm.pgtm[4] & 0x3F);
if (year) {
tm->tm_year=year-1900;
tm->tm_mon=month-1;
tm->tm_mday=day;
tm->tm_hour=hour;
tm->tm_min=min;
tm->tm_sec=sec;
tm->tm_isdst=-1; /*Auto calc DST offset.*/
char date_str[32];
strftime(date_str,sizeof(date_str),"%F %T",tm); //locale = %x %X
fprintf(stdinfo, "date : %s\n",date_str);
ret=true;
} else {
fprintf(stdinfo, "date : not set\n");
}
return ret;
}
#ifndef NDEBUG
/* This is basically a simplification of find_program_text_info() */
static void print_psi(psi_gi_t* psi_gi)
{
putc('\n', stdinfo);
int ps;
uint16_t program_count = 0;
for (ps=0; psnr_of_psi; ps++) {
psi_t *psi = (psi_t*)(((char*)(psi_gi+1)) + (ps * sizeof(psi_t)));
uint16_t first_prog_num = ntohs(psi->first_prog_id); /* assuming this is first to play? */
uint16_t start_prog_num = program_count+1;
uint16_t num_progs_in_set = ntohs(psi->nr_of_programs);
program_count += num_progs_in_set;
fprintf(stdinfo, "Programs in Program set %d:", ps+1);
int program_id;
for (program_id = start_prog_num;
program_id < start_prog_num+num_progs_in_set;
program_id++) {
const char* fmt = (program_id==first_prog_num ? " (%d)" : " %d");
fprintf(stdinfo, fmt, program_id);
}
putc('\n', stdinfo);
}
putc('\n', stdinfo);
}
#endif//NDEBUG
/*
* FIXME: This assumes the programs occur linearly within
* the default program sets. This has been accurate for all
* discs I've seen so far at least. Note I've noticed a
* couple of "SONY_MOBILE" discs with no labels at all.
*/
static psi_t* find_program_text_info(psi_gi_t* psi_gi, int program)
{
int ps;
uint16_t program_count = 0;
for (ps=0; psnr_of_psi; ps++) {
psi_t *psi = (psi_t*)(((char*)(psi_gi+1)) + (ps * sizeof(psi_t)));
uint16_t start_prog_num;
/*
start_prog_num = ntohs(psi->first_prog_id);
We need to maintain program count as first_prog_id is often not stored,
as is the case for LG and "CIRRUS LOGIC" V1.1 discs for example (it's 0 or 0xFFFF).
Also I noticed a Sony disc that had programs sets with 2 programs in them, which
sometimes set the first_prog_id to the second program in the set. Perhaps
this field identifies the prog to start playing, as the first program in those
sets was a single VOBU that was generated due to a split.
Perhaps I should name the programs label.ps_id(001) when > 1 ps. */
start_prog_num = program_count+1;
uint16_t num_progs_in_set = ntohs(psi->nr_of_programs);
/* TODO: Perhaps have an option to merge all programs
in a program set to a vob using this info. That would assume
though that the programs were adjacent. */
program_count += num_progs_in_set;
uint16_t end_prog_num = start_prog_num + num_progs_in_set - 1;
if ((program >= start_prog_num) && (program <= end_prog_num)) {
return psi;
}
}
return (psi_t*)NULL;
}
/*
* This function controls the storage used by the actual
* encoding conversion routines. Note a len must be passed
* since the text fields are sometimes not NUL terminated.
*
* A string in the local encoding is returned which must be free()
*/
static char* text_field_convert(const char* field, unsigned int len)
{
unsigned int conv_max_len=len*MB_LEN_MAX+1/*NUL*/;
char* field_local=malloc(conv_max_len);
if (!field_local) {
fprintf(stderr, "Error allocating space for text conversion\n");
return NULL;
}
if (*field) {
char field_copy[len+1]; /* Copy as may not be NUL terminated */
field_copy[len] = '\0';
(void) strncpy(field_copy, field, len);
size_t srclen = strlen(field_copy) + 1; /* convert NUL also */
if (!text_convert(field_copy, srclen, field_local, conv_max_len)) {
free(field_local);
field_local=NULL;
}
} else {
*field_local='\0';
}
return field_local;
}
/* Filter redundant info */
static bool disc_info_redundant(const char* info)
{
const char* info_exclude_list[] = {
"DVD VR",
"DVD-VR",
" ",
"" /* must be last */
};
const char** info_to_exclude = info_exclude_list;
while (**info_to_exclude) {
if (STREQ(info, *info_to_exclude)) {
return true;
}
info_to_exclude++;
}
return false;
}
static void print_disc_info(rtav_vmgi_t* rtav_vmgi_ptr)
{
char* txt_local;
txt_local = text_field_convert(rtav_vmgi_ptr->mat.disc_info2,
sizeof(rtav_vmgi_ptr->mat.disc_info2));
if (txt_local && *txt_local && !disc_info_redundant(txt_local)) {
fprintf(stdinfo, "info : %s\n", txt_local);
}
free(txt_local);
if (strncmp(rtav_vmgi_ptr->mat.disc_info1,
rtav_vmgi_ptr->mat.disc_info2,
sizeof(rtav_vmgi_ptr->mat.disc_info1))) {
/* If there is a unique disc_info1 here, then there is
* no disc_info2 above on the discs I've seen so far */
txt_local = text_field_convert(rtav_vmgi_ptr->mat.disc_info1,
sizeof(rtav_vmgi_ptr->mat.disc_info1));
if (txt_local && *txt_local && !disc_info_redundant(txt_local)) {
fprintf(stdinfo, "info : %s\n", txt_local);
}
free(txt_local);
}
}
static char* mb_clean_name(const char* src)
{
size_t src_size = strlen (src) + 1;
wchar_t *str_wc = NULL;
size_t src_chars = mbstowcs (NULL, src, 0);
if (src_chars == (size_t) -1)
return NULL;
src_chars += 1; /* make space for NUL */
str_wc = malloc (src_chars * sizeof (wchar_t));
if (str_wc == NULL)
return NULL;
if (mbstowcs (str_wc, src, src_chars) <= 0) {
free(str_wc);
return NULL;
}
str_wc[src_chars - 1] = L'\0';
wchar_t* wc = str_wc;
while (*wc) {
size_t good = wcscspn(wc, L" /:?\\");
if (good)
wc+=good;
else
*wc++=L'-';
}
char* newstr = malloc (src_size);
if (newstr == NULL) {
free(str_wc);
return NULL;
}
(void) wcstombs(newstr, str_wc, src_size);
free(str_wc);
return newstr;
}
/* Must pass a string on the heap which may be modified inplace,
* or may be reallocated. */
static char* clean_name(char* src, bool mb_src)
{
if (mb_src && (MB_CUR_MAX > 1)) {
char* cleaned = mb_clean_name(src);
free(src);
return cleaned;
}
char* c = src;
while (*c) {
size_t good = strcspn(c, " /:?\\");
if (good)
c+=good;
else
*c++='-';
}
return src;
}
static char* get_label_base(const psi_t* psi)
{
char* title_local = text_field_convert(psi->title, sizeof(psi->title));
if (title_local && *title_local &&
strncmp(title_local, psi->label, sizeof(psi->label))) { /* if title != label */
title_local = clean_name(title_local, true);
if (!title_local) {
fprintf(stderr, "Error generating file name from title\n");
return NULL;
} else {
return title_local;
}
}
free(title_local);
const char* label=psi->label; /* ASCII */
if (*label && !STREQ(label, " ")) {
char* label_local = my_strndup(label, sizeof(psi->label));
label_local = clean_name(label_local, false);
return label_local;
}
return NULL;
}
static void print_label(const psi_t* psi)
{
const char* label=psi->label; /* ASCII */
char* title_local = text_field_convert(psi->title, sizeof(psi->title));
if (title_local && *title_local &&
strncmp(title_local, label, sizeof(psi->label))) { /* if title != label */
fprintf(stdinfo, "title: %s\n", title_local);
}
free(title_local);
if (*label && !STREQ(label, " ")) {
fprintf(stdinfo, "label: %.*s\n", (int)sizeof(psi->label), label);
}
}
/*********************************************************************************
* MPEG2 processing routines
*********************************************************************************/
/*
This is to both apply the aspect ratio from the IFO to the sequence header (0xB3)
and to reset the size of any sequence display extension packets (0xB5) to
the size of the video, as processing of this is not handled well by players.
For e.g., for 16:9 recordings my camcorder leaves the aspect in the sequence
header at 4:3 but sets the pan scan width and height appropriately in the
sequence display extension. ffmpeg for a short time used this to compute the
aspect, but because many MPEG streams incorrectly set the pan scan widths and
heights, it was changed back to ignoring these when determining the aspect ratio.
See: http://svn.ffmpeg.org/ffmpeg?view=rev&revision=15183
Specifically for widescreen PAL movies my DVD-VR camcorder set:
aspect = 2 (4:3)
width x height = 720 x 576
pan scan width x height = 540 x 576
*/
#define MPEG_HEADER_LEN 4
#define SEQUENCE_ID 0xB3
#define SEQUENCE_EXTENSION_ID 0xB5
#define VIDEO_STREAM_0 0xE0 /* I've only seen E0 on dvd-vr discs (E0-F possible) */
#define SEQUENCE_LEN 4 /* length of data we need to parse from sequence packet */
#define SEQUENCE_EXTENSION_LEN 5 /* length of data we need to parse from sequence extension packet */
#define VIDEO_STREAM_LEN 3 /* length of data we need to parse from video stream packet */
/* Return offset to header or -1 if not found */
static int find_mpeg_header(const uint8_t* buf, const unsigned int bs, const uint8_t type)
{
unsigned int offset=0;
uint32_t header = 0x00000100 + type;
NTOHL(header);
while (offset <= bs - sizeof (header)) {
if (*(uint32_t*)(buf+offset) == header)
return offset;
offset++;
}
return -1;
}
static int sequence_offset;
static uint8_t sequence_aspect;
/* reset cached values for each program */
static void init_mpeg2_cache(void)
{
sequence_offset = -1;
sequence_aspect = -1;
}
static p_video_attr_t get_sequence_aspect(const uint8_t* buf)
{
p_video_attr_t s_video_attr;
s_video_attr.width = s_video_attr.height = -1;
uint8_t aspect_byte = *(buf + sequence_offset + MPEG_HEADER_LEN + 3);
s_video_attr.aspect = aspect_byte >> 4;
return s_video_attr;
}
static void set_sequence_aspect(uint8_t* buf, const unsigned int offset, p_video_attr_t s_video_attr)
{
uint8_t aspect_byte = *(buf + offset + MPEG_HEADER_LEN + 3);
aspect_byte = (aspect_byte & 0x0F) | ((uint8_t) s_video_attr.aspect) << 4;
*(buf + sequence_offset + MPEG_HEADER_LEN + 3) = aspect_byte;
}
static p_video_attr_t get_sequence_display_extension_sizes(const uint8_t* buf, const unsigned int offset)
{
p_video_attr_t e_video_attr;
e_video_attr.aspect=-1;
uint8_t type = *(buf + offset + MPEG_HEADER_LEN);
int skip_colour=(type&0x01) ? 3 : 0;
const uint8_t* display_size = buf + offset + MPEG_HEADER_LEN + skip_colour + sizeof (type);
uint16_t horiz_disp_size = *(display_size) << 6;
horiz_disp_size += *(display_size+1) >> 2;
uint16_t vert_disp_size = (*(display_size+1) & 0x01) << 13;
vert_disp_size += *(display_size+2) << 5;
vert_disp_size += *(display_size+3) >> 3;
e_video_attr.width = horiz_disp_size;
e_video_attr.height = vert_disp_size;
return e_video_attr;
}
static void set_sequence_display_extension_sizes(uint8_t* buf, const unsigned int offset, p_video_attr_t e_video_attr)
{
uint16_t horiz_disp_size = e_video_attr.width;
uint16_t vert_disp_size = e_video_attr.height;
uint8_t type = *(buf + offset + MPEG_HEADER_LEN);
int skip_colour=(type&0x01) ? 3 : 0;
uint8_t* display_size = buf + offset + MPEG_HEADER_LEN + skip_colour + sizeof (type);
/* One could precalc this per program, but frequency is low so not worth the effort */
*(uint32_t*)display_size = htonl(0x00020000);
*(display_size) |= (horiz_disp_size >> 6);
*(display_size+1) |= (horiz_disp_size << 2);
*(display_size+1) |= ((vert_disp_size >> 13) & 0x01);
*(display_size+2) |= (vert_disp_size >> 5);
*(display_size+3) |= (vert_disp_size << 3);
}
static void check_mpeg_encryption(uint8_t* buf, const unsigned int bs, const unsigned int program)
{
/* Note we'll warn below if we've not seen any video stream (E0 doesn't match).
* Note also I've only seen AC-3 audio on 0xBD and it has also been
* encrypted on discs I've seen. */
if (ifo_program_attrs[program].scrambled != PARTIALLY_SCRAMBLED) {
int pes_offset = find_mpeg_header(buf, bs-VIDEO_STREAM_LEN, VIDEO_STREAM_0);
if (pes_offset >= 0) {
/* extension header is always available for 0xBD and 0xE? types */
uint8_t scramble_byte = *(buf + pes_offset + MPEG_HEADER_LEN + 2);
bool scrambled;
if ((scramble_byte & 0xC0) == 0x80) { /* MPEG2 */
scrambled = scramble_byte & 0x30;
} else {
scrambled = false; /* assuming MPEG1 doesn't support encryption */
}
if (ifo_program_attrs[program].scrambled != SCRAMBLED_UNSET &&
ifo_program_attrs[program].scrambled != scrambled) {
ifo_program_attrs[program].scrambled = PARTIALLY_SCRAMBLED;
} else {
ifo_program_attrs[program].scrambled = scrambled;
}
}
}
}
static void fix_mpeg2_aspect(uint8_t* buf, const unsigned int bs, const unsigned int program)
{
static int sector;
bool found_sequence_header = false;
const bool look_harder = false; /* Should never need to be set to true as far as I can see */
p_video_attr_t ifo_video_attr = ifo_video_attrs[ifo_program_attrs[program].video_attr];
if (ifo_video_attr.aspect < 2) {
sector++;
return;
}
p_video_attr_t s_video_attr = { .aspect=ifo_video_attr.aspect, .width=-1, .height=-1 };
if (sequence_offset == -1) {
if ((sequence_offset = find_mpeg_header(buf, bs-SEQUENCE_LEN, SEQUENCE_ID)) >= 0) {
found_sequence_header = true;
#ifndef NDEBUG
fprintf(stdinfo,"Found SH @ %d+%d\n", sector, sequence_offset);
#endif
sequence_aspect = get_sequence_aspect(buf).aspect;
if (sequence_aspect != ifo_video_attr.aspect) {
set_sequence_aspect(buf, sequence_offset, s_video_attr);
}
}
} else {
if (find_mpeg_header(buf+sequence_offset, MPEG_HEADER_LEN, SEQUENCE_ID)==0) {
found_sequence_header = true;
#ifndef NDEBUG
fprintf(stdinfo,"Found SH @ %d+%d\n", sector, sequence_offset);
#endif
if (sequence_aspect!=ifo_video_attr.aspect)
set_sequence_aspect(buf, sequence_offset, s_video_attr);
} else if (look_harder) {
/* I can't see why the sequence headers would be at arbitrary offsets in each sector,
* and I've analyzed about 10 different VROs and they all have the same offsets.
* So I think that doing this will only redundantly scan every byte of sectors
* without sequence headers. */
int curr_offset = find_mpeg_header(buf, bs-SEQUENCE_LEN, SEQUENCE_ID);
if (curr_offset >= 0) {
found_sequence_header = true;
sequence_offset = curr_offset;
#ifndef NDEBUG
fprintf(stdinfo,"Found SH @ %d+%d\n", sector, sequence_offset);
#endif
set_sequence_aspect(buf, sequence_offset, s_video_attr);
} else {
}
}
}
/* As an optimization, only look for sequence display extension, if there
* is a sequence header in this sector. */
if (found_sequence_header || look_harder) {
if (ifo_video_attr.width <= 0 || ifo_video_attr.height <= 0) {
sector++;
return;
}
int extension_offset = look_harder ? 0 : sequence_offset + MPEG_HEADER_LEN + SEQUENCE_LEN;
int next_offset;
while ((next_offset = find_mpeg_header(buf + extension_offset,
bs - extension_offset - SEQUENCE_EXTENSION_LEN,
SEQUENCE_EXTENSION_ID)) >= 0) {
extension_offset += next_offset;
uint8_t type = *(buf + extension_offset + MPEG_HEADER_LEN);
if ((type&0xF0) == 0x20) {
p_video_attr_t e_video_attr = get_sequence_display_extension_sizes(buf, extension_offset);
#ifndef NDEBUG
fprintf(stdinfo, "Found SDE @ %d+%d (%d x %d)\n", sector, extension_offset, e_video_attr.width, e_video_attr.height);
#endif
e_video_attr.width = ifo_video_attr.width;
e_video_attr.height = ifo_video_attr.height;
set_sequence_display_extension_sizes(buf, extension_offset, e_video_attr);
#ifndef NDEBUG
e_video_attr = get_sequence_display_extension_sizes(buf, extension_offset);
fprintf(stdinfo, "New SDE @ %d+%d (%d x %d)\n", sector, extension_offset, e_video_attr.width, e_video_attr.height);
#endif
break; /* Should be only 1 of these per sector */
} else {
#ifndef NDEBUG
fprintf(stdinfo, "Found SE @ %d+%d (type=%d)\n", sector, extension_offset, (type&0xF0)>>4);
#endif
}
extension_offset++;
}
}
sector++;
}
/* Haven't had a request to do this yet:
http://forum.doom9.org/archive/index.php/t-102969.html
Note code there makes incorrect assumptions about offsets I think. */
static void add_mpeg_nav(uint8_t* buf, const unsigned int bs)
{
(void) buf; (void) bs;
}
void process_mpeg2(uint8_t* buf, const unsigned int bs, void* program)
{
fix_mpeg2_aspect(buf, bs, *(const unsigned int*)program);
add_mpeg_nav(buf, bs);
check_mpeg_encryption(buf, bs, *(const unsigned int*)program);
}
/*********************************************************************************
*
*********************************************************************************/
unsigned long required_program=0; /* process all programs by default */
const char* ifo_name=NULL;
const char* vro_name=NULL;
static void usage(char** argv, int error)
{
FILE* where = error==EXIT_FAILURE ? stderr : stdout;
fprintf(where, "Usage: %s [OPTION]... VR_MANGR.IFO [VR_MOVIE.VRO]\n"
"Print info about and optionally extract vob data from DVD-VR files.\n"
"\n"
"If the VRO file is specified, the component programs are\n"
"extracted to the current directory or to stdout.\n"
"\n"
" -p, --program=NUM Only process program NUM rather than all programs.\n"
"\n"
" -n, --name=NAME Specify a basename to use for extracted vob files\n"
" rather than using one based on the timestamp.\n"
" If you pass `-' the vob files will be written to stdout.\n"
" If you pass `[label]' the names will be based on\n"
" a sanitized version of the title or label.\n"
"\n"
" --help Display this help and exit.\n"
" --version Output version information and exit.\n"
,argv[0]);
exit(error);
}
static void get_options(int argc, char** argv)
{
static struct option const longopts[] =
{
/* I'm using capitals for long options
* without a corresponding short option. */
{"program", required_argument, NULL, 'p'},
{"name", required_argument, NULL, 'n'},
{"help", no_argument, NULL, 'H'},
{"version", no_argument, NULL, 'V'},
{NULL, 0, NULL, 0}
};
int opt;
while ((opt = getopt_long(argc, argv, "p:n:", longopts, NULL)) != -1) {
switch (opt) {
case 'p': {
char* trailing;
required_program = strtoul(optarg, &trailing, 10);
if (*trailing) {
usage(argv, EXIT_FAILURE);
}
break;
}
case 'n':
base_name = optarg;
break;
case 'V':
printf("dvd-vr "VERSION);
printf("\n\nWritten by Pádraig Brady \n");
exit(EXIT_SUCCESS);
break;
case 'H':
usage(argv, EXIT_SUCCESS);
break;
default: /* '?',':' */
usage(argv, EXIT_FAILURE);
break;
}
}
if (optind >= argc || /* no files specified */
argc > optind+2) { /* too many files specified */
usage(argv, EXIT_FAILURE);
}
ifo_name=argv[optind++];
if (optind < argc) {
vro_name=argv[optind++];
}
if (!STREQ(base_name, TIMESTAMP_FMT) && !vro_name) {
usage(argv, EXIT_FAILURE);
}
}
int main(int argc, char** argv)
{
setlocale(LC_ALL,"");
sys_charset=get_charset();
get_options(argc, argv);
if (STREQ(base_name, "-")) {
stdinfo = stderr;
} else {
stdinfo = stdout; /* allow users to grep metadata etc. */
}
int fd=open(ifo_name,O_RDONLY);
if (fd == -1) {
fprintf(stderr, "Error opening [%s] (%s)\n", ifo_name, strerror(errno));
exit(EXIT_FAILURE);
}
rtav_vmgi_t* rtav_vmgi_ptr=mmap(0,sizeof(rtav_vmgi_t),PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if (rtav_vmgi_ptr == MAP_FAILED) {
fprintf(stderr, "Failed to MMAP ifo file (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
if (strncmp("DVD_RTR_VMG0",rtav_vmgi_ptr->mat.id,sizeof(rtav_vmgi_ptr->mat.id))) {
fprintf(stderr, "invalid DVD-VR IFO identifier\n");
exit(EXIT_FAILURE);
}
uint32_t vmg_size = NTOHL(rtav_vmgi_ptr->mat.vmg_ea) + 1;
if (munmap(rtav_vmgi_ptr, sizeof(rtav_vmgi_t)) !=0) {
fprintf(stderr, "Failed to unmap ifo file (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
rtav_vmgi_ptr=mmap(0,vmg_size,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
if (rtav_vmgi_ptr == MAP_FAILED) {
fprintf(stderr, "Failed to re MMAP ifo file (%s)\n", strerror(errno));
exit(EXIT_FAILURE);
}
int vro_fd=-1;
if (vro_name) {
vro_fd=open(vro_name,O_RDONLY);
if (vro_fd == -1) {
fprintf(stderr, "Error opening [%s] (%s)\n", vro_name, strerror(errno));
exit(EXIT_FAILURE);
}
#ifdef POSIX_FADV_SEQUENTIAL
posix_fadvise(vro_fd, 0, 0, POSIX_FADV_SEQUENTIAL);/* More readahead done */
#endif //POSIX_FADV_SEQUENTIAL
}
NTOHS(rtav_vmgi_ptr->mat.version);
rtav_vmgi_ptr->mat.version &= 0x00FF;
fprintf(stdinfo, "format: DVD-VR V%d.%d\n",
rtav_vmgi_ptr->mat.version>>4,rtav_vmgi_ptr->mat.version&0x0F);
if (rtav_vmgi_ptr->mat.cprm.supported) {
fprintf(stdinfo, "Encryption: CPRM supported\n");
/* Note programs may not actually be encrypted.
* That's indicated per AV pack in 2 PES scrambling control bits */
}
disc_charset=parse_txt_encoding(rtav_vmgi_ptr->mat.txt_encoding);
print_disc_info(rtav_vmgi_ptr);
NTOHL(rtav_vmgi_ptr->mat.pgit_sa);
pgiti_t* pgiti = (pgiti_t*) ((char*)rtav_vmgi_ptr + rtav_vmgi_ptr->mat.pgit_sa);
NTOHL(pgiti->pgit_ea);
NTOHL(rtav_vmgi_ptr->mat.def_psi_sa);
psi_gi_t *def_psi_gi = (psi_gi_t*) ((char*)rtav_vmgi_ptr + rtav_vmgi_ptr->mat.def_psi_sa);
#ifndef NDEBUG
NTOHS(def_psi_gi->nr_of_programs);
if ((def_psi_gi->nr_of_psi > 1) && (def_psi_gi->nr_of_psi != def_psi_gi->nr_of_programs)) {
print_psi(def_psi_gi);
}
fprintf(stdinfo, "Number of info tables for VRO: %d\n",pgiti->nr_of_pgi);
fprintf(stdinfo, "Number of vob formats: %d\n",pgiti->nr_of_vob_formats);
fprintf(stdinfo, "pgit_ea: %08"PRIX32"\n",pgiti->pgit_ea);
#endif//NDEBUG
if (pgiti->nr_of_pgi == 0) {
fprintf(stderr, "Error: couldn't find info table for VRO\n");
exit(EXIT_FAILURE);
}
if (pgiti->nr_of_pgi > 1) {
fprintf(stderr, "Warning: Only processing 1 of the %"PRIu8" VRO info tables\n",
pgiti->nr_of_pgi);
}
vob_format_t* vob_format = (vob_format_t*) (pgiti+1);
int vob_type;
int vob_types=pgiti->nr_of_vob_formats;
ifo_video_attrs=malloc(vob_types * sizeof(p_video_attr_t));
if (!ifo_video_attrs) {
fprintf(stderr, "Error allocating space for video type attributes\n");
exit(EXIT_FAILURE);
}
for (vob_type=0; vob_type1) {
fprintf(stdinfo, "VOB format %d...\n",vob_type+1);
}
NTOHS(vob_format->video_attr);
if (!parse_video_attr(vob_format->video_attr, &ifo_video_attrs[vob_type])) {
fprintf(stderr, "Error parsing video_attr\n");
}
if (!parse_audio_attr(vob_format->audio_attr0)) {
fprintf(stderr, "Error parsing audio_attr0\n");
}
vob_format++;
}
pgi_gi_t* pgi_gi = (pgi_gi_t*) vob_format;
NTOHS(pgi_gi->nr_of_programs);
fprintf(stdinfo, "\nNumber of programs: %d\n", pgi_gi->nr_of_programs);
if (required_program && required_program>pgi_gi->nr_of_programs) {
fprintf(stderr, "Error: couldn't find specified program (%lu)\n", required_program);
exit(EXIT_FAILURE);
}
ifo_program_attrs=malloc(pgi_gi->nr_of_programs * sizeof(p_program_attr_t));
if (!ifo_program_attrs) {
fprintf(stderr, "Error allocating space for program attributes\n");
exit(EXIT_FAILURE);
}
struct tm now_tm;
time_t now=time(0);
(void) gmtime_r(&now, &now_tm);//used if no timestamp in program
unsigned int program;
typedef uint32_t vvobi_sa_t;
vvobi_sa_t* vvobi_sa=(vvobi_sa_t*)(pgi_gi+1);
for (program=0; programnr_of_programs; program++) {
if (required_program && program+1!=required_program) {
vvobi_sa++;
continue;
}
NTOHL(*vvobi_sa);
putc('\n', stdinfo);
fprintf(stdinfo, "num : %d\n", program+1);
psi_t* psi=find_program_text_info(def_psi_gi, program+1);
if (psi) {
print_label(psi);
} else {
fprintf(stdinfo, "label: Couldn't find. Please report.\n");
}
#ifndef NDEBUG
fprintf(stdinfo, "VVOB info (%d) address: %"PRIu32"\n",program+1,*vvobi_sa);
#endif//NDEBUG
vvob_t* vvob = (vvob_t*) (((uint8_t*)pgiti) + *vvobi_sa);
struct tm tm;
bool ts_ok = parse_pgtm(vvob->vob_timestamp,&tm);
char vob_base[(sizeof(psi->title)*MB_LEN_MAX)+4/*#123*/+1/*NUL*/];
if (STREQ(base_name, TIMESTAMP_FMT)) { //use timestamp to give unique filename
if (ts_ok) {
strftime(vob_base,sizeof(vob_base),TIMESTAMP_FMT,&tm);
} else { //use now + program num to give unique name
strftime(vob_base,sizeof(vob_base),TIMESTAMP_FMT,&now_tm);
int datelen=strlen(vob_base);
(void) snprintf(vob_base+datelen, sizeof(vob_base)-datelen, "#%03d", program+1);
}
} else if (STREQ(base_name, "[label]")) { //use the label to generate filename
if (!psi) {
fprintf(stderr, "Error: Couldn't generate name based on label\n");
exit(EXIT_FAILURE);
}
char* label_base = get_label_base(psi);
if (!label_base) {
fprintf(stderr, "Error: Couldn't generate name based on empty label\n");
exit(EXIT_FAILURE);
}
unsigned int ret = snprintf(vob_base, sizeof(vob_base), "%s#%03d", label_base, program+1);
if (ret >= sizeof(vob_base)) { /* Shouldn't happen. */
fprintf(stderr, "Error: label is too long\n");
exit(EXIT_FAILURE);
}
free(label_base);
} else {
unsigned int ret = snprintf(vob_base, sizeof(vob_base), "%s#%03d", base_name, program+1);
if (ret >= sizeof(vob_base)) {
fprintf(stderr, "Error: Specified basename is too long (>%zu)\n", sizeof(vob_base)-4);
exit(EXIT_FAILURE);
}
}
int vob_fd=-1;
char vob_name[sizeof(vob_base)+32];
if (vro_fd!=-1) {
if (STREQ(base_name, "-")) {
vob_fd=fileno(stdout);
} else {
(void) snprintf(vob_name,sizeof(vob_name),"%s.vob",vob_base);
vob_fd=open(vob_name,O_WRONLY|O_CREAT|O_EXCL,0666);
if (vob_fd == -1 && errno == EEXIST && STREQ(base_name, TIMESTAMP_FMT)) {
/* JVC DVD recorder can generate duplicate timestamps at least :( */
/* FIXME: The second time ripping a disc will duplicate the first VOB with duplicate timestamp.
* Would need to scan all program info first and change format if any duplicate timestamps. */
(void) snprintf(vob_name,sizeof(vob_name),"%s#%03d.vob",vob_base, program+1);
vob_fd=open(vob_name,O_WRONLY|O_CREAT|O_EXCL,0666);
}
}
if (vob_fd == -1) {
fprintf(stderr, "Error opening [%s] (%s)\n", vob_name, strerror(errno));
vvobi_sa++;
continue;
}
}
if (vob_types>1) {
fprintf(stdinfo, "vob format: %d\n", vvob->vob_format_id);
}
ifo_program_attrs[program].video_attr = vvob->vob_format_id-1;
ifo_program_attrs[program].scrambled = SCRAMBLED_UNSET;
NTOHS(vvob->vob_attr);
int skip=0;
if (vvob->vob_attr & 0x80) {
skip+=sizeof(adj_vob_t);
#ifndef NDEBUG
fprintf(stdinfo, "skipping adjacent VOB info\n");
#endif//NDEBUG
}
skip+=sizeof(uint16_t); /* ?? */
vobu_map_t* vobu_map = (vobu_map_t*) (((uint8_t*)(vvob+1)) + skip);
NTOHS(vobu_map->nr_of_time_info);
NTOHS(vobu_map->nr_of_vobu_info);
NTOHS(vobu_map->time_offset);
NTOHL(vobu_map->vob_offset);
#ifndef NDEBUG
fprintf(stdinfo, "num time infos: %"PRIu16"\n",vobu_map->nr_of_time_info);
fprintf(stdinfo, "num VOBUs: %"PRIu16"\n",vobu_map->nr_of_vobu_info);
fprintf(stdinfo, "time offset: %"PRIu16"\n",vobu_map->time_offset); /* What units? */
fprintf(stdinfo, "vob offset: %"PRIu32"*%d\n",vobu_map->vob_offset,DVD_SECTOR_SIZE); /* offset in the VRO file of the VOB */
#endif//NDEBUG
off_t vob_offset = vobu_map->vob_offset;
if (vob_offset > OFF_T_MAX / DVD_SECTOR_SIZE)
{
fprintf(stderr, "Overflow in extracting VOB at offset %"PRIu32"*%d\n",vobu_map->vob_offset,DVD_SECTOR_SIZE);
exit(EXIT_FAILURE);
}
vob_offset *= DVD_SECTOR_SIZE;
if (vro_fd!=-1) {
if (lseek(vro_fd, vob_offset, SEEK_SET)==(off_t)-1) {
fprintf(stderr, "Error seeking within VRO [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
vobu_info_t* vobu_info = (vobu_info_t*) (((uint8_t*)(vobu_map+1)) + vobu_map->nr_of_time_info*sizeof(time_info_t));
int vobus;
uint64_t tot=0;
int display_char;
bool processed_some_video = false;
int error=0;
if (vro_fd != -1) {
percent_display(PERCENT_START, 0, 0);
init_mpeg2_cache();
}
for (vobus=0; vobusnr_of_vobu_info; vobus++) {
uint16_t vobu_size = vobu_info->vobu_size;
NTOHS(vobu_size); vobu_size&=0x03FF;
if (vro_fd != -1) {
off_t curr_offset = lseek(vro_fd, 0, SEEK_CUR);
if (curr_offset == (off_t)-1) {
fprintf(stderr, "Error determining VRO offset [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
int ret = stream_data(vro_fd, vob_fd, vobu_size, DVD_SECTOR_SIZE, process_mpeg2, &program);
if (ret == -2) { /* write error */
exit(EXIT_FAILURE);
} else if (ret == -1) { /* read error */
display_char='X';
error=1;
off_t new_offset = lseek(vro_fd, 0, SEEK_CUR);
if (new_offset == (off_t)-1) {
fprintf(stderr, "Error determining VRO offset [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
off_t skip_len = (curr_offset + vobu_size*DVD_SECTOR_SIZE) - new_offset;
if (skip_len) {
#ifndef NDEBUG
fprintf(stderr, "Warning: Skipping %"PRIdMAX" bytes\n", skip_len);
/* Note we mark the whole VOBU as bad not just this skip len */
#endif//NDEBUG
if (lseek(vro_fd, skip_len, SEEK_CUR) == (off_t)-1) {
fprintf(stderr, "Error skipping in VRO [%s]\n", strerror(errno));
exit(EXIT_FAILURE);
}
}
} else if (ifo_program_attrs[program].scrambled == SCRAMBLED ||
ifo_program_attrs[program].scrambled == PARTIALLY_SCRAMBLED) {
display_char='E';
processed_some_video = true;
} else {
display_char=0; /* default */
processed_some_video = true;
}
int percent=((vobus+1)*100)/vobu_map->nr_of_vobu_info;
percent_display(PERCENT_UPDATE, percent, display_char);
}
tot+=vobu_size;
vobu_info++;
}
if (vro_fd != -1) {
if (!error) {
percent_display(PERCENT_END, 0, 0);
} else {
/* Leave the percent display showing read errors */
putc('\n', stderr);
}
if (vob_fd != fileno(stdout)) {
close(vob_fd);
touch(vob_name, &tm);
}
}
fprintf(stdinfo, "size : %'"PRIu64"\n",tot*DVD_SECTOR_SIZE);
if (ifo_program_attrs[program].scrambled == SCRAMBLED) {
fprintf(stderr, "Warning: program is encrypted\n");
} else if (ifo_program_attrs[program].scrambled == PARTIALLY_SCRAMBLED) {
fprintf(stderr, "Warning: program is partially encrypted\n");
} else if (ifo_program_attrs[program].scrambled == SCRAMBLED_UNSET && processed_some_video) {
fprintf(stderr, "Warning: didn't detect a video stream, please report\n");
fprintf(stderr, " (preferably with a sample vob file)\n");
}
vvobi_sa++;
}
free(ifo_program_attrs);
free(ifo_video_attrs);
munmap(rtav_vmgi_ptr, vmg_size);
close(fd);
if (vro_fd != -1)
close(vro_fd);
return EXIT_SUCCESS;
}