[vlc-devel] [PATCH] dvdnav: only read the auto-detected main title of the DVD when the dvdnav-menu option is set to false
Adrien Maglo
magsoft at videolan.org
Mon Mar 21 14:24:35 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 | 576 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 576 insertions(+)
diff --git a/modules/access/dvdnav.c b/modules/access/dvdnav.c
index 884c04f..9db8522 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
@@ -160,6 +161,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 +188,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 +217,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 )
{
@@ -307,6 +313,17 @@ static int CommonOpen( vlc_object_t *p_this,
msg_Warn( p_demux, "cannot go to dvd menu" );
}
}
+ else
+ {
+ /* 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;
+ }
+ }
i_angle = var_CreateGetInteger( p_demux, "dvdnav-angle" );
if( i_angle <= 0 ) i_angle = 1;
@@ -948,6 +965,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 +1634,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