[vlc-devel] [PATCH] dvdnav: only read the auto-detected main title of the DVD when the dvdnav-main-feature option is used

Adrien Maglo magsoft at videolan.org
Mon Mar 21 15:51:29 CET 2016


This should allow to skip menus or easily transcode the content of a DVD
even if it contains an ARccOS protection.
The main title scan code originally comes from HandBrake 0.10.5 (GPL v2)
---
 modules/access/dvdnav.c | 585 +++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 584 insertions(+), 1 deletion(-)

diff --git a/modules/access/dvdnav.c b/modules/access/dvdnav.c
index 884c04f..776bbd0 100644
--- a/modules/access/dvdnav.c
+++ b/modules/access/dvdnav.c
@@ -5,6 +5,7 @@
  * $Id$
  *
  * Authors: Laurent Aimar <fenrir at via.ecp.fr>
+ *          The HandBrake project
  *
  * This program is free software; you can redistribute it and/or modify it
  * under the terms of the GNU Lesser General Public License as published by
@@ -75,6 +76,11 @@
     "Start the DVD directly in the main menu. This "\
     "will try to skip all the useless warning introductions." )
 
+#define MAIN_FEATURE_TEXT N_("Start directly in the main feature")
+#define MAIN_FEATURE_LONGTEXT N_( \
+    "Start the DVD directly in the main feature. This "\
+    "will try to skip all the introductions and menus." )
+
 #define LANGUAGE_DEFAULT ("en")
 
 static int  AccessDemuxOpen ( vlc_object_t * );
@@ -94,6 +100,8 @@ vlc_module_begin ()
         ANGLE_LONGTEXT, false )
     add_bool( "dvdnav-menu", true,
         MENU_TEXT, MENU_LONGTEXT, false )
+    add_bool( "dvdnav-main-feature", false,
+        MAIN_FEATURE_TEXT, MAIN_FEATURE_LONGTEXT, false )
     set_capability( "access_demux", 5 )
     add_shortcut( "dvd", "dvdnav", "file" )
     set_callbacks( AccessDemuxOpen, Close )
@@ -160,6 +168,8 @@ struct demux_sys_t
     mtime_t     i_pgc_length;
     int         i_vobu_index;
     int         i_vobu_flush;
+
+    int         i_main_title;
 };
 
 static int Control( demux_t *, int, va_list );
@@ -185,6 +195,8 @@ static int EventMouse( vlc_object_t *, char const *,
 static int EventIntf( vlc_object_t *, char const *,
                       vlc_value_t, vlc_value_t, void * );
 
+static int findMainFeature( demux_t *p_demux );
+
 /*****************************************************************************
  * CommonOpen:
  *****************************************************************************/
@@ -212,6 +224,7 @@ static int CommonOpen( vlc_object_t *p_this,
     p_sys->i_vobu_index = 0;
     p_sys->i_vobu_flush = 0;
     p_sys->b_readahead = b_readahead;
+    p_sys->i_main_title = -1;
 
     if( 1 )
     {
@@ -284,7 +297,18 @@ static int CommonOpen( vlc_object_t *p_this,
 
     DemuxTitles( p_demux );
 
-    if( var_CreateGetBool( p_demux, "dvdnav-menu" ) )
+    if ( var_CreateGetBool( p_demux, "dvdnav-main-feature" ) )
+    {
+        /* Try to get and read main feature. */
+        p_sys->i_main_title = findMainFeature(p_demux);
+        if( dvdnav_title_play( p_sys->dvdnav, p_sys->i_main_title ) != DVDNAV_STATUS_OK )
+        {
+            msg_Err( p_demux, "cannot read the auto-detected main feature (title %d)",
+                     p_sys->i_main_title );
+            return VLC_EGENERIC;
+        }
+    }
+    else if( var_CreateGetBool( p_demux, "dvdnav-menu" ) )
     {
         msg_Dbg( p_demux, "trying to go to dvd menu" );
 
@@ -948,6 +972,12 @@ static int Demux( demux_t *p_demux )
             if( i_title >= 0 && i_title < p_sys->i_title &&
                 p_demux->info.i_title != i_title )
             {
+                if ( p_sys->i_main_title != -1
+                     && i_title != p_sys->i_main_title )
+                {
+                    // Return EOS because we must just read the main title.
+                    return 0;
+                }
                 p_demux->info.i_update |= INPUT_UPDATE_TITLE;
                 p_demux->info.i_title = i_title;
             }
@@ -1611,3 +1641,556 @@ bailout:
     close( fd );
     return ret;
 }
+
+/*****************************************************************************
+ * Scan the DVD for the main feature by searching a title reachable
+ * through the menus.
+ * This code originaly comes from HandBrake 0.10.5. but has been adapted to
+ * VLC.
+ *****************************************************************************/
+
+static int skipToMenu( demux_t * p_demux, int blocks )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    dvdnav_t * dvdnav = p_sys->dvdnav;
+
+    int ii;
+    int result, event, len;
+    uint8_t buf[2048];
+
+    for ( ii = 0; ii < blocks; ii++ )
+    {
+        result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
+        if ( result == DVDNAV_STATUS_ERR )
+        {
+            msg_Dbg( p_demux, "read error, %s", dvdnav_err_to_string(dvdnav));
+            return 0;
+        }
+        switch ( event )
+        {
+        case DVDNAV_BLOCK_OK:
+            break;
+
+        case DVDNAV_CELL_CHANGE:
+        {
+        } break;
+
+        case DVDNAV_STILL_FRAME:
+        {
+            dvdnav_still_event_t *event;
+            event = (dvdnav_still_event_t*)buf;
+            dvdnav_still_skip( dvdnav );
+            if ( event->length == 255 )
+            {
+                // Infinite still. Can't be the main feature unless
+                // you like watching paint dry.
+                return 0;
+            }
+        } break;
+
+        case DVDNAV_WAIT:
+            dvdnav_wait_skip( dvdnav );
+            break;
+
+        case DVDNAV_STOP:
+            return 0;
+
+        case DVDNAV_HOP_CHANNEL:
+            break;
+
+        case DVDNAV_NAV_PACKET:
+        {
+            pci_t *pci = dvdnav_get_current_nav_pci( dvdnav );
+            if ( pci == NULL ) break;
+
+            int buttons = pci->hli.hl_gi.btn_ns;
+
+            int title, part;
+            result = dvdnav_current_title_info( dvdnav, &title, &part );
+            if (result != DVDNAV_STATUS_OK)
+                msg_Dbg( p_demux, "title info: %s", dvdnav_err_to_string(dvdnav));
+            else if ( title == 0 && buttons > 0 )
+            {
+                // Check button activation duration to see if this
+                // isn't another fake menu.
+                if ( pci->hli.hl_gi.btn_se_e_ptm - pci->hli.hl_gi.hli_s_ptm >
+                     15 * 90000 )
+                {
+                    // Found what appears to be a good menu.
+                    return 1;
+                }
+            }
+        } 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;
+        }
+    }
+    return 0;
+}
+
+
+static int tryButton( demux_t * p_demux, dvdnav_t * dvdnav, int button )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    input_title_t **list_title = p_sys->title;
+
+    int result, event, len;
+    uint8_t buf[2048];
+    int ii, jj;
+    int32_t cur_title = 0, title, part;
+    int64_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)
+        msg_Dbg( p_demux, "button select and activate: %s", dvdnav_err_to_string(dvdnav));
+
+    result = dvdnav_current_title_info( dvdnav, &title, &part );
+    if (result != DVDNAV_STATUS_OK)
+        msg_Dbg( p_demux, "current title info: %s", dvdnav_err_to_string(dvdnav));
+
+    cur_title = title;
+
+    for (jj = 0; jj < 10; jj++)
+    {
+        for (ii = 0; ii < 2000; ii++)
+        {
+            result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
+            if ( result == DVDNAV_STATUS_ERR )
+            {
+                msg_Err( p_demux, "read error, %s", dvdnav_err_to_string(dvdnav));
+                goto done;
+            }
+            switch ( event )
+            {
+            case DVDNAV_BLOCK_OK:
+                break;
+
+            case DVDNAV_CELL_CHANGE:
+            {
+                result = dvdnav_current_title_info( dvdnav, &title, &part );
+                if (result != DVDNAV_STATUS_OK)
+                    msg_Dbg( p_demux, "title info: %s", dvdnav_err_to_string(dvdnav));
+
+                cur_title = title;
+                // Note, some "fake" titles have long advertised durations
+                // but then jump to the real title early in playback.
+                // So keep reading after finding a long title to detect
+                // such cases.
+            } break;
+
+            case DVDNAV_STILL_FRAME:
+            {
+                dvdnav_still_event_t *event;
+                event = (dvdnav_still_event_t*)buf;
+                dvdnav_still_skip( dvdnav );
+                if ( event->length == 255 )
+                {
+                    // Infinite still. Can't be the main feature unless
+                    // you like watching paint dry.
+                    goto done;
+                }
+            } break;
+
+            case DVDNAV_WAIT:
+                dvdnav_wait_skip( dvdnav );
+                break;
+
+            case DVDNAV_STOP:
+                goto done;
+
+            case DVDNAV_HOP_CHANNEL:
+                break;
+
+            case DVDNAV_NAV_PACKET:
+            {
+            } break;
+
+            case DVDNAV_VTS_CHANGE:
+            {
+                result = dvdnav_current_title_info( dvdnav, &title, &part );
+                if (result != DVDNAV_STATUS_OK)
+                    msg_Dbg( p_demux, "title info: %s", dvdnav_err_to_string(dvdnav));
+
+                cur_title = title;
+                // Note, some "fake" titles have long advertised durations
+                // but then jump to the real title early in playback.
+                // So keep reading after finding a long title to detect
+                // such cases.
+            } 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;
+            }
+        }
+        // Check if the current title is long enough to qualify
+        // as the main feature.
+        if ( cur_title > 0 )
+        {
+            if ( cur_title < p_sys->i_title )
+            {
+                input_title_t *hbtitle = list_title[cur_title];
+
+                if ( hbtitle->i_length / ( 1000 * 1000 ) > 10 * 60 )
+                {
+                    msg_Dbg( p_demux, "found candidate feature title %d duration %ld on button %d",
+                             cur_title, hbtitle->i_length, button+1 );
+                    return cur_title;
+                }
+                if ( hbtitle->i_length > longest_duration )
+                {
+                    longest_duration = hbtitle->i_length;
+                    longest = title;
+                }
+            }
+            // Some titles have long lead-ins. Try skipping it.
+            dvdnav_next_pg_search( dvdnav );
+        }
+    }
+
+done:
+    if ( longest != -1 && longest < p_sys->i_title )
+    {
+        input_title_t * hbtitle = list_title[longest];
+        msg_Dbg( p_demux, "found candidate feature title %d duration %ld on button %d",
+        longest, hbtitle->i_length, button+1 );
+    }
+    return longest;
+}
+
+
+static int tryMenu( demux_t * p_demux, DVDMenuID_t menu, uint64_t fallback_duration )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    input_title_t **list_title = p_sys->title;
+    dvdnav_t * dvdnav = p_sys->dvdnav;
+
+    int result, event, len;
+    uint8_t buf[2048];
+    int ii, jj;
+    int32_t cur_title, title, part;
+    int64_t longest_duration = 0;
+    int longest = -1;
+
+    // A bit of a hack here.  Abusing Escape menu to mean use whatever
+    // current menu is already set.
+    if ( menu != DVD_MENU_Escape )
+    {
+        result = dvdnav_menu_call( 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( dvdnav, 1 );
+            result = dvdnav_menu_call( dvdnav, menu );
+            if ( result != DVDNAV_STATUS_OK )
+            {
+                msg_Err( p_demux, "can not set dvd menu, %s", dvdnav_err_to_string(dvdnav) );
+                goto done;
+            }
+        }
+    }
+
+    result = dvdnav_current_title_info( dvdnav, &title, &part );
+    if (result != DVDNAV_STATUS_OK)
+        msg_Err( p_demux, "title info: %s", dvdnav_err_to_string(dvdnav) );
+
+    cur_title = title;
+
+    for (jj = 0; jj < 4; jj++)
+    {
+        for (ii = 0; ii < 4000; ii++)
+        {
+            result = dvdnav_get_next_block( dvdnav, buf, &event, &len );
+            if ( result == DVDNAV_STATUS_ERR )
+            {
+                msg_Err( p_demux, "read Error, %s", dvdnav_err_to_string(dvdnav) );
+                goto done;
+            }
+            switch ( event )
+            {
+            case DVDNAV_BLOCK_OK:
+                break;
+
+            case DVDNAV_CELL_CHANGE:
+            {
+                result = dvdnav_current_title_info( dvdnav, &title, &part );
+                if (result != DVDNAV_STATUS_OK)
+                    msg_Dbg( p_demux, "title info: %s", dvdnav_err_to_string(dvdnav) );
+                cur_title = title;
+            } break;
+
+            case DVDNAV_STILL_FRAME:
+            {
+                dvdnav_still_event_t *event;
+                event = (dvdnav_still_event_t*)buf;
+                dvdnav_still_skip( dvdnav );
+                if ( event->length == 255 )
+                {
+                    // Infinite still.  There won't be any menus after this.
+                    goto done;
+                }
+            } break;
+
+            case DVDNAV_WAIT:
+                dvdnav_wait_skip( dvdnav );
+                break;
+
+            case DVDNAV_STOP:
+                goto done;
+
+            case DVDNAV_HOP_CHANNEL:
+                break;
+
+            case DVDNAV_NAV_PACKET:
+            {
+                pci_t *pci = dvdnav_get_current_nav_pci( dvdnav );
+                int kk;
+                int buttons;
+                if ( pci == NULL ) break;
+
+                buttons = pci->hli.hl_gi.btn_ns;
+
+                // If we are on a menu that has buttons and
+                // the button activation duration is long enough
+                // that this isn't another fake menu.
+                if ( cur_title == 0 && buttons > 0 &&
+                     pci->hli.hl_gi.btn_se_e_ptm - pci->hli.hl_gi.hli_s_ptm >
+                     15 * 90000 )
+                {
+                    for (kk = 0; kk < buttons; kk++)
+                    {
+                        dvdnav_t *dvdnav_copy;
+
+                        result = dvdnav_dup( &dvdnav_copy, dvdnav );
+                        if (result != DVDNAV_STATUS_OK)
+                        {
+                            msg_Err( p_demux, "duplication failed: %s", dvdnav_err_to_string(dvdnav) );
+                            goto done;
+                        }
+                        title = tryButton( p_demux, dvdnav_copy, kk );
+                        dvdnav_free_dup( dvdnav_copy );
+
+                        if ( title >= 0 && title < p_sys->i_title )
+                        {
+                            input_title_t * hbtitle = list_title[title];
+                            if ( hbtitle->i_length > longest_duration )
+                            {
+                                longest_duration = hbtitle->i_length;
+                                longest = title;
+                                if ((float)fallback_duration * 0.75 < longest_duration)
+                                    goto done;
+                            }
+                        }
+                    }
+                    goto done;
+                }
+            } break;
+
+            case DVDNAV_VTS_CHANGE:
+            {
+                result = dvdnav_current_title_info( dvdnav, &title, &part );
+                if (result != DVDNAV_STATUS_OK)
+                    msg_Err( p_demux, "title info: %s", dvdnav_err_to_string(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( dvdnav );
+        else
+            result = dvdnav_menu_call( dvdnav, menu );
+    }
+
+done:
+    return longest;
+}
+
+
+static int findMainFeature( demux_t *p_demux )
+{
+    demux_sys_t *p_sys = p_demux->p_sys;
+    dvdnav_t *dvdnav = p_sys->dvdnav;
+
+    int longest_root = -1;
+    int longest_title = -1;
+    int longest_fallback = 0;
+    int ii;
+    int64_t longest_duration_root = 0;
+    int64_t longest_duration_title = 0;
+    int64_t longest_duration_fallback = 0;
+    int64_t avg_duration = 0;
+    int avg_cnt = 0;
+    input_title_t * title;
+    input_title_t **list_title = p_sys->title;
+
+    msg_Dbg( p_demux, "searching menus for main feature" );
+    for ( ii = 0; ii < p_sys->i_title; ii++ )
+    {
+        title = list_title[ii];
+        if ( title->i_length > longest_duration_fallback )
+        {
+            longest_duration_fallback = title->i_length;
+            longest_fallback = ii;
+        }
+        if ( title->i_length > 60 * 30 * 1000 * 1000 )
+        {
+            avg_duration += title->i_length;
+            avg_cnt++;
+        }
+    }
+    if ( avg_cnt )
+        avg_duration /= avg_cnt;
+
+    title = list_title[longest_fallback];
+    if ( title )
+        msg_Dbg( p_demux, "longest title %d duration %ld",
+                 longest_fallback, title->i_length);
+
+    dvdnav_reset( dvdnav );
+    if ( skipToMenu( p_demux, 2000 ) )
+    {
+        longest_root = tryMenu( p_demux, DVD_MENU_Escape, longest_duration_fallback );
+        if ( longest_root >= 0 )
+        {
+            title = list_title[longest_root];
+            if ( title )
+            {
+                longest_duration_root = title->i_length;
+                msg_Dbg( p_demux, "found first-play title %d duration %ld",
+                         longest_root, title->i_length);
+            }
+        }
+        else
+            msg_Dbg( p_demux, "no first-play menu title found" );
+    }
+
+    if ( longest_root < 0 ||
+         (float)longest_duration_fallback * 0.7 > longest_duration_root)
+    {
+        longest_root = tryMenu( p_demux, DVD_MENU_Root, longest_duration_fallback );
+        if ( longest_root >= 0 )
+        {
+            title = list_title[longest_root];
+            if ( title )
+            {
+                longest_duration_root = title->i_length;
+                msg_Dbg( p_demux, "found root title %d duration %ld",
+                         longest_root, title->i_length );
+            }
+        }
+        else
+            msg_Dbg( p_demux, "no root menu title found" );
+    }
+
+    if ( longest_root < 0 ||
+         (float)longest_duration_fallback * 0.7 > longest_duration_root)
+    {
+        longest_title = tryMenu( p_demux, DVD_MENU_Title, longest_duration_fallback );
+        if ( longest_title >= 0 )
+        {
+            title = list_title[longest_title];
+            if ( title )
+            {
+                longest_duration_title = title->i_length;
+                msg_Dbg( p_demux, "found title %d duration %ld",
+                         longest_title, title->i_length );
+            }
+        }
+        else
+            msg_Dbg( p_demux, "no title menu title found" );
+    }
+
+    int64_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_duration < 60 * 30 * 1000 * 1000 )
+    {
+        float factor = (float)avg_duration / longest_duration;
+        if ( factor > 1 )
+            factor = 1 / factor;
+        if ( avg_cnt > 10 && factor < 0.7 )
+        {
+            longest = longest_fallback;
+            msg_Dbg( p_demux, "using longest title %d", longest );
+        }
+    }
+    return longest;
+}
-- 
2.5.0



More information about the vlc-devel mailing list