#include "dvdnav/dvdnav.h"
#include "dvdread/ifo_read.h"
+#include "dvdread/ifo_print.h"
#include "dvdread/nav_read.h"
#define DVD_READ_CACHE 1
hb_dvdnav_set_angle
};
+// there can be at most 999 PGCs per title. round that up to the nearest
+// power of two.
+#define MAX_PGCN 1024
+
/***********************************************************************
* Local prototypes
**********************************************************************/
-static void PgcWalkInit( void );
+static void PgcWalkInit( uint32_t pgcn_map[MAX_PGCN/32] );
static int FindChapterIndex( hb_list_t * list, int pgcn, int pgn );
-static int NextPgcn( ifo_handle_t *ifo, int pgcn );
+static int NextPgcn( ifo_handle_t *ifo, int pgcn, uint32_t pgcn_map[MAX_PGCN/32] );
static int FindNextCell( pgc_t *pgc, int cell_cur );
static int dvdtime2msec( dvd_time_t * );
{
hb_dvd_t * e;
hb_dvdnav_t * d;
+ int region_mask;
e = calloc( sizeof( hb_dvd_t ), 1 );
d = &(e->dvdnav);
+ /* Log DVD drive region code */
+ if ( hb_dvd_region( path, ®ion_mask ) == 0 )
+ {
+ hb_log( "dvd: Region mask 0x%02x", region_mask );
+ if ( region_mask == 0xFF )
+ {
+ hb_log( "dvd: Warning, DVD device has no region set" );
+ }
+ }
+
/* Open device */
if( dvdnav_open(&d->dvdnav, path) != DVDNAV_STATUS_OK )
{
int i;
// Initialize map of visited pgc's to prevent loops
- PgcWalkInit();
+ uint32_t pgcn_map[MAX_PGCN/32];
+ PgcWalkInit( pgcn_map );
pgcn = ifo->vts_ptt_srpt->title[ttn-1].ptt[pttn-1].pgcn;
pgn = ifo->vts_ptt_srpt->title[ttn-1].ptt[pttn-1].pgn;
- if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
+ if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp || pgcn >= MAX_PGCN)
{
hb_error( "invalid PGC ID %d, skipping", pgcn );
return 0;
hb_error( "scan: pgc not valid, skipping" );
break;
}
+ if (pgn > pgc->nr_of_programs)
+ {
+ pgn = 1;
+ continue;
+ }
+
duration += 90LL * dvdtime2msec( &pgc->playback_time );
cell_start = pgc->program_map[pgn-1] - 1;
}
*last_pgcn = pgcn;
pgn = 1;
- } while((pgcn = NextPgcn(ifo, pgcn)) != 0);
+ } while((pgcn = NextPgcn(ifo, pgcn, pgcn_map)) != 0);
return duration;
}
hb_log( "scan: scanning title %d", t );
title = hb_title_init( d->path, t );
+ title->type = HB_DVD_TYPE;
if (dvdnav_get_title_string(d->dvdnav, &name) == DVDNAV_STATUS_OK)
{
strncpy( title->name, name, sizeof( title->name ) );
/* Get pgc */
- if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
+ if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp || pgcn >= MAX_PGCN)
{
hb_error( "invalid PGC ID %d for title %d, skipping", pgcn, t );
goto fail;
pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
- hb_log("pgc_id: %d, pgn: %d: pgc: 0x%x", pgcn, pgn, pgc);
+ hb_log("pgc_id: %d, pgn: %d: pgc: %p", pgcn, pgn, pgc);
+ if (pgn > pgc->nr_of_programs)
+ {
+ hb_error( "invalid PGN %d for title %d, skipping", pgn, t );
+ goto fail;
+ }
/* Title start */
title->cell_start = pgc->program_map[pgn-1] - 1;
title->hours = title->duration / 90000 / 3600;
title->minutes = ( ( title->duration / 90000 ) % 3600 ) / 60;
title->seconds = ( title->duration / 90000 ) % 60;
- hb_log( "scan: duration is %02d:%02d:%02d (%lld ms)",
+ hb_log( "scan: duration is %02d:%02d:%02d (%"PRId64" ms)",
title->hours, title->minutes, title->seconds,
title->duration / 90 );
hb_list_add( title->list_audio, audio );
}
- if( !hb_list_count( title->list_audio ) )
- {
- hb_log( "scan: ignoring title (no audio track)" );
- goto fail;
- }
-
memcpy( title->palette,
ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette,
16 * sizeof( uint32_t ) );
lang = lang_for_code( ifo->vtsi_mat->vts_subp_attr[i].lang_code );
subtitle = calloc( sizeof( hb_subtitle_t ), 1 );
+ subtitle->track = i+1;
subtitle->id = ( ( 0x20 + position ) << 8 ) | 0xbd;
snprintf( subtitle->lang, sizeof( subtitle->lang ), "%s",
strlen(lang->native_name) ? lang->native_name : lang->eng_name);
snprintf( subtitle->iso639_2, sizeof( subtitle->iso639_2 ), "%s",
lang->iso639_2);
+ subtitle->format = PICTURESUB;
+ subtitle->source = VOBSUB;
+ subtitle->config.dest = RENDERSUB; // By default render (burn-in) the VOBSUB.
subtitle->type = lang_extension;
}
/* Chapters */
- PgcWalkInit();
+ uint32_t pgcn_map[MAX_PGCN/32];
+ PgcWalkInit( pgcn_map );
c = 0;
do
{
}
pgn = 1;
- } while ((pgcn = NextPgcn(ifo, pgcn)) != 0);
+ } while ((pgcn = NextPgcn(ifo, pgcn, pgcn_map)) != 0);
hb_log( "scan: title %d has %d chapters", t, c );
count = hb_list_count( title->list_chapter );
for (i = 0; i < count; i++)
{
- int pgcn_next, pgn_next;
- pgc_t * pgc_next;
- hb_chapter_t *chapter_next;
- int pgc_cell_end;
-
chapter = hb_list_item( title->list_chapter, i );
pgcn = chapter->pgcn;
/* Start cell */
chapter->cell_start = pgc->program_map[pgn-1] - 1;
- chapter->block_start =
- pgc->cell_playback[chapter->cell_start].first_sector;
- pgc_cell_end = pgc->nr_of_cells - 1;
-
- /* End cell */
- if( i < count - 1 )
+ chapter->block_start = pgc->cell_playback[chapter->cell_start].first_sector;
+ // if there are no more programs in this pgc, the end cell is the
+ // last cell. Otherwise it's the cell before the start cell of the
+ // next program.
+ if ( pgn == pgc->nr_of_programs )
{
- /* The cell before the starting cell of the next chapter,
- or... */
- chapter_next = hb_list_item( title->list_chapter, i+1 );
- pgcn_next = chapter_next->pgcn;
- pgn_next = chapter_next->pgn;
- pgc_next = ifo->vts_pgcit->pgci_srp[pgcn_next-1].pgc;
- if (pgcn_next == pgcn)
- {
- chapter->cell_end = pgc_next->program_map[pgn_next-1] - 2;
- }
- else
- {
- chapter->cell_end = pgc_cell_end;
- }
- if( chapter->cell_end < 0 )
- {
- /* Huh? */
- free( chapter );
- continue;
- }
+ chapter->cell_end = pgc->nr_of_cells - 1;
}
else
{
- /* ... the last cell of the title */
- chapter->cell_end = pgc_cell_end;
+ chapter->cell_end = pgc->program_map[pgn] - 2;;
}
- chapter->block_end =
- pgc->cell_playback[chapter->cell_end].last_sector;
+ chapter->block_end = pgc->cell_playback[chapter->cell_end].last_sector;
/* Block count, duration */
chapter->block_count = 0;
chapter->minutes = ( seconds % 3600 ) / 60;
chapter->seconds = seconds % 60;
- hb_log( "scan: chap %d c=%d->%d, b=%d->%d (%d), %lld ms",
+ hb_log( "scan: chap %d c=%d->%d, b=%d->%d (%d), %"PRId64" ms",
chapter->index, chapter->cell_start, chapter->cell_end,
chapter->block_start, chapter->block_end,
chapter->block_count, chapter->duration / 90 );
}
d->title = t;
d->stopped = 0;
+ d->chapter = 0;
return 1;
}
static int hb_dvdnav_seek( hb_dvd_t * e, float f )
{
hb_dvdnav_t * d = &(e->dvdnav);
- uint64_t sector;
+ uint64_t sector = f * d->title_block_count;
int result, event, len;
uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
int done = 0, ii;
return 0;
}
+ // XXX the current version of libdvdnav can't seek outside the current
+ // PGC. Check if the place we're seeking to is in a different
+ // PGC. Position there & adjust the offset if so.
+ uint64_t pgc_offset = 0;
+ uint64_t chap_offset = 0;
+ hb_chapter_t *pgc_change = hb_list_item(d->list_chapter, 0 );
+ for ( ii = 0; ii < hb_list_count( d->list_chapter ); ++ii )
+ {
+ hb_chapter_t *chapter = hb_list_item( d->list_chapter, ii );
+ uint64_t chap_len = chapter->block_end - chapter->block_start + 1;
+
+ if ( chapter->pgcn != pgc_change->pgcn )
+ {
+ // this chapter's in a different pgc from the previous - note the
+ // change so we can make sector offset's be pgc relative.
+ pgc_offset = chap_offset;
+ pgc_change = chapter;
+ }
+ if ( chap_offset <= sector && sector < chap_offset + chap_len )
+ {
+ // this chapter contains the sector we want - see if it's in a
+ // different pgc than the one we're currently in.
+ int32_t title, pgcn, pgn;
+ if (dvdnav_current_title_program( d->dvdnav, &title, &pgcn, &pgn ) != DVDNAV_STATUS_OK)
+ hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
+ if ( d->title != title || chapter->pgcn != pgcn )
+ {
+ // this chapter is in a different pgc - switch to it.
+ if (dvdnav_program_play(d->dvdnav, d->title, chapter->pgcn, chapter->pgn) != DVDNAV_STATUS_OK)
+ hb_log("dvdnav prog play err: %s", dvdnav_err_to_string(d->dvdnav));
+ }
+ // seek sectors are pgc-relative so remove the pgc start sector.
+ sector -= pgc_offset;
+ break;
+ }
+ chap_offset += chap_len;
+ }
+
// dvdnav will not let you seek or poll current position
// till it reaches a certain point in parsing. so we
// have to get blocks until we reach a cell
result = dvdnav_get_next_block( d->dvdnav, buf, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
- hb_log("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
+ hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
return 0;
}
switch ( event )
case DVDNAV_BLOCK_OK:
case DVDNAV_CELL_CHANGE:
done = 1;
+ break;
case DVDNAV_STILL_FRAME:
dvdnav_still_skip( d->dvdnav );
default:
break;
}
- } while (!(event == DVDNAV_CELL_CHANGE || event == DVDNAV_BLOCK_OK));
+ }
- sector = f * d->title_block_count;
if (dvdnav_sector_search(d->dvdnav, sector, SEEK_SET) != DVDNAV_STATUS_OK)
{
hb_error( "dvd: dvdnav_sector_search failed - %s",
hb_dvdnav_t * d = &(e->dvdnav);
int result, event, len;
int chapter = 0;
+ int error_count = 0;
while ( 1 )
{
result = dvdnav_get_next_block( d->dvdnav, b->data, &event, &len );
if ( result == DVDNAV_STATUS_ERR )
{
- hb_log("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
- return 0;
+ hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
+ if (dvdnav_sector_search(d->dvdnav, 1, SEEK_CUR) != DVDNAV_STATUS_OK)
+ {
+ hb_error( "dvd: dvdnav_sector_search failed - %s",
+ dvdnav_err_to_string(d->dvdnav) );
+ return 0;
+ }
+ error_count++;
+ if (error_count > 10)
+ {
+ hb_error("dvdnav: Error, too many consecutive read errors");
+ return 0;
+ }
+ continue;
}
switch ( event )
{
if (chapter > 1)
b->new_chap = chapter;
chapter = 0;
+ error_count = 0;
return 1;
case DVDNAV_NOP:
return 0;
}
c = FindChapterIndex(d->list_chapter, pgcn, pgn);
- if (c > d->chapter)
+ if (c != d->chapter)
+ {
+ if (c < d->chapter)
+ {
+ // Some titles end with a 'link' back to the beginning so
+ // a transition to an earlier chapter means we're done.
+ return 0;
+ }
chapter = d->chapter = c;
+ }
}
break;
return cell_next;
}
-static uint8_t pgcn_map[1280];
-
/***********************************************************************
* NextPgcn
***********************************************************************
* Assumes pgc and cell_cur are correctly set, and sets cell_next to the
* cell to be read when we will be done with cell_cur.
+ * Since pg chains can be circularly linked (either from a read error or
+ * deliberately) pgcn_map tracks program chains we've already seen.
**********************************************************************/
-static int NextPgcn( ifo_handle_t *ifo, int pgcn )
+static int NextPgcn( ifo_handle_t *ifo, int pgcn, uint32_t pgcn_map[MAX_PGCN/32] )
{
- int byte;
- uint8_t bit;
int next_pgcn;
pgc_t *pgc;
- byte = pgcn >> 3;
- bit = 1 << (pgcn & 0x7);
- pgcn_map[byte] |= bit;
+ pgcn_map[pgcn >> 5] |= (1 << (pgcn & 31));
pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
next_pgcn = pgc->next_pgc_nr;
- if ( next_pgcn < 1 || next_pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
+ if ( next_pgcn < 1 || next_pgcn >= MAX_PGCN || next_pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
return 0;
- byte = next_pgcn >> 3;
- bit = 1 << (pgcn & 0x7);
- if (pgcn_map[byte] & bit)
- return 0;
-
- return next_pgcn;
+ return pgcn_map[next_pgcn >> 5] & (1 << (next_pgcn & 31))? 0 : next_pgcn;
}
/***********************************************************************
* Pgc links can loop. I track which have been visited in a bit vector
* Initialize the bit vector to empty.
**********************************************************************/
-static void PgcWalkInit( void )
+static void PgcWalkInit( uint32_t pgcn_map[MAX_PGCN/32] )
{
- memset(pgcn_map, 0, 128);
+ memset(pgcn_map, 0, sizeof(pgcn_map) );
}
/***********************************************************************