OSDN Git Service

import original 0.9.5 release
[handbrake-jp/handbrake-jp.git] / libhb / dvdnav.c
index d525b96..7571b57 100644 (file)
@@ -18,7 +18,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 hb_title_t  * hb_dvdnav_title_scan( hb_dvd_t * d, int t, uint64_t min_duration );
 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 );
@@ -26,7 +26,8 @@ static int           hb_dvdnav_read( hb_dvd_t * d, hb_buffer_t * b );
 static int           hb_dvdnav_chapter( hb_dvd_t * d );
 static void          hb_dvdnav_close( hb_dvd_t ** _d );
 static int           hb_dvdnav_angle_count( hb_dvd_t * d );
-static void          hb_dvdnav_set_angle( hb_dvd_t * e, int angle );
+static void          hb_dvdnav_set_angle( hb_dvd_t * d, int angle );
+static int           hb_dvdnav_main_feature( hb_dvd_t * d, hb_list_t * list_title );
 
 hb_dvd_func_t hb_dvdnav_func =
 {
@@ -41,7 +42,8 @@ hb_dvd_func_t hb_dvdnav_func =
     hb_dvdnav_read,
     hb_dvdnav_chapter,
     hb_dvdnav_angle_count,
-    hb_dvdnav_set_angle
+    hb_dvdnav_set_angle,
+    hb_dvdnav_main_feature
 };
 
 // there can be at most 999 PGCs per title. round that up to the nearest
@@ -287,7 +289,7 @@ PttDuration(ifo_handle_t *ifo, int ttn, int pttn, int *blocks, int *last_pgcn)
 /***********************************************************************
  * hb_dvdnav_title_scan
  **********************************************************************/
-static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
+static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t, uint64_t min_duration )
 {
 
     hb_dvdnav_t * d = &(e->dvdnav);
@@ -307,6 +309,7 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
     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 ) );
@@ -412,10 +415,19 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         }
     }
 
+    /* Get duration */
+    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 (%"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( longest < 900000LL )
+    if( longest < min_duration )
     {
         hb_log( "scan: ignoring title (too short)" );
         goto fail;
@@ -453,20 +465,11 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
     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,
+    hb_log( "scan: vts=%d, ttn=%d, cells=%d->%d, blocks=%"PRIu64"->%"PRIu64", "
+            "%"PRIu64" blocks", title->vts, title->ttn, title->cell_start,
             title->cell_end, title->block_start, title->block_end,
             title->block_count );
 
-    /* Get duration */
-    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 (%"PRId64" ms)",
-            title->hours, title->minutes, title->seconds,
-            title->duration / 90 );
-
     /* Detect languages */
     for( i = 0; i < ifo->vtsi_mat->nr_of_vts_audio_streams; i++ )
     {
@@ -588,10 +591,6 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         hb_list_add( title->list_audio, audio );
     }
 
-    memcpy( title->palette,
-            ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette,
-            16 * sizeof( uint32_t ) );
-
     /* Check for subtitles */
     for( i = 0; i < ifo->vtsi_mat->nr_of_vts_subp_streams; i++ )
     {
@@ -647,6 +646,10 @@ static hb_title_t * hb_dvdnav_title_scan( hb_dvd_t * e, int t )
         subtitle->config.dest   = RENDERSUB;  // By default render (burn-in) the VOBSUB.
 
         subtitle->type = lang_extension;
+        
+        memcpy( subtitle->palette,
+            ifo->vts_pgcit->pgci_srp[title_pgcn-1].pgc->palette,
+            16 * sizeof( uint32_t ) );
 
         switch( lang_extension )
         {  
@@ -779,7 +782,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), %"PRId64" ms",
+        hb_log( "scan: chap %d c=%d->%d, b=%"PRIu64"->%"PRIu64" (%"PRIu64"), %"PRId64" ms",
                 chapter->index, chapter->cell_start, chapter->cell_end,
                 chapter->block_start, chapter->block_end,
                 chapter->block_count, chapter->duration / 90 );
@@ -817,6 +820,498 @@ cleanup:
 }
 
 /***********************************************************************
+ * hb_dvdnav_title_scan
+ **********************************************************************/
+static int find_title( hb_list_t * list_title, int title )
+{
+    int ii;
+
+    for ( ii = 0; ii < hb_list_count( list_title ); ii++ )
+    {
+        hb_title_t * hbtitle = hb_list_item( list_title, ii );
+        if ( hbtitle->index == title )
+            return ii;
+    }
+    return -1;
+}
+
+static void skip_some( dvdnav_t * dvdnav, int blocks )
+{
+    int ii;
+    int result, event, len;
+    uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
+
+    for ( ii = 0; ii < blocks; ii++ )
+    {
+        result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
+        if ( result == DVDNAV_STATUS_ERR )
+        {
+            hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(dvdnav));
+            return;
+        }
+        switch ( event )
+        {
+        case DVDNAV_BLOCK_OK:
+            break;
+
+        case DVDNAV_CELL_CHANGE:
+        {
+        } break;
+
+        case DVDNAV_STILL_FRAME:
+            dvdnav_still_skip( dvdnav );
+            break;
+
+        case DVDNAV_WAIT:
+            dvdnav_wait_skip( dvdnav );
+            break;
+
+        case DVDNAV_STOP:
+            return;
+
+        case DVDNAV_HOP_CHANNEL:
+            break;
+
+        case DVDNAV_NAV_PACKET:
+            break;
+
+        case DVDNAV_VTS_CHANGE:
+        {
+            dvdnav_vts_change_event_t *event;
+            event = (dvdnav_vts_change_event_t*)buf;
+            // Some discs initialize the vts with the "first play" item
+            // and some don't seem to.  So if we see it is uninitialized,
+            // set it.
+            if ( event->new_vtsN <= 0 )
+                result = dvdnav_title_play( dvdnav, 1 );
+        } break;
+
+        case DVDNAV_HIGHLIGHT:
+            break;
+
+        case DVDNAV_AUDIO_STREAM_CHANGE:
+            break;
+
+        case DVDNAV_SPU_STREAM_CHANGE:
+            break;
+
+        case DVDNAV_SPU_CLUT_CHANGE:
+            break;
+
+        case DVDNAV_NOP:
+            break;
+
+        default:
+            break;
+        }
+    }
+}
+
+static int try_button( dvdnav_t * dvdnav, int menu_id, int button, hb_list_t * list_title )
+{
+    int result, event, len;
+    uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
+    int ii, jj;
+    int32_t cur_title, title, pgcn, pgn;
+    uint64_t longest_duration = 0;
+    int longest = -1;
+
+    pci_t *pci = dvdnav_get_current_nav_pci( dvdnav );
+    result = dvdnav_button_select_and_activate( dvdnav, pci, button + 1 );
+    if (result != DVDNAV_STATUS_OK)
+    {
+        hb_log("dvdnav_button_select_and_activate: %s", dvdnav_err_to_string(dvdnav));
+    }
+
+    result = dvdnav_current_title_program( dvdnav, &title, &pgcn, &pgn );
+    if (result != DVDNAV_STATUS_OK)
+        hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(dvdnav));
+
+    if ( title > 0 )
+    {
+        hb_title_t * hbtitle;
+        int index;
+        index = find_title( list_title, title );
+        hbtitle = hb_list_item( list_title, index );
+        if ( hbtitle != NULL )
+        {
+            if ( hbtitle->duration > longest_duration )
+            {
+                longest_duration = hbtitle->duration;
+                longest = title;
+            }
+        }
+        // If the duration is longer than 10min, assume
+        // this isn't garbage leading to something bigger.
+        if ( longest_duration / 90000 > 10 * 60 )
+        {
+            return longest;
+        }
+    }
+    cur_title = title;
+
+    for (jj = 0; jj < 5; jj++)
+    {
+        for (ii = 0; ii < 2000; ii++)
+        {
+            result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
+            if ( result == DVDNAV_STATUS_ERR )
+            {
+                hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(dvdnav));
+                return 0;
+            }
+            switch ( event )
+            {
+            case DVDNAV_BLOCK_OK:
+                break;
+
+            case DVDNAV_CELL_CHANGE:
+            {
+                result = dvdnav_current_title_program( dvdnav, &title, &pgcn, &pgn );
+                if (result != DVDNAV_STATUS_OK)
+                    hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(dvdnav));
+
+                if ( title != cur_title && title > 0 )
+                {
+                    hb_title_t * hbtitle;
+                    int index;
+                    index = find_title( list_title, title );
+                    hbtitle = hb_list_item( list_title, index );
+                    if ( hbtitle != NULL )
+                    {
+                        if ( hbtitle->duration > longest_duration )
+                        {
+                            longest_duration = hbtitle->duration;
+                            longest = title;
+                        }
+                    }
+                    // If the duration is longer than 10min, assume
+                    // this isn't garbage leading to something bigger.
+                    if ( longest_duration / 90000 > 10 * 60 )
+                    {
+                        return longest;
+                    }
+                }
+                cur_title = title;
+                if (title > 0)
+                {
+                    result = dvdnav_next_pg_search( dvdnav );
+                    if ( result != DVDNAV_STATUS_OK )
+                    {
+                        return longest;
+                    }
+                }
+            } break;
+
+            case DVDNAV_STILL_FRAME:
+                dvdnav_still_skip( dvdnav );
+                break;
+
+            case DVDNAV_WAIT:
+                dvdnav_wait_skip( dvdnav );
+                break;
+
+            case DVDNAV_STOP:
+                return 0;
+
+            case DVDNAV_HOP_CHANNEL:
+                break;
+
+            case DVDNAV_NAV_PACKET:
+            {
+            } break;
+
+            case DVDNAV_VTS_CHANGE:
+            {
+                result = dvdnav_current_title_program( dvdnav, &title, &pgcn, &pgn );
+                if (result != DVDNAV_STATUS_OK)
+                    hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(dvdnav));
+
+                if ( title != cur_title && title > 0 )
+                {
+                    hb_title_t * hbtitle;
+                    int index;
+                    index = find_title( list_title, title );
+                    hbtitle = hb_list_item( list_title, index );
+                    if ( hbtitle != NULL )
+                    {
+                        if ( hbtitle->duration > longest_duration )
+                        {
+                            longest_duration = hbtitle->duration;
+                            longest = title;
+                        }
+                    }
+                    if ( longest_duration / 90000 > 10 * 60 )
+                    {
+                        return longest;
+                    }
+                }
+                cur_title = title;
+            } break;
+
+            case DVDNAV_HIGHLIGHT:
+                break;
+
+            case DVDNAV_AUDIO_STREAM_CHANGE:
+                break;
+
+            case DVDNAV_SPU_STREAM_CHANGE:
+                break;
+
+            case DVDNAV_SPU_CLUT_CHANGE:
+                break;
+
+            case DVDNAV_NOP:
+                break;
+
+            default:
+                break;
+            }
+        }
+        if (cur_title > 0)
+        {
+            // Some titles have long lead-ins. Try skipping it.
+            dvdnav_next_pg_search( dvdnav );
+        }
+    }
+    return longest;
+}
+
+static int try_menu( 
+    hb_dvdnav_t * d, 
+    hb_list_t * list_title, 
+    DVDMenuID_t menu, 
+    uint64_t fallback_duration )
+{
+    int result, event, len;
+    uint8_t buf[HB_DVD_READ_BUFFER_SIZE];
+    int ii, jj;
+    int32_t cur_title, title, pgcn, pgn;
+    uint64_t longest_duration = 0;
+    int longest = -1;
+
+
+    result = dvdnav_menu_call( d->dvdnav, menu );
+    if ( result != DVDNAV_STATUS_OK )
+    {
+        // Sometimes the "first play" item doesn't initialize the
+        // initial VTS. So do it here.
+        result = dvdnav_title_play( d->dvdnav, 1 );
+        result = dvdnav_menu_call( d->dvdnav, menu );
+        if ( result != DVDNAV_STATUS_OK )
+        {
+            hb_error("dvdnav: Can not set dvd menu, %s", dvdnav_err_to_string(d->dvdnav));
+            goto done;
+        }
+    }
+
+    result = dvdnav_current_title_program( d->dvdnav, &title, &pgcn, &pgn );
+    if (result != DVDNAV_STATUS_OK)
+        hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
+
+    cur_title = title;
+
+    for (jj = 0; jj < 4; jj++)
+    {
+        for (ii = 0; ii < 4000; ii++)
+        {
+            result = dvdnav_get_next_block( d->dvdnav, buf, &event, &len );
+            if ( result == DVDNAV_STATUS_ERR )
+            {
+                hb_error("dvdnav: Read Error, %s", dvdnav_err_to_string(d->dvdnav));
+                goto done;
+            }
+            switch ( event )
+            {
+            case DVDNAV_BLOCK_OK:
+                break;
+
+            case DVDNAV_CELL_CHANGE:
+            {
+                result = dvdnav_current_title_program( d->dvdnav, &title, &pgcn, &pgn );
+                if (result != DVDNAV_STATUS_OK)
+                    hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
+                cur_title = title;
+            } break;
+
+            case DVDNAV_STILL_FRAME:
+                dvdnav_still_skip( d->dvdnav );
+                break;
+
+            case DVDNAV_WAIT:
+                dvdnav_wait_skip( d->dvdnav );
+                break;
+
+            case DVDNAV_STOP:
+                goto done;
+
+            case DVDNAV_HOP_CHANNEL:
+                break;
+
+            case DVDNAV_NAV_PACKET:
+            {
+                pci_t *pci = dvdnav_get_current_nav_pci( d->dvdnav );
+                int kk;
+                int buttons;
+                if ( pci == NULL ) break;
+
+                buttons = pci->hli.hl_gi.btn_ns;
+                if ( cur_title == 0 && buttons > 1 )
+                {
+                    int menu_title, menu_id;
+                    result = dvdnav_current_title_info( d->dvdnav, &menu_title, &menu_id );
+                    if (result != DVDNAV_STATUS_OK)
+                        hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
+                    for (kk = 0; kk < buttons; kk++)
+                    {
+                        dvdnav_t *dvdnav_copy;
+
+                        result = dvdnav_dup( &dvdnav_copy, d->dvdnav );
+                        if (result != DVDNAV_STATUS_OK)
+                        {
+                            hb_log("dvdnav dup failed: %s", dvdnav_err_to_string(d->dvdnav));
+                            goto done;
+                        }
+                        title = try_button( dvdnav_copy, menu_id, kk, list_title );
+                        dvdnav_free_dup( dvdnav_copy );
+
+                        if ( title >= 0 )
+                        {
+                            hb_title_t * hbtitle;
+                            int index;
+                            index = find_title( list_title, title );
+                            hbtitle = hb_list_item( list_title, index );
+                            if ( hbtitle != NULL )
+                            {
+                                if ( hbtitle->duration > longest_duration )
+                                {
+                                    longest_duration = hbtitle->duration;
+                                    longest = title;
+                                    if ((float)fallback_duration * 0.75 < longest_duration)
+                                        goto done;
+                                }
+                            }
+                        }
+                    }
+                    goto done;
+                }
+                if ( cur_title == 0 && buttons == 1 )
+                {
+                    dvdnav_button_select_and_activate( d->dvdnav, pci, 1 );
+                }
+            } break;
+
+            case DVDNAV_VTS_CHANGE:
+            {
+                result = dvdnav_current_title_program( d->dvdnav, &title, &pgcn, &pgn );
+                if (result != DVDNAV_STATUS_OK)
+                    hb_log("dvdnav cur pgcn err: %s", dvdnav_err_to_string(d->dvdnav));
+                cur_title = title;
+            } break;
+
+            case DVDNAV_HIGHLIGHT:
+                break;
+
+            case DVDNAV_AUDIO_STREAM_CHANGE:
+                break;
+
+            case DVDNAV_SPU_STREAM_CHANGE:
+                break;
+
+            case DVDNAV_SPU_CLUT_CHANGE:
+                break;
+
+            case DVDNAV_NOP:
+                break;
+
+            default:
+                break;
+            }
+        }
+        // Sometimes the menu is preceeded by a intro that just
+        // gets restarted when hitting the menu button. So
+        // try skipping with the skip forward button.  Then
+        // try hitting the menu again.
+        if ( !(jj & 1) )
+            dvdnav_next_pg_search( d->dvdnav );
+        else
+            result = dvdnav_menu_call( d->dvdnav, menu );
+    }
+
+done:
+    return longest;
+}
+
+static int hb_dvdnav_main_feature( hb_dvd_t * e, hb_list_t * list_title )
+{
+    hb_dvdnav_t * d = &(e->dvdnav);
+    int longest_root;
+    int longest_title = 0;
+    int longest_fallback = 0;
+    int ii;
+    uint64_t longest_duration_root = 0;
+    uint64_t longest_duration_title = 0;
+    uint64_t longest_duration_fallback = 0;
+
+    for ( ii = 0; ii < hb_list_count( list_title ); ii++ )
+    {
+        hb_title_t * title = hb_list_item( list_title, ii );
+        if ( title->duration > longest_duration_fallback )
+        {
+            longest_duration_fallback = title->duration;
+            longest_fallback = title->index;
+        }
+    }
+
+    dvdnav_reset( d->dvdnav );
+    skip_some( d->dvdnav, 500 );
+
+    longest_root = try_menu( d, list_title, DVD_MENU_Root, longest_duration_fallback );
+    if ( longest_root >= 0 )
+    {
+        hb_title_t * hbtitle;
+        int index;
+        index = find_title( list_title, longest_root );
+        hbtitle = hb_list_item( list_title, index );
+        if ( hbtitle )
+            longest_duration_root = hbtitle->duration;
+    }
+    if ( longest_root < 0 || 
+         (float)longest_duration_fallback * 0.7 > longest_duration_root)
+    {
+        longest_title = try_menu( d, list_title, DVD_MENU_Title, longest_duration_fallback );
+        if ( longest_title >= 0 )
+        {
+            hb_title_t * hbtitle;
+            int index;
+            index = find_title( list_title, longest_title );
+            hbtitle = hb_list_item( list_title, index );
+            if ( hbtitle )
+                longest_duration_title = hbtitle->duration;
+        }
+    }
+
+    uint64_t longest_duration;
+    int longest;
+
+    if ( longest_duration_root > longest_duration_title )
+    {
+        longest_duration = longest_duration_root;
+        longest = longest_root;
+    }
+    else
+    {
+        longest_duration = longest_duration_title;
+        longest = longest_title;
+    }
+    if ((float)longest_duration_fallback * 0.7 > longest_duration)
+    {
+        longest = longest_fallback;
+    }
+    return longest;
+}
+
+/***********************************************************************
  * hb_dvdnav_start
  ***********************************************************************
  * Title and chapter start at 1
@@ -835,6 +1330,7 @@ static int hb_dvdnav_start( hb_dvd_t * e, hb_title_t *title, int c )
     {
         return 0;
     }
+    dvdnav_reset( d->dvdnav );
     chapter = hb_list_item( title->list_chapter, c - 1);
     if (chapter != NULL)
         result = dvdnav_program_play(d->dvdnav, t, chapter->pgcn, chapter->pgn);
@@ -882,24 +1378,35 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
     // 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 ( chapter->block_start <= sector && sector <= chapter->block_end )
+        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 we find ourselves in a new title, it means a title
+            // transition was made while reading data.  Jumping between
+            // titles can cause the vm to get into a bad state.  So
+            // reset the vm in this case.
+            if ( d->title != title )
+                dvdnav_reset( d->dvdnav );
+
             if ( d->title != title || chapter->pgcn != pgcn )
             {
                 // this chapter is in a different pgc - switch to it.
@@ -907,9 +1414,10 @@ static int hb_dvdnav_seek( hb_dvd_t * e, float f )
                     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;
+            sector -= pgc_offset;
             break;
         }
+        chap_offset += chap_len;
     }
 
     // dvdnav will not let you seek or poll current position
@@ -921,7 +1429,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 )
@@ -987,7 +1495,7 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
         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));
+            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",
@@ -995,14 +1503,13 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
                 return 0;
             }
             error_count++;
-            if (error_count > 10)
+            if (error_count > 500)
             {
-                hb_log("dvdnav: Error, too many consecutive read errors");
+                hb_error("dvdnav: Error, too many consecutive read errors");
                 return 0;
             }
             continue;
         }
-        error_count = 0;
         switch ( event )
         {
         case DVDNAV_BLOCK_OK:
@@ -1014,6 +1521,7 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
             if (chapter > 1)
                 b->new_chap = chapter;
             chapter = 0;
+            error_count = 0;
             return 1;
 
         case DVDNAV_NOP:
@@ -1082,6 +1590,16 @@ static int hb_dvdnav_read( hb_dvd_t * e, hb_buffer_t * b )
             * event can be used to query such information only when
             * necessary and update the decoding/displaying accordingly. 
             */
+            {
+                int tt = 0, pgcn = 0, pgn = 0;
+
+                dvdnav_current_title_program(d->dvdnav, &tt, &pgcn, &pgn);
+                if (tt != d->title)
+                {
+                    // Transition to another title signals that we are done.
+                    return 0;
+                }
+            }
             break;
 
         case DVDNAV_CELL_CHANGE: