OSDN Git Service

Rate limit hb_error() messages to at most 1 message per second of the same message...
[handbrake-jp/handbrake-jp-git.git] / libhb / dvdnav.c
index eee9d28..2f1ac93 100644 (file)
@@ -10,6 +10,7 @@
 
 #include "dvdnav/dvdnav.h"
 #include "dvdread/ifo_read.h"
+#include "dvdread/ifo_print.h"
 #include "dvdread/nav_read.h"
 
 #define DVD_READ_CACHE 1
@@ -18,7 +19,7 @@ static char        * hb_dvdnav_name( char * path );
 static hb_dvd_t    * hb_dvdnav_init( char * path );
 static int           hb_dvdnav_title_count( hb_dvd_t * d );
 static hb_title_t  * hb_dvdnav_title_scan( hb_dvd_t * d, int t );
-static int           hb_dvdnav_start( hb_dvd_t * d, int title, int chapter );
+static int           hb_dvdnav_start( hb_dvd_t * d, hb_title_t *title, int chapter );
 static void          hb_dvdnav_stop( hb_dvd_t * d );
 static int           hb_dvdnav_seek( hb_dvd_t * d, float f );
 static int           hb_dvdnav_read( hb_dvd_t * d, hb_buffer_t * b );
@@ -43,12 +44,18 @@ hb_dvd_func_t hb_dvdnav_func =
     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( 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, uint32_t pgcn_map[MAX_PGCN/32] );
 static int FindNextCell( pgc_t *pgc, int cell_cur );
 static int dvdtime2msec( dvd_time_t * );
-//static int hb_dvdnav_is_break( hb_dvdnav_t * d );
 
 hb_dvd_func_t * hb_dvdnav_methods( void )
 {
@@ -88,7 +95,7 @@ static char * hb_dvdnav_name( char * path )
 static int hb_dvdnav_reset( hb_dvdnav_t * d )
 {
     if ( d->dvdnav ) 
-               dvdnav_close( d->dvdnav );
+        dvdnav_close( d->dvdnav );
 
     /* Open device */
     if( dvdnav_open(&d->dvdnav, d->path) != DVDNAV_STATUS_OK )
@@ -119,7 +126,7 @@ static int hb_dvdnav_reset( hb_dvdnav_t * d )
                  dvdnav_err_to_string(d->dvdnav));
         goto fail;
     }
-       return 1;
+    return 1;
 
 fail:
     if( d->dvdnav ) dvdnav_close( d->dvdnav );
@@ -135,10 +142,21 @@ static hb_dvd_t * hb_dvdnav_init( char * path )
 {
     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, &region_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 )
     {
@@ -210,6 +228,62 @@ static int hb_dvdnav_title_count( hb_dvd_t * e )
     return titles;
 }
 
+static uint64_t
+PttDuration(ifo_handle_t *ifo, int ttn, int pttn, int *blocks, int *last_pgcn)
+{
+    int            pgcn, pgn;
+    pgc_t        * pgc;
+    uint64_t       duration = 0;
+    int            cell_start, cell_end;
+    int            i;
+
+    // Initialize map of visited pgc's to prevent loops
+    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 || pgcn >= MAX_PGCN)
+    {
+        hb_error( "invalid PGC ID %d, skipping", pgcn );
+        return 0;
+    }
+
+    if( pgn <= 0 || pgn > 99 )
+    {
+        hb_error( "scan: pgn %d not valid, skipping", pgn );
+        return 0;
+    }
+
+    *blocks = 0;
+    do
+    {
+        pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
+        if (!pgc)
+        {
+            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;
+        cell_end = pgc->nr_of_cells - 1;
+        for(i = cell_start; i <= cell_end; i = FindNextCell(pgc, i))
+        {
+            *blocks += pgc->cell_playback[i].last_sector + 1 -
+                pgc->cell_playback[i].first_sector;
+        }
+        *last_pgcn = pgcn;
+        pgn = 1;
+    } while((pgcn = NextPgcn(ifo, pgcn, pgcn_map)) != 0);
+    return duration;
+}
+
 /***********************************************************************
  * hb_dvdnav_title_scan
  **********************************************************************/
@@ -218,13 +292,15 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
     hb_dvdnav_t * d = &(e->dvdnav);
     hb_title_t   * title;
-    ifo_handle_t * vts = NULL;
-    int            pgc_id, pgn, i;
+    ifo_handle_t * ifo = NULL;
+    int            pgcn, pgn, pgcn_end, i, c;
+    int            title_pgcn;
     pgc_t        * pgc;
     int            cell_cur;
     hb_chapter_t * chapter;
-    int            c;
-    uint64_t       duration;
+    int            count;
+    uint64_t       duration, longest;
+    int            longest_pgcn, longest_pgn, longest_pgcn_end;
     float          duration_correction;
     const char   * name;
 
@@ -259,7 +335,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
     }
 
     hb_log( "scan: opening IFO for VTS %d", title->vts );
-    if( !( vts = ifoOpen( d->reader, title->vts ) ) )
+    if( !( ifo = ifoOpen( d->reader, title->vts ) ) )
     {
         hb_error( "scan: ifoOpen failed" );
         goto fail;
@@ -267,31 +343,31 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
     /* ignore titles with bogus cell addresses so we don't abort later
      ** in libdvdread. */
-    for ( i = 0; i < vts->vts_c_adt->nr_of_vobs; ++i)
+    for ( i = 0; i < ifo->vts_c_adt->nr_of_vobs; ++i)
     {
-        if( (vts->vts_c_adt->cell_adr_table[i].start_sector & 0xffffff ) ==
+        if( (ifo->vts_c_adt->cell_adr_table[i].start_sector & 0xffffff ) ==
             0xffffff )
         {
             hb_error( "scan: cell_adr_table[%d].start_sector invalid (0x%x) "
                       "- skipping title", i,
-                      vts->vts_c_adt->cell_adr_table[i].start_sector );
+                      ifo->vts_c_adt->cell_adr_table[i].start_sector );
             goto fail;
         }
-        if( (vts->vts_c_adt->cell_adr_table[i].last_sector & 0xffffff ) ==
+        if( (ifo->vts_c_adt->cell_adr_table[i].last_sector & 0xffffff ) ==
             0xffffff )
         {
             hb_error( "scan: cell_adr_table[%d].last_sector invalid (0x%x) "
                       "- skipping title", i,
-                      vts->vts_c_adt->cell_adr_table[i].last_sector );
+                      ifo->vts_c_adt->cell_adr_table[i].last_sector );
             goto fail;
         }
-        if( vts->vts_c_adt->cell_adr_table[i].start_sector >=
-            vts->vts_c_adt->cell_adr_table[i].last_sector )
+        if( ifo->vts_c_adt->cell_adr_table[i].start_sector >=
+            ifo->vts_c_adt->cell_adr_table[i].last_sector )
         {
             hb_error( "scan: cell_adr_table[%d].start_sector (0x%x) "
                       "is not before last_sector (0x%x) - skipping title", i,
-                      vts->vts_c_adt->cell_adr_table[i].start_sector,
-                      vts->vts_c_adt->cell_adr_table[i].last_sector );
+                      ifo->vts_c_adt->cell_adr_table[i].start_sector,
+                      ifo->vts_c_adt->cell_adr_table[i].last_sector );
             goto fail;
         }
     }
@@ -303,55 +379,79 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
     /* Position of the title in the VTS */
     title->ttn = d->vmg->tt_srpt->title[t-1].vts_ttn;
-    if ( title->ttn < 1 || title->ttn > vts->vts_ptt_srpt->nr_of_srpts )
+    if ( title->ttn < 1 || title->ttn > ifo->vts_ptt_srpt->nr_of_srpts )
     {
         hb_error( "invalid VTS PTT offset %d for title %d, skipping", title->ttn, t );
         goto fail;
     }
 
+    longest = 0LL;
+    longest_pgcn = -1;
+    longest_pgn = 1;
+    longest_pgcn_end = -1;
+    pgcn_end = -1;
+    for( i = 0; i < ifo->vts_ptt_srpt->title[title->ttn-1].nr_of_ptts; i++ )
+    {
+        int blocks = 0;
 
-    /* Get pgc */
-    pgc_id = vts->vts_ptt_srpt->title[title->ttn-1].ptt[0].pgcn;
-    if ( pgc_id < 1 || pgc_id > vts->vts_pgcit->nr_of_pgci_srp )
+        duration = PttDuration(ifo, title->ttn, i+1, &blocks, &pgcn_end);
+        pgcn  = ifo->vts_ptt_srpt->title[title->ttn-1].ptt[i].pgcn;
+        pgn   = ifo->vts_ptt_srpt->title[title->ttn-1].ptt[i].pgn;
+        if( duration > longest )
+        {
+            longest_pgcn  = pgcn;
+            longest_pgn   = pgn;
+            longest_pgcn_end   = pgcn_end;
+            longest = duration;
+            title->block_count = blocks;
+        }
+        else if (pgcn == longest_pgcn && pgn < longest_pgn)
+        {
+            longest_pgn   = pgn;
+            title->block_count = blocks;
+        }
+    }
+
+    /* ignore titles under 10 seconds because they're often stills or
+     * clips with no audio & our preview code doesn't currently handle
+     * either of these. */
+    if( longest < 900000LL )
     {
-        hb_error( "invalid PGC ID %d for title %d, skipping", pgc_id, t );
+        hb_log( "scan: ignoring title (too short)" );
         goto fail;
     }
-    pgn    = vts->vts_ptt_srpt->title[title->ttn-1].ptt[0].pgn;
-    pgc = vts->vts_pgcit->pgci_srp[pgc_id-1].pgc;
 
-    hb_log("pgc_id: %d, pgn: %d: pgc: 0x%x", pgc_id, pgn, pgc);
+    pgcn       = longest_pgcn;
+    pgcn_end   = longest_pgcn_end;
+    pgn        = longest_pgn;;
+    title_pgcn = pgcn;
 
-    if( !pgc )
+
+    /* Get pgc */
+    if ( pgcn < 1 || pgcn > ifo->vts_pgcit->nr_of_pgci_srp || pgcn >= MAX_PGCN)
     {
-        hb_error( "scan: pgc not valid, skipping" );
+        hb_error( "invalid PGC ID %d for title %d, skipping", pgcn, t );
         goto fail;
     }
 
-    if( pgn <= 0 || pgn > 99 )
+    pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
+
+    hb_log("pgc_id: %d, pgn: %d: pgc: %p", pgcn, pgn, pgc);
+    if (pgn > pgc->nr_of_programs)
     {
-        hb_error( "scan: pgn %d not valid, skipping", pgn );
+        hb_error( "invalid PGN %d for title %d, skipping", pgn, t );
         goto fail;
     }
 
-    /* Start cell */
-    title->cell_start  = pgc->program_map[pgn-1] - 1;
+    /* Title start */
+    title->cell_start = pgc->program_map[pgn-1] - 1;
     title->block_start = pgc->cell_playback[title->cell_start].first_sector;
 
-    /* End cell */
-    title->cell_end  = pgc->nr_of_cells - 1;
-    title->block_end = pgc->cell_playback[title->cell_end].last_sector;
+    pgc = ifo->vts_pgcit->pgci_srp[pgcn_end-1].pgc;
 
-    /* Block count */
-    title->block_count = 0;
-    cell_cur = title->cell_start;
-    while( cell_cur <= title->cell_end )
-    {
-#define cp pgc->cell_playback[cell_cur]
-        title->block_count += cp.last_sector + 1 - cp.first_sector;
-#undef cp
-        cell_cur = FindNextCell( pgc, cell_cur );
-    }
+    /* Title end */
+    title->cell_end = pgc->nr_of_cells - 1;
+    title->block_end = pgc->cell_playback[title->cell_end].last_sector;
 
     hb_log( "scan: vts=%d, ttn=%d, cells=%d->%d, blocks=%d->%d, "
             "%d blocks", title->vts, title->ttn, title->cell_start,
@@ -359,25 +459,16 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
             title->block_count );
 
     /* Get duration */
-    title->duration = 90LL * dvdtime2msec( &pgc->playback_time );
+    title->duration = longest;
     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 );
 
-    /* ignore titles under 10 seconds because they're often stills or
-     * clips with no audio & our preview code doesn't currently handle
-     * either of these. */
-    if( title->duration < 900000LL )
-    {
-        hb_log( "scan: ignoring title (too short)" );
-        goto fail;
-    }
-
     /* Detect languages */
-    for( i = 0; i < vts->vtsi_mat->nr_of_vts_audio_streams; i++ )
+    for( i = 0; i < ifo->vtsi_mat->nr_of_vts_audio_streams; i++ )
     {
         hb_audio_t * audio, * audio_tmp;
         int          audio_format, lang_code, audio_control,
@@ -389,11 +480,11 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
         audio = calloc( sizeof( hb_audio_t ), 1 );
 
-        audio_format  = vts->vtsi_mat->vts_audio_attr[i].audio_format;
-        lang_code     = vts->vtsi_mat->vts_audio_attr[i].lang_code;
-        lang_extension = vts->vtsi_mat->vts_audio_attr[i].code_extension;
+        audio_format  = ifo->vtsi_mat->vts_audio_attr[i].audio_format;
+        lang_code     = ifo->vtsi_mat->vts_audio_attr[i].lang_code;
+        lang_extension = ifo->vtsi_mat->vts_audio_attr[i].code_extension;
         audio_control =
-            vts->vts_pgcit->pgci_srp[pgc_id-1].pgc->audio_control[i];
+            ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->audio_control[i];
 
         if( !( audio_control & 0x8000 ) )
         {
@@ -459,7 +550,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
         audio->config.lang.type = lang_extension;
 
-        lang = lang_for_code( vts->vtsi_mat->vts_audio_attr[i].lang_code );
+        lang = lang_for_code( ifo->vtsi_mat->vts_audio_attr[i].lang_code );
 
         snprintf( audio->config.lang.description, sizeof( audio->config.lang.description ), "%s (%s)",
             strlen(lang->native_name) ? lang->native_name : lang->eng_name,
@@ -497,18 +588,12 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         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,
-            vts->vts_pgcit->pgci_srp[pgc_id-1].pgc->palette,
+            ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette,
             16 * sizeof( uint32_t ) );
 
     /* Check for subtitles */
-    for( i = 0; i < vts->vtsi_mat->nr_of_vts_subp_streams; i++ )
+    for( i = 0; i < ifo->vtsi_mat->nr_of_vts_subp_streams; i++ )
     {
         hb_subtitle_t * subtitle;
         int spu_control;
@@ -519,7 +604,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         hb_log( "scan: checking subtitle %d", i + 1 );
 
         spu_control =
-            vts->vts_pgcit->pgci_srp[pgc_id-1].pgc->subp_control[i];
+            ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->subp_control[i];
 
         if( !( spu_control & 0x80000000 ) )
         {
@@ -527,9 +612,9 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
             continue;
         }
 
-        if( vts->vtsi_mat->vts_video_attr.display_aspect_ratio )
+        if( ifo->vtsi_mat->vts_video_attr.display_aspect_ratio )
         {
-            switch( vts->vtsi_mat->vts_video_attr.permitted_df )
+            switch( ifo->vtsi_mat->vts_video_attr.permitted_df )
             {
                 case 1:
                     position = spu_control & 0xFF;
@@ -546,16 +631,20 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
             position = ( spu_control >> 24 ) & 0x7F;
         }
 
-        lang_extension = vts->vtsi_mat->vts_subp_attr[i].code_extension;
+        lang_extension = ifo->vtsi_mat->vts_subp_attr[i].code_extension;
 
-        lang = lang_for_code( vts->vtsi_mat->vts_subp_attr[i].lang_code );
+        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;
 
@@ -612,55 +701,56 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
     }
 
     /* Chapters */
-    hb_log( "scan: title %d has %d chapters", t,
-            vts->vts_ptt_srpt->title[title->ttn-1].nr_of_ptts );
-    for( i = 0, c = 1;
-         i < vts->vts_ptt_srpt->title[title->ttn-1].nr_of_ptts; i++ )
+    uint32_t pgcn_map[MAX_PGCN/32];
+    PgcWalkInit( pgcn_map );
+    c = 0;
+    do
     {
-        int pgc_id_next, pgn_next;
-        pgc_t * pgc_next;
+        pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
 
-        chapter = calloc( sizeof( hb_chapter_t ), 1 );
-        /* remember the on-disc chapter number */
-        chapter->index = i + 1;
+        for (i = pgn; i <= pgc->nr_of_programs; i++)
+        {
+            chapter = calloc( sizeof( hb_chapter_t ), 1 );
 
-        pgc_id = vts->vts_ptt_srpt->title[title->ttn-1].ptt[i].pgcn;
-        pgn    = vts->vts_ptt_srpt->title[title->ttn-1].ptt[i].pgn;
-        pgc = vts->vts_pgcit->pgci_srp[pgc_id-1].pgc;
+            chapter->index = c + 1;
+            chapter->pgcn = pgcn;
+            chapter->pgn = i;
+            hb_list_add( title->list_chapter, chapter );
+            c++;
+        }
+
+        pgn = 1;
+    } while ((pgcn = NextPgcn(ifo, pgcn, pgcn_map)) != 0);
+
+    hb_log( "scan: title %d has %d chapters", t, c );
+
+    duration = 0;
+    count = hb_list_count( title->list_chapter );
+    for (i = 0; i < count; i++)
+    {
+        chapter = hb_list_item( title->list_chapter, i );
+
+        pgcn = chapter->pgcn;
+        pgn = chapter->pgn;
+        pgc = ifo->vts_pgcit->pgci_srp[pgcn-1].pgc;
 
         /* Start cell */
         chapter->cell_start  = pgc->program_map[pgn-1] - 1;
-        chapter->block_start =
-            pgc->cell_playback[chapter->cell_start].first_sector;
-
-        /* End cell */
-        if( i != vts->vts_ptt_srpt->title[title->ttn-1].nr_of_ptts - 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... */
-            pgc_id_next = vts->vts_ptt_srpt->title[title->ttn-1].ptt[i+1].pgcn;
-            pgn_next    = vts->vts_ptt_srpt->title[title->ttn-1].ptt[i+1].pgn;
-            pgc_next    = vts->vts_pgcit->pgci_srp[pgc_id_next-1].pgc;
-            chapter->cell_end = pgc_next->program_map[pgn_next-1] - 2;
-            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 = title->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 */
-        pgc_id = vts->vts_ptt_srpt->title[title->ttn-1].ptt[0].pgcn;
-        pgn    = vts->vts_ptt_srpt->title[title->ttn-1].ptt[0].pgn;
-        pgc = vts->vts_pgcit->pgci_srp[pgc_id-1].pgc;
         chapter->block_count = 0;
         chapter->duration = 0;
 
@@ -673,18 +763,11 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 #undef cp
             cell_cur = FindNextCell( pgc, cell_cur );
         }
-        hb_list_add( title->list_chapter, chapter );
-        c++;
+        duration += chapter->duration;
     }
 
     /* The durations we get for chapters aren't precise. Scale them so
        the total matches the title duration */
-    duration = 0;
-    for( i = 0; i < hb_list_count( title->list_chapter ); i++ )
-    {
-        chapter = hb_list_item( title->list_chapter, i );
-        duration += chapter->duration;
-    }
     duration_correction = (float) title->duration / (float) duration;
     for( i = 0; i < hb_list_count( title->list_chapter ); i++ )
     {
@@ -696,7 +779,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         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 );
@@ -704,7 +787,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
 
     /* Get aspect. We don't get width/height/rate infos here as
        they tend to be wrong */
-    switch( vts->vtsi_mat->vts_video_attr.display_aspect_ratio )
+    switch( ifo->vtsi_mat->vts_video_attr.display_aspect_ratio )
     {
         case 0:
             title->container_aspect = 4. / 3.;
@@ -728,7 +811,7 @@ fail:
     title = NULL;
 
 cleanup:
-    if( vts ) ifoClose( vts );
+    if( ifo ) ifoClose( ifo );
 
     return title;
 }
@@ -738,53 +821,34 @@ cleanup:
  ***********************************************************************
  * Title and chapter start at 1
  **********************************************************************/
-static int hb_dvdnav_start( hb_dvd_t * e, int title, int chapter )
+static int hb_dvdnav_start( hb_dvd_t * e, hb_title_t *title, int c )
 {
     hb_dvdnav_t * d = &(e->dvdnav);
-    ifo_handle_t *ifo;
-    int ii;
-    int vts, ttn, pgc_id, pgn;
-    pgc_t *pgc;
-    int cell_start, cell_end, title_start, title_end;
+    int t = title->index;
+    hb_chapter_t *chapter;
+    dvdnav_status_t result;
 
-    vts = d->vmg->tt_srpt->title[title-1].title_set_nr;
-    ttn = d->vmg->tt_srpt->title[title-1].vts_ttn;
-    if( !( ifo = ifoOpen( d->reader, vts ) ) )
-    {
-        hb_error( "dvd: ifoOpen failed for VTS %d", vts );
-        return 0;
-    }
-
-    /* Get title first and last blocks */
-    pgc_id         = ifo->vts_ptt_srpt->title[ttn-1].ptt[0].pgcn;
-    pgn            = ifo->vts_ptt_srpt->title[ttn-1].ptt[0].pgn;
-    pgc         = ifo->vts_pgcit->pgci_srp[pgc_id-1].pgc;
-    cell_start  = pgc->program_map[pgn - 1] - 1;
-    cell_end    = pgc->nr_of_cells - 1;
-    title_start = pgc->cell_playback[cell_start].first_sector;
-    title_end   = pgc->cell_playback[cell_end].last_sector;
-
-    /* Block count */
-    d->title_block_count = 0;
-    for( ii = cell_start; ii <= cell_end; ii++ )
-    {
-        d->title_block_count += pgc->cell_playback[ii].last_sector + 1 -
-            pgc->cell_playback[ii].first_sector;
-    }
-    ifoClose(ifo);
+    d->title_block_count = title->block_count;
+    d->list_chapter = title->list_chapter;
 
-       if ( d->stopped && !hb_dvdnav_reset(d) )
+    if ( d->stopped && !hb_dvdnav_reset(d) )
     {
         return 0;
     }
-    if ( dvdnav_part_play(d->dvdnav, title, chapter) != DVDNAV_STATUS_OK )
+    chapter = hb_list_item( title->list_chapter, c - 1);
+    if (chapter != NULL)
+        result = dvdnav_program_play(d->dvdnav, t, chapter->pgcn, chapter->pgn);
+    else
+        result = dvdnav_part_play(d->dvdnav, t, 1);
+    if (result != DVDNAV_STATUS_OK)
     {
-        hb_error( "dvd: dvdnav_title_play failed - %s", 
+        hb_error( "dvd: dvdnav_*_play failed - %s", 
                   dvdnav_err_to_string(d->dvdnav) );
         return 0;
     }
-    d->title = title;
-       d->stopped = 0;
+    d->title = t;
+    d->stopped = 0;
+    d->chapter = 0;
     return 1;
 }
 
@@ -795,7 +859,6 @@ static int hb_dvdnav_start( hb_dvd_t * e, int title, int chapter )
  **********************************************************************/
 static void hb_dvdnav_stop( hb_dvd_t * e )
 {
-    hb_dvdnav_t * d = &(e->dvdnav);
 }
 
 /***********************************************************************
@@ -806,15 +869,49 @@ static void hb_dvdnav_stop( hb_dvd_t * e )
 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;
 
     if (d->stopped)
-       {
-               return 0;
-       }
+    {
+        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.
+    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 );
+
+        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_change = chapter;
+        }
+        if ( chapter->block_start <= sector && sector <= chapter->block_end )
+        {
+            // 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_change->block_start;
+            break;
+        }
+    }
+
     // 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
@@ -824,7 +921,7 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
         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 )
@@ -832,6 +929,7 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
         case DVDNAV_BLOCK_OK:
         case DVDNAV_CELL_CHANGE:
             done = 1;
+            break;
 
         case DVDNAV_STILL_FRAME:
             dvdnav_still_skip( d->dvdnav );
@@ -842,7 +940,8 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
             break;
 
         case DVDNAV_STOP:
-                       d->stopped = 1;
+            hb_log("dvdnav: stop encountered during seek");
+            d->stopped = 1;
             return 0;
 
         case DVDNAV_HOP_CHANNEL:
@@ -856,9 +955,8 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
         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", 
@@ -878,19 +976,33 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
     hb_dvdnav_t * d = &(e->dvdnav);
     int result, event, len;
     int chapter = 0;
+    int error_count = 0;
 
     while ( 1 )
     {
-       if (d->stopped)
-               {
-                       return 0;
-               }
+        if (d->stopped)
+        {
+            return 0;
+        }
         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;
         }
+        error_count = 0;
         switch ( event )
         {
         case DVDNAV_BLOCK_OK:
@@ -980,16 +1092,25 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
             * and update the decoding/displaying accordingly. 
             */
             {
-                int tt = 0, ptt = 0;
+                int tt = 0, pgcn = 0, pgn = 0, c;
 
-                dvdnav_current_title_info(d->dvdnav, &tt, &ptt);
+                dvdnav_current_title_program(d->dvdnav, &tt, &pgcn, &pgn);
                 if (tt != d->title)
                 {
                     // Transition to another title signals that we are done.
                     return 0;
                 }
-                if (ptt > d->chapter)
-                    chapter = d->chapter = ptt;
+                c = FindChapterIndex(d->list_chapter, pgcn, pgn);
+                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;
 
@@ -1027,7 +1148,7 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
             /*
             * Playback should end here. 
             */
-                       d->stopped = 1;
+            d->stopped = 1;
             return 0;
 
         default:
@@ -1046,12 +1167,14 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
 static int hb_dvdnav_chapter( hb_dvd_t * e )
 {
     hb_dvdnav_t * d = &(e->dvdnav);
-    int32_t t, c;
+    int32_t t, pgcn, pgn;
+    int32_t c;
 
-    if (dvdnav_current_title_info(d->dvdnav, &t, &c) != DVDNAV_STATUS_OK)
+    if (dvdnav_current_title_program(d->dvdnav, &t, &pgcn, &pgn) != DVDNAV_STATUS_OK)
     {
         return -1;
     }
+    c = FindChapterIndex( d->list_chapter, pgcn, pgn );
     return c;
 }
 
@@ -1106,6 +1229,27 @@ static void hb_dvdnav_set_angle( hb_dvd_t * e, int angle )
 }
 
 /***********************************************************************
+ * FindChapterIndex
+ ***********************************************************************
+ * 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.
+ **********************************************************************/
+static int FindChapterIndex( hb_list_t * list, int pgcn, int pgn )
+{
+    int count, ii;
+    hb_chapter_t *chapter;
+
+    count = hb_list_count( list );
+    for (ii = 0; ii < count; ii++)
+    {
+        chapter = hb_list_item( list, ii );
+        if (chapter->pgcn == pgcn && chapter->pgn == pgn)
+            return chapter->index;
+    }
+    return 0;
+}
+
+/***********************************************************************
  * FindNextCell
  ***********************************************************************
  * Assumes pgc and cell_cur are correctly set, and sets cell_next to the
@@ -1138,6 +1282,40 @@ static int FindNextCell( pgc_t *pgc, int cell_cur )
 }
 
 /***********************************************************************
+ * 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, uint32_t pgcn_map[MAX_PGCN/32] )
+{
+    int next_pgcn;
+    pgc_t *pgc;
+
+    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 >= MAX_PGCN || next_pgcn > ifo->vts_pgcit->nr_of_pgci_srp )
+        return 0;
+
+    return pgcn_map[next_pgcn >> 5] & (1 << (next_pgcn & 31))? 0 : next_pgcn;
+}
+
+/***********************************************************************
+ * PgcWalkInit
+ ***********************************************************************
+ * Pgc links can loop. I track which have been visited in a bit vector
+ * Initialize the bit vector to empty.
+ **********************************************************************/
+static void PgcWalkInit( uint32_t pgcn_map[MAX_PGCN/32] )
+{
+    memset(pgcn_map, 0, sizeof(pgcn_map) );
+}
+
+/***********************************************************************
  * dvdtime2msec
  ***********************************************************************
  * From lsdvd