[vlc-devel] [PATCH] subsdelay filter - initial version
Yuval Tze
yuvaltze at gmail.com
Sat Mar 19 23:34:25 CET 2011
---
include/vlc_filter.h | 11 +
modules/gui/qt4/components/extended_panels.cpp | 6 +
modules/gui/qt4/ui/video_effects.ui | 139 +++
modules/video_filter/Modules.am | 4 +-
modules/video_filter/subsdelay.c | 1317 ++++++++++++++++++++++++
src/libvlccore.sym | 1 +
src/misc/filter_chain.c | 30 +-
src/misc/subpicture.c | 10 +-
src/video_output/vout_subpictures.c | 3 +
9 files changed, 1511 insertions(+), 10 deletions(-)
create mode 100755 modules/video_filter/subsdelay.c
diff --git a/include/vlc_filter.h b/include/vlc_filter.h
index ea24344..b30295f 100644
--- a/include/vlc_filter.h
+++ b/include/vlc_filter.h
@@ -104,6 +104,7 @@ struct filter_t
struct
{
subpicture_t * (*pf_filter) ( filter_t *, mtime_t );
+ subpicture_t * (*pf_filter2) ( filter_t *, subpicture_t * );
subpicture_t * (*pf_buffer_new)( filter_t * );
void (*pf_buffer_del)( filter_t *, subpicture_t * );
int (*pf_mouse) ( filter_t *,
@@ -112,6 +113,7 @@ struct filter_t
const video_format_t * );
} sub;
#define pf_sub_filter u.sub.pf_filter
+#define pf_sub_filter2 u.sub.pf_filter2
#define pf_sub_buffer_new u.sub.pf_buffer_new
#define pf_sub_buffer_del u.sub.pf_buffer_del
#define pf_sub_mouse u.sub.pf_mouse
@@ -402,6 +404,15 @@ VLC_EXPORT( block_t *, filter_chain_AudioFilter, ( filter_chain_t *, block_t * )
VLC_EXPORT( void, filter_chain_SubFilter, ( filter_chain_t *, mtime_t ) );
/**
+ * Apply filter2 chain to subpictures.
+ *
+ * \param p_chain pointer to filter2 chain
+ * \param p_subpicture subpicture to apply filters on
+ * \return modified subpicture after applying all subpicture filters
+ */
+VLC_EXPORT( subpicture_t *, filter_chain_SubFilter2, ( filter_chain_t *, subpicture_t * ) );
+
+/**
* Apply the filter chain to a mouse state.
*
* It will be applied from the output to the input. It makes sense only
diff --git a/modules/gui/qt4/components/extended_panels.cpp b/modules/gui/qt4/components/extended_panels.cpp
index afb6681..1d511c8 100644
--- a/modules/gui/qt4/components/extended_panels.cpp
+++ b/modules/gui/qt4/components/extended_panels.cpp
@@ -238,6 +238,12 @@ ExtVideo::ExtVideo( intf_thread_t *_p_intf, QTabWidget *_parent ) :
_parent->removeTab( _parent->indexOf( ui.tab_atmo ) );
}
+
+ SETUP_VFILTER( subsdelay )
+ SETUP_VFILTER_OPTION( subsdelayModeCombo, currentIndexChanged( QString ) )
+ SETUP_VFILTER_OPTION( subsdelayFactorSpin, valueChanged( double ) )
+ SETUP_VFILTER_OPTION( subsdelayOverlapSpin, valueChanged( int ) )
+
#undef SETUP_VFILTER
#undef SETUP_VFILTER_OPTION
diff --git a/modules/gui/qt4/ui/video_effects.ui b/modules/gui/qt4/ui/video_effects.ui
index d79679f..5a4d7db 100644
--- a/modules/gui/qt4/ui/video_effects.ui
+++ b/modules/gui/qt4/ui/video_effects.ui
@@ -1599,6 +1599,141 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="tab_4">
+ <attribute name="title">
+ <string>Subtitles</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_11" columnstretch="0,0" columnminimumwidth="0,0">
+ <item row="0" column="0">
+ <widget class="QGroupBox" name="subsdelayEnable">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="title">
+ <string>Subtitles delay</string>
+ </property>
+ <property name="checkable">
+ <bool>true</bool>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_69">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Mode</string>
+ </property>
+ <property name="buddy">
+ <cstring>logoFileText</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QComboBox" name="subsdelayModeCombo">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_68">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Factor</string>
+ </property>
+ <property name="buddy">
+ <cstring>logoYSpin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QDoubleSpinBox" name="subsdelayFactorSpin">
+ <property name="maximum">
+ <double>20.000000000000000</double>
+ </property>
+ <property name="singleStep">
+ <double>0.100000000000000</double>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_67">
+ <property name="minimumSize">
+ <size>
+ <width>50</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Overlap</string>
+ </property>
+ <property name="buddy">
+ <cstring>logoXSpin</cstring>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QSpinBox" name="subsdelayOverlapSpin">
+ <property name="wrapping">
+ <bool>true</bool>
+ </property>
+ <property name="minimum">
+ <number>1</number>
+ </property>
+ <property name="maximum">
+ <number>4</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <spacer name="verticalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>40</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</widget>
<tabstops>
<tabstop>adjustEnable</tabstop>
@@ -1680,6 +1815,10 @@
<tabstop>eraseMaskText</tabstop>
<tabstop>eraseYSpin</tabstop>
<tabstop>eraseXSpin</tabstop>
+ <tabstop>subsdelayEnable</tabstop>
+ <tabstop>subsdelayModeCombo</tabstop>
+ <tabstop>subsdelayFactorSpin</tabstop>
+ <tabstop>subsdelayOverlapSpin</tabstop>
</tabstops>
<resources/>
<connections/>
diff --git a/modules/video_filter/Modules.am b/modules/video_filter/Modules.am
index 2b3bbe5..c48401d 100644
--- a/modules/video_filter/Modules.am
+++ b/modules/video_filter/Modules.am
@@ -76,6 +76,7 @@ SOURCES_atmo = atmo/atmo.cpp \
atmo/FnordlichtConnection.cpp atmo/FnordlichtConnection.h \
atmo/AtmoPacketQueue.cpp atmo/AtmoPacketQueue.h
SOURCES_gradfun = gradfun.c gradfun.h
+SOURCES_subsdelay = subsdelay.c
noinst_HEADERS = filter_picture.h
libvlc_LTLIBRARIES += \
@@ -121,4 +122,5 @@ libvlc_LTLIBRARIES += \
libwall_plugin.la \
libwave_plugin.la \
libgradfun_plugin.la \
- libyuvp_plugin.la
+ libyuvp_plugin.la \
+ libsubsdelay_plugin.la
diff --git a/modules/video_filter/subsdelay.c b/modules/video_filter/subsdelay.c
new file mode 100755
index 0000000..db5f81e
--- /dev/null
+++ b/modules/video_filter/subsdelay.c
@@ -0,0 +1,1317 @@
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+#include <vlc_filter.h>
+#include <vlc_subpicture.h>
+#include <vlc_osd.h>
+#include <vlc_es.h>
+#include <stdlib.h>
+
+/*****************************************************************************
+ * Local constants
+ *****************************************************************************/
+
+/* descriptions */
+
+#define SUBSDELAY_HELP N_("Change subtitles delay")
+
+#define MODE_TEXT N_( "Delay calculation mode" )
+#define MODE_LONGTEXT N_( \
+ "Absolute delay - add absolute delay to each subtitle. " \
+ "Relative to source delay - multiply subtitle delay. " \
+ "Relative to source content - determine subtitle delay from its content (text)." )
+
+#define FACTOR_TEXT N_( "Calculation factor" )
+#define FACTOR_LONGTEXT N_( "Calculation factor. " \
+ "In Absolute delay mode the factor represents seconds.")
+
+#define OVERLAP_TEXT N_( "Maximum overlapping subtitles" )
+#define OVERLAP_LONGTEXT N_( "Maximum number of subtitles allowed at the same time." )
+
+#define MIN_ALPHA_TEXT N_( "Minimum alpha value" )
+#define MIN_ALPHA_LONGTEXT N_( \
+ "Alpha value of the earliest subtitle, where 0 is fully transparent and 255 is fully opaque." )
+
+#define MIN_STOPS_INTERVAL_TEXT N_( "Interval between two disappearances" )
+#define MIN_STOPS_INTERVAL_LONGTEXT N_( \
+ "Minimum time (in milliseconds) that subtitle should stay after its predecessor has disappeared " \
+ "(subtitle delay will be extended to meet this requirement)." )
+
+#define MIN_STOP_START_INTERVAL_TEXT N_( "Interval between disappearance and appearance" )
+#define MIN_STOP_START_INTERVAL_LONGTEXT N_( \
+ "Minimum time (in milliseconds) between subtitle disappearance and newer subtitle appearance " \
+ "(earlier subtitle delay will be extended to fill the gap)." )
+
+#define MIN_START_STOP_INTERVAL_TEXT N_( "Interval between appearance and disappearance" )
+#define MIN_START_STOP_INTERVAL_LONGTEXT N_( \
+ "Minimum time (in milliseconds) that subtitle should stay after newer subtitle has appeared " \
+ "(earlier subtitle delay will be shortened to avoid the overlap)." )
+
+static const int pi_mode_values[] = { 0, 1, 2 };
+static const char * const ppsz_mode_descriptions[] = { N_( "Absolute delay" ), N_( "Relative to source delay" ), N_(
+ "Relative to source content" ) };
+
+/* parameters */
+
+#define CFG_PREFIX "subsdelay-"
+
+#define CFG_MODE CFG_PREFIX "mode"
+#define CFG_FACTOR CFG_PREFIX "factor"
+#define CFG_OVERLAP CFG_PREFIX "overlap"
+
+#define CFG_MIN_ALPHA CFG_PREFIX "min-alpha"
+#define CFG_MIN_STOPS_INTERVAL CFG_PREFIX "min-stops"
+#define CFG_MIN_STOP_START_INTERVAL CFG_PREFIX "min-stop-start"
+#define CFG_MIN_START_STOP_INTERVAL CFG_PREFIX "min-start-stop"
+
+
+/* max subtitles handled on the heap */
+#define SUBSDELAY_MAX_ENTRIES 16
+
+/* factor convert macros */
+#define INT_FACTOR_BASE 1000
+#define FLOAT_FACTOR_TO_INT_FACTOR( x ) (int)( ( x ) * INT_FACTOR_BASE )
+#define INT_FACTOR_TO_MICROSEC( x ) ( ( x ) * ( 1000000 / INT_FACTOR_BASE ) )
+#define INT_FACTOR_TO_RANK_FACTOR( x ) ( x )
+#define MILLISEC_TO_MICROSEC( x ) ( ( x ) * 1000 )
+
+
+#define SUBSDELAY_MODE_ABSOLUTE 0
+#define SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY 1
+#define SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT 2
+
+
+/*****************************************************************************
+ * subsdelay_heap_entry_t: Heap entry
+ *****************************************************************************/
+
+typedef subpicture_updater_sys_t subsdelay_heap_entry_t;
+
+struct subpicture_updater_sys_t
+{
+ subpicture_t *p_subpic; /* subtitle */
+
+ filter_t *p_filter; /* assigned subsdelay filter */
+
+ subsdelay_heap_entry_t *p_next; /* next entry */
+
+ bool b_update_stop; /* stop value should be updated */
+
+ bool b_update_ephemer; /* ephemer value should be updated */
+
+ bool b_update_position; /* subtitle position should be updated */
+
+ bool b_check_empty; /* subtitle content should be checked */
+
+ mtime_t i_new_stop; /* new stop value */
+
+ subpicture_updater_t new_updater; /* new updater */
+
+ video_format_t last_fmt_src; /* last source format */
+
+ video_format_t last_fmt_dst; /* last dest format */
+
+ /* subtitle source values: */
+
+ mtime_t i_source_start; /* source subtitle start value */
+
+ mtime_t i_source_stop; /* source subtitle stop value */
+
+ int i_source_alpha; /* source subtitle alpha value */
+
+ bool b_source_ephemer; /* source subtitle ephemer value */
+
+ subpicture_updater_t source_updater; /* source updater */
+
+ void (*pf_source_destroy)( subpicture_t * ); /* source subtitle destroy callback */
+
+ void (*pf_source_update_regions)( spu_t *, subpicture_t *, const video_format_t *, mtime_t ); /* source subtitle update regions callback */
+};
+
+/*****************************************************************************
+ * subsdelay_heap_t: Heap
+ *****************************************************************************/
+
+typedef struct
+{
+ vlc_mutex_t lock; /* heap global lock */
+
+ subsdelay_heap_entry_t *p_list[SUBSDELAY_MAX_ENTRIES]; /* subtitles entries array */
+
+ subsdelay_heap_entry_t *p_head; /* subtitles entries linked list */
+
+ int i_count; /* subtitles count */
+
+} subsdelay_heap_t;
+
+
+
+/*****************************************************************************
+* filter_sys_t: Subsdelay filter descriptor
+ *****************************************************************************/
+
+struct filter_sys_t
+{
+ int i_mode; /* delay calculation mode */
+
+ int i_factor; /* calculation factor */
+
+ int i_overlap; /* max overlap */
+
+ int i_min_alpha; /* oldest subtitle alpha value */
+
+ int64_t i_min_stops_interval;
+
+ int64_t i_min_stop_start_interval;
+
+ int64_t i_min_start_stop_interval;
+
+ subsdelay_heap_t heap; /* subpictures list */
+
+ filter_t *p_next; /* next subsdelay filter */
+};
+
+
+/*****************************************************************************
+ * Filter functions
+ *****************************************************************************/
+
+static int SubsdelayCreate( vlc_object_t * );
+
+static void SubsdelayDestroy( vlc_object_t * );
+
+static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic );
+
+static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
+ void *p_data );
+
+/*****************************************************************************
+ * Helper functions
+ *****************************************************************************/
+
+static void SubsdelayEnforceDelayRules( filter_t *p_filter );
+
+static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry );
+
+static void SubsdelayRecalculateDelays( filter_t *p_filter );
+
+static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha );
+
+static int SubsdelayGetTextRank( char *psz_text );
+
+static bool SubsdelayIsTextEmpty( char *psz_text );
+
+
+
+/*****************************************************************************
+ * Subpicture wrappers
+ *****************************************************************************/
+
+static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
+ bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts );
+
+static void SubpicDestroyWrapper( subpicture_t *p_subpic );
+
+/*****************************************************************************
+ * Heap functions
+ *****************************************************************************/
+
+static void SubsdelayHeapInit( subsdelay_heap_t *p_heap );
+
+static void SubsdelayHeapClean( subsdelay_heap_t *p_heap );
+
+static int SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter );
+
+static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry );
+
+static void SubsdelayRebuildList( subsdelay_heap_t *p_heap );
+
+static void SubsdelayHeapLock( subsdelay_heap_t *p_heap );
+
+static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap );
+
+static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_subpic, filter_t *p_filter );
+
+static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry );
+
+/* heap / entries special functionality */
+
+static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date );
+
+static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+vlc_module_begin()
+ set_shortname( _("Subsdelay") )
+ set_description( _("Subtitles delay") )
+ set_help( SUBSDELAY_HELP )
+ set_capability( "sub filter", 0 )
+ set_callbacks( SubsdelayCreate, SubsdelayDestroy )
+ set_category( CAT_VIDEO )
+ set_subcategory( SUBCAT_VIDEO_SUBPIC )
+
+ add_integer( CFG_MODE, 1, MODE_TEXT, MODE_LONGTEXT, false )
+ change_integer_list( pi_mode_values, ppsz_mode_descriptions )
+
+ add_float_with_range( CFG_FACTOR, 2, 0, 20, NULL, FACTOR_TEXT, FACTOR_LONGTEXT, false )
+
+ add_integer_with_range( CFG_OVERLAP, 3, 1, 4, NULL, OVERLAP_TEXT, OVERLAP_LONGTEXT, false )
+
+ add_integer_with_range( CFG_MIN_ALPHA, 125, 0, 255, NULL, MIN_ALPHA_TEXT, MIN_ALPHA_LONGTEXT, false )
+
+ set_section( N_("Overlap fix"), NULL )
+
+ add_integer( CFG_MIN_STOPS_INTERVAL, 1000, MIN_STOPS_INTERVAL_TEXT, MIN_STOPS_INTERVAL_LONGTEXT, false )
+
+ add_integer( CFG_MIN_START_STOP_INTERVAL, 1000, MIN_START_STOP_INTERVAL_TEXT,
+ MIN_START_STOP_INTERVAL_LONGTEXT, false )
+
+ add_integer( CFG_MIN_STOP_START_INTERVAL, 1000, MIN_STOP_START_INTERVAL_TEXT,
+ MIN_STOP_START_INTERVAL_LONGTEXT, false )
+
+ vlc_module_end ()
+
+static const char * const ppsz_filter_options[] = { "mode", "factor", "overlap", NULL };
+
+/*****************************************************************************
+ * SubsdelayCreate: Create subsdelay filter
+ *****************************************************************************/
+static int SubsdelayCreate( vlc_object_t *p_this )
+{
+ filter_t *p_filter = (filter_t *) p_this;
+ filter_sys_t *p_sys;
+
+ /* allocate structure */
+ p_sys = (filter_sys_t*) malloc( sizeof(filter_sys_t) );
+
+ if( p_sys == NULL )
+ {
+ return VLC_ENOMEM;
+ }
+
+ /* init parameters */
+
+ p_sys->i_mode = var_CreateGetIntegerCommand( p_filter, CFG_MODE );
+ var_AddCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
+
+ p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( var_CreateGetFloatCommand( p_filter, CFG_FACTOR ) );
+ var_AddCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
+
+ p_sys->i_overlap = var_CreateGetIntegerCommand( p_filter, CFG_OVERLAP );
+ var_AddCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
+
+ p_sys->i_min_alpha = var_CreateGetIntegerCommand( p_filter, CFG_MIN_ALPHA );
+ var_AddCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
+
+ p_sys->i_min_stops_interval
+ = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOPS_INTERVAL ) );
+ var_AddCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
+
+ p_sys->i_min_stop_start_interval
+ = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOP_START_INTERVAL ) );
+ var_AddCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
+
+ p_sys->i_min_start_stop_interval
+ = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_START_STOP_INTERVAL ) );
+ var_AddCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
+
+ p_filter->p_sys = p_sys;
+ p_filter->pf_sub_filter2 = SubsdelayFilter;
+
+ config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options, p_filter->p_cfg );
+
+ SubsdelayHeapInit( &p_sys->heap );
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * SubsdelayDestroy: Destroy subsdelay filter
+ *****************************************************************************/
+static void SubsdelayDestroy( vlc_object_t *p_this )
+{
+ filter_t *p_filter = (filter_t *) p_this;
+ filter_sys_t *p_sys = p_filter->p_sys;
+
+ SubsdelayHeapClean( &p_sys->heap );
+
+ /* destroy parameters */
+
+ var_DelCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_MODE );
+
+ var_DelCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_FACTOR );
+
+ var_DelCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_OVERLAP );
+
+ var_DelCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_MIN_ALPHA );
+
+ var_DelCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_MIN_STOPS_INTERVAL );
+
+ var_DelCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_MIN_STOP_START_INTERVAL );
+
+ var_DelCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
+ var_Destroy( p_filter, CFG_MIN_START_STOP_INTERVAL );
+
+ free( p_sys );
+}
+
+/*****************************************************************************
+ * SubsdelayFilter: Filter new subpicture
+ *****************************************************************************/
+static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic )
+{
+ subsdelay_heap_t *p_heap;
+ int i_res;
+
+ if( !p_subpic->b_subtitle )
+ {
+ return p_subpic;
+ }
+
+ p_heap = &p_filter->p_sys->heap;
+
+ /* add subpicture to the heap */
+
+ SubsdelayHeapLock( p_heap );
+
+ i_res = SubsdelayHeapPush( p_heap, p_subpic, p_filter );
+ if( i_res != VLC_SUCCESS)
+ {
+ SubsdelayHeapUnlock( p_heap );
+
+ msg_Err(p_filter, "Can't add subpicture to the heap");
+
+ return p_subpic;
+ }
+
+ if( p_subpic->b_ephemer )
+ {
+ /* set a relativly long delay in hope that the next subtitle
+ will arrive in this time and the real delay could be determined */
+
+ p_subpic->i_stop = p_subpic->i_start + 20000000; /* start + 20 sec */
+ p_subpic->b_ephemer = false;
+ }
+
+
+ SubsdelayEnforceDelayRules( p_filter );
+
+ SubsdelayHeapUnlock( p_heap );
+
+ return p_subpic;
+}
+
+/*****************************************************************************
+ * SubsdelayCallback: Subsdelay parameters callback
+ *****************************************************************************/
+static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
+ void *p_data )
+{
+ filter_sys_t *p_sys = (filter_sys_t *) p_data;
+
+ VLC_UNUSED( oldval );
+
+ if( !strcmp( psz_var, CFG_MODE ) )
+ {
+ p_sys->i_mode = newval.i_int;
+ }
+ else if( !strcmp( psz_var, CFG_FACTOR ) )
+ {
+ p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( newval.f_float );
+ }
+ else if( !strcmp( psz_var, CFG_OVERLAP ) )
+ {
+ p_sys->i_overlap = newval.i_int;
+ }
+ else if( !strcmp( psz_var, CFG_MIN_ALPHA ) )
+ {
+ p_sys->i_min_alpha = newval.i_int;
+ }
+ else if( !strcmp( psz_var, CFG_MIN_STOPS_INTERVAL ) )
+ {
+ p_sys->i_min_stops_interval = MILLISEC_TO_MICROSEC( newval.i_int );
+ }
+ else if( !strcmp( psz_var, CFG_MIN_STOP_START_INTERVAL ) )
+ {
+ p_sys->i_min_stop_start_interval = MILLISEC_TO_MICROSEC( newval.i_int );
+ }
+ else if( !strcmp( psz_var, CFG_MIN_START_STOP_INTERVAL ) )
+ {
+ p_sys->i_min_start_stop_interval = MILLISEC_TO_MICROSEC( newval.i_int );
+ }
+ else
+ {
+ return VLC_ENOVAR;
+ }
+
+ SubsdelayRecalculateDelays( (filter_t *) p_this );
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * SubsdelayHeapInit: Initialize heap
+ *****************************************************************************/
+static void SubsdelayHeapInit( subsdelay_heap_t *p_heap )
+{
+ p_heap->i_count = 0;
+ p_heap->p_head = NULL;
+
+ vlc_mutex_init( &p_heap->lock );
+}
+
+/*****************************************************************************
+ * SubsdelayHeapClean: Clean heap and remove all its entries
+ *****************************************************************************/
+static void SubsdelayHeapClean( subsdelay_heap_t *p_heap )
+{
+ subsdelay_heap_entry_t *p_entry;
+
+ SubsdelayHeapLock( p_heap );
+
+ for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
+ {
+ p_entry->p_filter = NULL;
+ }
+
+ SubsdelayHeapUnlock( p_heap );
+
+
+ vlc_mutex_destroy( &p_heap->lock );
+}
+
+/*****************************************************************************
+ * SubsdelayHeapPush: Add new subpicture to the heap
+ *****************************************************************************/
+static int SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter )
+{
+ subsdelay_heap_entry_t *p_entry, *p_last, *p_new_entry;
+
+ if( p_heap->i_count >= SUBSDELAY_MAX_ENTRIES )
+ {
+ return VLC_EGENERIC; /* the heap is full */
+ }
+
+ p_new_entry = SubsdelayEntryCreate( p_subpic, p_filter );
+
+ if( !p_new_entry )
+ {
+ return VLC_ENOMEM;
+ }
+
+
+ p_last = NULL;
+
+ for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
+ {
+ if( p_entry->i_source_start > p_subpic->i_start )
+ {
+ /* the new entry should be inserted before p_entry */
+ break;
+ }
+
+ p_last = p_entry;
+ }
+
+ if( p_last )
+ {
+ p_new_entry->p_next = p_last->p_next;
+ p_last->p_next = p_new_entry;
+
+
+ if( p_last->b_update_ephemer )
+ {
+ /* the correct stop value can be determined */
+
+ p_last->i_source_stop = p_new_entry->i_source_start;
+ p_last->b_update_ephemer = false;
+ }
+ }
+ else
+ {
+ p_new_entry->p_next = p_heap->p_head;
+ p_heap->p_head = p_new_entry;
+ }
+
+
+ /* rebuild list */
+
+ SubsdelayRebuildList( p_heap );
+
+ return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * SubsdelayHeapRemove: Remove entry
+ *****************************************************************************/
+static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry )
+{
+ subsdelay_heap_entry_t *p_curr, *p_prev;
+
+ p_prev = NULL;
+
+ for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
+ {
+ if( p_curr == p_entry )
+ {
+ break;
+ }
+
+ p_prev = p_curr;
+ }
+
+ if( p_prev )
+ {
+ p_prev->p_next = p_entry->p_next;
+ }
+ else
+ {
+ p_heap->p_head = p_entry->p_next;
+ }
+
+ SubsdelayRebuildList( p_heap );
+}
+
+
+static void SubsdelayRebuildList( subsdelay_heap_t *p_heap )
+{
+ subsdelay_heap_entry_t *p_curr;
+ int i_index;
+
+ i_index = 0;
+ for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
+ {
+ p_heap->p_list[i_index] = p_curr;
+ i_index++;
+ }
+
+ p_heap->i_count = i_index;
+}
+
+/*****************************************************************************
+ * SubsdelayHeapLock: Lock the heap
+ *****************************************************************************/
+static void SubsdelayHeapLock( subsdelay_heap_t *p_heap )
+{
+ vlc_mutex_lock( &p_heap->lock );
+}
+
+/*****************************************************************************
+ * SubsdelayHeapUnlock: Unlock the heap
+ *****************************************************************************/
+static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap )
+{
+ vlc_mutex_unlock( &p_heap->lock );
+}
+
+
+/*****************************************************************************
+ * SubsdelayHeapCreateEntry: Create new entry
+ *****************************************************************************/
+static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_subpic, filter_t *p_filter )
+{
+ subsdelay_heap_entry_t *p_entry;
+
+ /* allocate structure */
+
+ p_entry = (subsdelay_heap_entry_t *) malloc( sizeof( subsdelay_heap_entry_t ) );
+
+ if( p_entry == NULL )
+ {
+ return NULL;
+ }
+
+ /* initialize entry */
+
+ p_entry->p_subpic = p_subpic;
+ p_entry->p_filter = p_filter;
+ p_entry->p_next = NULL;
+ p_entry->b_update_stop = true;
+ p_entry->b_update_ephemer = p_subpic->b_ephemer;
+ p_entry->b_update_position = true;
+ p_entry->b_check_empty = true;
+ p_entry->i_new_stop = p_subpic->i_stop;
+ p_entry->i_source_stop = p_subpic->i_stop;
+ p_entry->i_source_start = p_subpic->i_start;
+ p_entry->i_source_alpha = p_subpic->i_alpha;
+ p_entry->b_source_ephemer = p_subpic->b_ephemer;
+ p_entry->source_updater = p_subpic->updater;
+
+ video_format_Init( &p_entry->last_fmt_src, 0 );
+ video_format_Init( &p_entry->last_fmt_dst, 0 );
+
+ p_entry->new_updater.p_sys = p_entry;
+ p_entry->new_updater.pf_destroy = SubpicDestroyWrapper;
+ p_entry->new_updater.pf_update = NULL;
+ p_entry->new_updater.pf_validate = SubpicValidateWrapper;
+
+ p_subpic->updater = p_entry->new_updater;
+
+
+ return p_entry;
+}
+
+/*****************************************************************************
+ * SubsdelayEntryDestroy: Destroy entry and restore subpic source values
+ *****************************************************************************/
+static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry )
+{
+ /* restore source values */
+
+ p_entry->p_subpic->i_stop = p_entry->i_source_stop;
+ p_entry->p_subpic->i_start = p_entry->i_source_start;
+ p_entry->p_subpic->i_alpha = p_entry->i_source_alpha;
+ p_entry->p_subpic->b_ephemer = p_entry->b_source_ephemer;
+ p_entry->p_subpic->updater = p_entry->source_updater;
+
+ free( p_entry );
+}
+
+/*****************************************************************************
+ * SubsdelayHeapCountOverlap: Count overlapping subtitles at a given time
+ *****************************************************************************/
+static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date )
+{
+ subsdelay_heap_entry_t *p_curr;
+ int i_overlaps;
+
+ VLC_UNUSED( p_heap );
+
+ i_overlaps = 0;
+
+ for( p_curr = p_entry->p_next; p_curr != NULL; p_curr = p_curr->p_next )
+ {
+ if( p_curr->i_source_start > i_date )
+ {
+ break;
+ }
+
+ if( !p_curr->b_check_empty ) /* subtitle checked, and it's not empty */
+ {
+ i_overlaps++;
+ }
+ }
+
+ return i_overlaps;
+}
+
+/*****************************************************************************
+ * SubsdelayEntryNewStopValueUpdated: Update source stop value after the new
+ * stop value was changed
+ *****************************************************************************/
+static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry )
+{
+ if( !p_entry->b_update_stop )
+ {
+ p_entry->p_subpic->i_stop = p_entry->i_new_stop - 100000; /* 0.1 sec less */
+ }
+}
+
+/*****************************************************************************
+ * SubsdelayEnforceDelayRules: Update subtitles delay after adding new
+ * subtitle or changing subtitle stop value
+ *****************************************************************************/
+static void SubsdelayEnforceDelayRules( filter_t *p_filter )
+{
+ subsdelay_heap_entry_t ** p_list;
+ int i, j, i_count, i_overlap;
+ int64_t i_offset;
+ int64_t i_min_stops_interval;
+ int64_t i_min_stop_start_interval;
+ int64_t i_min_start_stop_interval;
+
+ p_list = p_filter->p_sys->heap.p_list;
+ i_count = p_filter->p_sys->heap.i_count;
+
+ i_overlap = p_filter->p_sys->i_overlap;
+ i_min_stops_interval = p_filter->p_sys->i_min_stops_interval;
+ i_min_stop_start_interval = p_filter->p_sys->i_min_stop_start_interval;
+ i_min_start_stop_interval = p_filter->p_sys->i_min_start_stop_interval;
+
+ /* step 1 - enforce min stops interval rule (extend delays) */
+
+ /* look for:
+ [subtitle 1 ..............]
+ [subtitle 2 ..............]
+ |<-MinStopsInterval->|
+
+ * and extend newer subtitle:
+ [subtitle 1 ..............]
+ [subtitle 2 ............................]
+ |<-MinStopsInterval->|
+ */
+
+ for( i = 0; i < i_count - 1; i++ )
+ {
+ p_list[i + 1]->i_new_stop = __MAX( p_list[i + 1]->i_new_stop,
+ p_list[i]->i_new_stop + i_min_stops_interval );
+ }
+
+ /* step 2 - enforce min stop start interval rule (extend delays) */
+
+ /* look for:
+ [subtitle 1 .........]
+ [subtitle 2 ....]
+ |<-MinStopStartInterval->|
+
+ * and fill the gap:
+ [subtitle 1 ..................]
+ [subtitle 2 ....]
+ |<-MinStopStartInterval->|
+ */
+
+ for( i = 0; i < i_count; i++ )
+ {
+ for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
+ {
+ i_offset = p_list[j]->i_source_start - p_list[i]->i_new_stop;
+
+ if( i_offset <= 0 )
+ {
+ continue;
+ }
+
+ if( i_offset < i_min_stop_start_interval )
+ {
+ p_list[i]->i_new_stop = p_list[j]->i_source_start;
+ }
+
+ break;
+ }
+ }
+
+ /* step 3 - enforce min start stop interval rule (shorten delays) */
+
+ /* look for:
+ [subtitle 1 ............]
+ [subtitle 2 ....................]
+ |<-MinStartStopInterval->|
+
+ * and remove the overlapping part:
+ [subtitle 1 ...]
+ [subtitle 2 ....................]
+ |<-MinStartStopInterval->|
+ */
+
+
+ for( i = 0; i < i_count; i++ )
+ {
+ for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
+ {
+ i_offset = p_list[i]->i_new_stop - p_list[j]->i_source_start;
+
+ if( i_offset <= 0 )
+ {
+ break;
+ }
+
+ if( i_offset < i_min_start_stop_interval )
+ {
+ p_list[i]->i_new_stop = p_list[j]->i_source_start;
+ break;
+ }
+ }
+ }
+
+ /* step 4 - enforce max overlapping rule (shorten delays)*/
+
+ /* look for: (overlap = 2)
+ [subtitle 1 ..............]
+ [subtitle 2 ..............]
+ [subtitle 3 ..............]
+
+
+ * and cut older subtitle:
+ [subtitle 1 .....]
+ [subtitle 2 ..............]
+ [subtitle 3 ..............]
+ */
+
+ for( i = 0; i < i_count - i_overlap; i++ )
+ {
+ if( p_list[i]->i_new_stop > p_list[i + i_overlap]->i_source_start )
+ {
+ p_list[i]->i_new_stop = p_list[i + i_overlap]->i_source_start;
+ }
+ }
+
+ /* finally - update all */
+
+ for( i = 0; i < i_count; i++ )
+ {
+ SubsdelayEntryNewStopValueUpdated( p_list[i] );
+ }
+}
+
+/*****************************************************************************
+ * SubsdelayRecalculateDelays: Recalculate subtitles delay after changing
+ * one of the filter's parameters
+ *****************************************************************************/
+static void SubsdelayRecalculateDelays( filter_t *p_filter )
+{
+ subsdelay_heap_entry_t ** p_list;
+ subsdelay_heap_entry_t *p_entry;
+ int i, i_count;
+
+ p_list = p_filter->p_sys->heap.p_list;
+ i_count = p_filter->p_sys->heap.i_count;
+
+ for( i = 0; i < i_count; i++ )
+ {
+ p_entry = p_list[i];
+
+ if( !p_entry->b_update_ephemer )
+ {
+ p_entry->i_new_stop = p_entry->i_source_start + SubsdelayEstimateDelay( p_filter, p_entry );
+ p_entry->b_update_stop = false;
+
+ SubsdelayEnforceDelayRules( p_filter );
+ }
+ }
+
+}
+
+/*****************************************************************************
+ * SubpicDestroyWrapper: Subpicture destroy callback wrapper
+ *****************************************************************************/
+static void SubpicDestroyWrapper( subpicture_t *p_subpic )
+{
+ subsdelay_heap_entry_t *p_entry;
+ subsdelay_heap_t *p_heap;
+
+ p_entry = p_subpic->updater.p_sys;
+
+ if( !p_entry )
+ {
+ return;
+ }
+
+ if( p_entry->p_filter )
+ {
+ p_heap = &p_entry->p_filter->p_sys->heap;
+
+ SubsdelayHeapLock( p_heap );
+ SubsdelayHeapRemove( p_heap, p_entry );
+ SubsdelayHeapUnlock( p_heap );
+ }
+
+ SubsdelayEntryDestroy( p_entry );
+
+ if( p_subpic->updater.pf_destroy )
+ {
+ p_subpic->updater.pf_destroy( p_subpic );
+ }
+
+
+}
+
+/*****************************************************************************
+ * SubpicValidateWrapper: Subpicture validate callback wrapper
+ *****************************************************************************/
+static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
+ bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts )
+{
+ subsdelay_heap_entry_t *p_entry;
+ subsdelay_heap_t *p_heap;
+ filter_t *p_filter;
+
+ int i_overlapping;
+ mtime_t i_new_ts;
+
+ int i_last_x, i_last_y, i_last_align;
+ bool b_position_saved;
+
+
+ i_last_x = 0;
+ i_last_y = 0;
+ i_last_align = 0;
+
+
+ p_entry = p_subpic->updater.p_sys;
+ if( !p_entry )
+ {
+ return VLC_SUCCESS;
+ }
+
+ p_filter = p_entry->p_filter;
+
+ if( !p_filter )
+ {
+ /* filter doesn't exist so this entry is no longer needed */
+ SubsdelayEntryDestroy( p_entry );
+
+ if( p_subpic->updater.pf_validate) /* call the original callback */
+ {
+ return p_subpic->updater.pf_validate( p_subpic, has_src_changed, p_fmt_src, has_dst_changed, p_fmt_dst, i_ts );
+ }
+
+ return VLC_SUCCESS;
+ }
+
+
+ p_heap = &p_filter->p_sys->heap;
+
+ SubsdelayHeapLock( p_heap );
+
+
+ b_position_saved = false;
+
+ if( p_subpic->p_region )
+ {
+ i_last_x = p_subpic->p_region->i_x;
+ i_last_y = p_subpic->p_region->i_y;
+ i_last_align = p_subpic->p_region->i_align;
+
+ b_position_saved = true;
+ }
+
+
+ i_new_ts = p_entry->i_source_start +
+ ( (double)( p_entry->i_source_stop - p_entry->i_source_start ) * ( i_ts - p_entry->i_source_start ) ) /
+ ( p_entry->i_new_stop - p_entry->i_source_start );
+
+
+ if( p_entry->source_updater.pf_validate )
+ {
+ p_subpic->updater = p_entry->source_updater;
+
+ if( p_subpic->updater.pf_validate( p_subpic,
+ !video_format_IsSimilar( p_fmt_src, &p_entry->last_fmt_src ), p_fmt_src,
+ !video_format_IsSimilar( p_fmt_dst, &p_entry->last_fmt_dst ), p_fmt_dst,
+ i_new_ts ) )
+ {
+ subpicture_region_ChainDelete( p_subpic->p_region );
+ p_subpic->p_region = NULL;
+
+ p_subpic->updater.pf_update( p_subpic, p_fmt_src, p_fmt_dst, i_new_ts );
+
+ video_format_Clean( &p_entry->last_fmt_src );
+ video_format_Clean( &p_entry->last_fmt_dst );
+
+ video_format_Copy( &p_entry->last_fmt_src, p_fmt_src );
+ video_format_Copy( &p_entry->last_fmt_dst, p_fmt_dst );
+ }
+
+ p_subpic->updater = p_entry->new_updater;
+ }
+
+
+ if( p_entry->b_check_empty && p_subpic->p_region )
+ {
+ if( SubsdelayIsTextEmpty( p_subpic->p_region->psz_html ) ||
+ SubsdelayIsTextEmpty( p_subpic->p_region->psz_text ) )
+ {
+ /* remove empty subtitle */
+ p_subpic->b_ephemer = false;
+ p_subpic->i_stop = p_subpic->i_start;
+ SubsdelayHeapRemove( p_heap, p_entry );
+ SubsdelayEntryDestroy( p_entry );
+
+ SubsdelayHeapUnlock( p_heap );
+
+ return VLC_SUCCESS;
+ }
+
+ p_entry->b_check_empty = false;
+ }
+
+ if( p_entry->b_update_stop && !p_entry->b_update_ephemer )
+ {
+ p_entry->i_new_stop = p_entry->i_source_start + SubsdelayEstimateDelay( p_filter, p_entry );
+ p_entry->b_update_stop = false;
+
+ SubsdelayEnforceDelayRules( p_filter );
+ }
+
+ i_overlapping = SubsdelayHeapCountOverlap( p_heap, p_entry, i_ts );
+
+ p_subpic->i_alpha = SubsdelayCalculateAlpha( p_filter, i_overlapping, p_entry->i_source_alpha );
+
+ if( p_entry->b_update_position )
+ {
+ p_subpic->b_absolute = false;
+
+ if( p_subpic->p_region )
+ {
+ p_subpic->p_region->i_x = 0;
+ p_subpic->p_region->i_y = 10;
+ p_subpic->p_region->i_align = ( p_subpic->p_region->i_align & ( ~SUBPICTURE_ALIGN_MASK ) )
+ | SUBPICTURE_ALIGN_BOTTOM;
+ }
+
+ p_entry->b_update_position = false;
+ }
+ else if( b_position_saved )
+ {
+ p_subpic->b_absolute = true;
+
+ if( p_subpic->p_region )
+ {
+ p_subpic->p_region->i_x = i_last_x;
+ p_subpic->p_region->i_y = i_last_y;
+ p_subpic->p_region->i_align = i_last_align;
+ }
+ }
+
+ SubsdelayHeapUnlock( p_heap );
+
+ return VLC_SUCCESS;
+
+
+/*
+ if( p_entry->source_updater.pf_validate )
+ {
+ i_new_ts = p_entry->i_source_start + ( ( p_entry->i_source_stop - p_entry->i_source_start ) * ( i_ts
+ - p_entry->i_source_start ) ) / ( p_entry->i_new_stop - p_entry->i_source_start );
+
+ p_subpic->updater = p_entry->source_updater;
+ res = p_subpic->updater.pf_validate( p_subpic, has_src_changed, p_fmt_src, has_dst_changed, p_fmt_dst, i_new_ts );
+ p_subpic->updater = p_entry->new_updater;
+
+ return res;
+ }
+
+
+ todo:
+ do update region wrapper stuff
+ call source validate
+ if res = fail (need update)
+ clean region
+ call source update
+ finish update region wrapper stuff
+ return success (already updated..)
+
+
+ return VLC_SUCCESS;*/
+}
+
+/*****************************************************************************
+ * SubsdelayEstimateDelay: Calculate new subtitle delay according to its
+ * content and the calculation mode
+ *****************************************************************************/
+static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry )
+{
+ int i_mode;
+ int i_factor;
+ int i_rank;
+
+ i_mode = p_filter->p_sys->i_mode;
+ i_factor = p_filter->p_sys->i_factor;
+
+ if( i_mode == SUBSDELAY_MODE_ABSOLUTE )
+ {
+ return ( p_entry->i_source_stop - p_entry->i_source_start + INT_FACTOR_TO_MICROSEC( i_factor ) );
+ }
+
+ if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT )
+ {
+ if( p_entry->p_subpic && p_entry->p_subpic->p_region && ( p_entry->p_subpic->p_region->psz_text
+ || p_entry->p_subpic->p_region->psz_html ) )
+ {
+ if( p_entry->p_subpic->p_region->psz_text )
+ {
+ i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_text );
+ }
+ else
+ {
+ i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_html );
+ }
+
+ return ( i_rank * INT_FACTOR_TO_RANK_FACTOR( i_factor ) );
+ }
+
+ /* content is unavailable, calculation mode should be based on source delay */
+ i_mode = SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY;
+ }
+
+ if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY )
+ {
+ return ( ( i_factor * ( p_entry->i_source_stop - p_entry->i_source_start ) ) / INT_FACTOR_BASE );
+ }
+
+ return 10000000;
+}
+
+/*****************************************************************************
+ * SubsdelayCalculateAlpha: Calculate subtitle alpha according to source alpha
+ * value and number of overlapping subtitles
+ *****************************************************************************/
+static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha )
+{
+ int i_new_alpha;
+ int i_min_alpha;
+
+ i_min_alpha = p_filter->p_sys->i_min_alpha;
+
+ if( i_overlapping > p_filter->p_sys->i_overlap - 1)
+ {
+ i_overlapping = p_filter->p_sys->i_overlap - 1;
+ }
+
+ switch ( p_filter->p_sys->i_overlap )
+ {
+ case 1:
+ i_new_alpha = 255;
+ break;
+
+ case 2:
+ i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha );
+ break;
+
+ case 3:
+ i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 2;
+ break;
+
+ default:
+ i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 3;
+ break;
+ }
+
+ return ( i_source_alpha * i_new_alpha ) / 255;
+}
+
+/*****************************************************************************
+ * SubsdelayGetWordRank: Calculate single word rank according to its length
+ *****************************************************************************/
+static int SubsdelayGetWordRank( int i_length )
+{
+ static int p_rank[20] = { 0 };
+ int i;
+
+ if( p_rank[0] == 0 )
+ {
+ /* inititalize rank array */
+ p_rank[0] = 300; /* base rank */
+ p_rank[1] = p_rank[0];
+ p_rank[2] = p_rank[1];
+
+ for( i = 3; i < 20; i++ )
+ {
+ p_rank[i] = (int) ( 1.1 * p_rank[i - 1] );
+ }
+ }
+
+ if( i_length > 20 )
+ {
+ i_length = 20;
+ }
+
+ if( i_length < 1 )
+ {
+ return 0;
+ }
+
+ return p_rank[i_length - 1];
+}
+
+/*****************************************************************************
+ * SubsdelayGetTextRank: Calculate text rank
+ *****************************************************************************/
+static int SubsdelayGetTextRank( char *psz_text )
+{
+ bool b_skip_esc;
+ bool b_skip_tag;
+ char c;
+
+ int i, i_word_length, i_rank;
+
+ i = 0;
+ i_word_length = 0;
+ i_rank = 0;
+
+ b_skip_esc = false;
+ b_skip_tag = false;
+
+ while ( psz_text[i] != '\0' )
+ {
+ c = psz_text[i];
+ i++;
+
+ if( c == '\\' && !b_skip_esc )
+ {
+ b_skip_esc = true;
+ continue;
+ }
+
+ if( psz_text[i] == '<' )
+ {
+ b_skip_tag = true;
+ continue;
+ }
+
+ if( !b_skip_esc && !b_skip_tag )
+ {
+ if( c == ' ' || c == ',' || c == '.' || c == '-' || c == '?' || c == '!' ) /* common delimiters */
+ {
+ if( i_word_length > 0 )
+ {
+ i_rank += SubsdelayGetWordRank( i_word_length );
+ }
+
+ i_word_length = 0;
+ }
+ else
+ {
+ i_word_length++;
+ }
+ }
+
+ b_skip_esc = false;
+
+ if( c == '>' )
+ {
+ b_skip_tag = false;
+ }
+
+ }
+
+ if( i_word_length > 0 )
+ {
+ i_rank += SubsdelayGetWordRank( i_word_length );
+ }
+
+ return i_rank;
+}
+
+/*****************************************************************************
+ * SubsdelayIsTextEmpty: Check if the text contains spaces only
+ *****************************************************************************/
+static bool SubsdelayIsTextEmpty( char *psz_text )
+{
+ int i;
+
+ if( !psz_text )
+ {
+ return false;
+ }
+
+ i = 0;
+ while ( psz_text[i] != '\0' )
+ {
+ if( psz_text[i] != ' ' )
+ {
+ return false;
+ }
+
+ i++;
+ }
+
+ return true;
+}
+
diff --git a/src/libvlccore.sym b/src/libvlccore.sym
index 30173a5..965c387 100644
--- a/src/libvlccore.sym
+++ b/src/libvlccore.sym
@@ -134,6 +134,7 @@ filter_chain_MouseEvent
filter_chain_New
filter_chain_Reset
filter_chain_SubFilter
+filter_chain_SubFilter2
filter_chain_VideoFilter
filter_chain_VideoFlush
filter_ConfigureBlend
diff --git a/src/misc/filter_chain.c b/src/misc/filter_chain.c
index cb9be4a..f21d974 100644
--- a/src/misc/filter_chain.c
+++ b/src/misc/filter_chain.c
@@ -299,11 +299,33 @@ void filter_chain_SubFilter( filter_chain_t *p_chain,
for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
{
filter_t *p_filter = &f->filter;
- subpicture_t *p_subpic = p_filter->pf_sub_filter( p_filter, display_date );
- /* XXX I find that spu_t cast ugly */
- if( p_subpic )
- spu_PutSubpicture( (spu_t*)p_chain->p_this, p_subpic );
+
+ if( p_filter->pf_sub_filter )
+ {
+ subpicture_t *p_subpic = p_filter->pf_sub_filter( p_filter, display_date );
+
+ /* XXX I find that spu_t cast ugly */
+ if( p_subpic )
+ spu_PutSubpicture( (spu_t*)p_chain->p_this, p_subpic );
+ }
+ }
+}
+
+subpicture_t *filter_chain_SubFilter2( filter_chain_t *p_chain, subpicture_t *p_subpic )
+{
+ for( chained_filter_t *f = p_chain->first; f != NULL; f = f->next )
+ {
+ filter_t *p_filter = &f->filter;
+
+ if( p_filter->pf_sub_filter2 )
+ {
+ p_subpic = p_filter->pf_sub_filter2( p_filter, p_subpic );
+
+ if( !p_subpic )
+ break;
+ }
}
+ return p_subpic;
}
int filter_chain_MouseFilter( filter_chain_t *p_chain, vlc_mouse_t *p_dst, const vlc_mouse_t *p_src )
diff --git a/src/misc/subpicture.c b/src/misc/subpicture.c
index 3e84a27..a1ee386 100644
--- a/src/misc/subpicture.c
+++ b/src/misc/subpicture.c
@@ -152,11 +152,11 @@ void subpicture_Update( subpicture_t *p_subpicture,
if( !p_upd->pf_validate )
return;
if( !p_upd->pf_validate( p_subpicture,
- !video_format_IsSimilar( p_fmt_src,
- &p_private->src ), p_fmt_src,
- !video_format_IsSimilar( p_fmt_dst,
- &p_private->dst ), p_fmt_dst,
- i_ts ) )
+ p_private ? !video_format_IsSimilar( p_fmt_src, &p_private->src ) : false,
+ p_fmt_src,
+ p_private ? !video_format_IsSimilar( p_fmt_dst, &p_private->dst ) : false,
+ p_fmt_dst,
+ i_ts ) )
return;
subpicture_region_ChainDelete( p_subpicture->p_region );
diff --git a/src/video_output/vout_subpictures.c b/src/video_output/vout_subpictures.c
index dc21fa7..af10f73 100644
--- a/src/video_output/vout_subpictures.c
+++ b/src/video_output/vout_subpictures.c
@@ -1434,6 +1434,9 @@ void spu_PutSubpicture( spu_t *p_spu, subpicture_t *p_subpic )
{
spu_private_t *p_sys = p_spu->p;
+ /* Run filter chain on the new subpicture */
+ p_subpic = filter_chain_SubFilter2(p_spu->p->p_chain, p_subpic);
+
/* SPU_DEFAULT_CHANNEL always reset itself */
if( p_subpic->i_channel == SPU_DEFAULT_CHANNEL )
spu_ClearChannel( p_spu, SPU_DEFAULT_CHANNEL );
--
1.7.1
More information about the vlc-devel
mailing list