1 /*****************************************************************************
2 * subsdelay.c : Subsdelay plugin for vlc
3 *****************************************************************************
4 * Copyright © 2011 VideoLAN
7 * Authors: Yuval Tze <yuvaltze@gmail.com>
9 * This program is free software; you can redistribute it and/or modify it
10 * under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 2.1 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public License
20 * along with this program; if not, write to the Free Software Foundation,
21 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
22 *****************************************************************************/
24 /*****************************************************************************
26 *****************************************************************************/
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_subpicture.h>
39 /*****************************************************************************
41 *****************************************************************************/
45 #define SUBSDELAY_HELP N_("Change subtitle delay")
47 #define MODE_TEXT N_( "Delay calculation mode" )
48 #define MODE_LONGTEXT N_( \
49 "Absolute delay - add absolute delay to each subtitle. " \
50 "Relative to source delay - multiply subtitle delay. " \
51 "Relative to source content - determine subtitle delay from its content (text)." )
53 #define FACTOR_TEXT N_( "Calculation factor" )
54 #define FACTOR_LONGTEXT N_( "Calculation factor. " \
55 "In Absolute delay mode the factor represents seconds.")
57 #define OVERLAP_TEXT N_( "Maximum overlapping subtitles" )
58 #define OVERLAP_LONGTEXT N_( "Maximum number of subtitles allowed at the same time." )
60 #define MIN_ALPHA_TEXT N_( "Minimum alpha value" )
61 #define MIN_ALPHA_LONGTEXT N_( \
62 "Alpha value of the earliest subtitle, where 0 is fully transparent and 255 is fully opaque." )
64 #define MIN_STOPS_INTERVAL_TEXT N_( "Interval between two disappearances" )
65 #define MIN_STOPS_INTERVAL_LONGTEXT N_( \
66 "Minimum time (in milliseconds) that subtitle should stay after its predecessor has disappeared " \
67 "(subtitle delay will be extended to meet this requirement)." )
69 #define MIN_STOP_START_INTERVAL_TEXT N_( "Interval between disappearance and appearance" )
70 #define MIN_STOP_START_INTERVAL_LONGTEXT N_( \
71 "Minimum time (in milliseconds) between subtitle disappearance and newer subtitle appearance " \
72 "(earlier subtitle delay will be extended to fill the gap)." )
74 #define MIN_START_STOP_INTERVAL_TEXT N_( "Interval between appearance and disappearance" )
75 #define MIN_START_STOP_INTERVAL_LONGTEXT N_( \
76 "Minimum time (in milliseconds) that subtitle should stay after newer subtitle has appeared " \
77 "(earlier subtitle delay will be shortened to avoid the overlap)." )
79 static const int pi_mode_values[] = { 0, 1, 2 };
80 static const char * const ppsz_mode_descriptions[] = { N_( "Absolute delay" ), N_( "Relative to source delay" ), N_(
81 "Relative to source content" ) };
85 #define CFG_PREFIX "subsdelay-"
87 #define CFG_MODE CFG_PREFIX "mode"
88 #define CFG_FACTOR CFG_PREFIX "factor"
89 #define CFG_OVERLAP CFG_PREFIX "overlap"
91 #define CFG_MIN_ALPHA CFG_PREFIX "min-alpha"
92 #define CFG_MIN_STOPS_INTERVAL CFG_PREFIX "min-stops"
93 #define CFG_MIN_STOP_START_INTERVAL CFG_PREFIX "min-stop-start"
94 #define CFG_MIN_START_STOP_INTERVAL CFG_PREFIX "min-start-stop"
97 /* max subtitles handled on the heap */
98 #define SUBSDELAY_MAX_ENTRIES 16
100 /* factor convert macros */
101 #define INT_FACTOR_BASE 1000
102 #define FLOAT_FACTOR_TO_INT_FACTOR( x ) (int)( ( x ) * INT_FACTOR_BASE )
103 #define INT_FACTOR_TO_MICROSEC( x ) ( ( x ) * ( 1000000 / INT_FACTOR_BASE ) )
104 #define INT_FACTOR_TO_RANK_FACTOR( x ) ( x )
105 #define MILLISEC_TO_MICROSEC( x ) ( ( x ) * 1000 )
108 #define SUBSDELAY_MODE_ABSOLUTE 0
109 #define SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY 1
110 #define SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT 2
113 /*****************************************************************************
114 * subsdelay_heap_entry_t: Heap entry
115 *****************************************************************************/
117 typedef subpicture_updater_sys_t subsdelay_heap_entry_t;
119 struct subpicture_updater_sys_t
121 subpicture_t *p_subpic; /* local subtitle */
123 subpicture_t *p_source; /* subtitle source */
125 filter_t *p_filter; /* assigned subsdelay filter */
127 subsdelay_heap_entry_t *p_next; /* next entry */
129 bool b_update_stop; /* new stop value should be calculated */
131 bool b_update_ephemer; /* actual stop value is unknown */
133 bool b_update_position; /* subtitle position should be updated */
135 bool b_check_empty; /* subtitle content should be checked */
137 mtime_t i_new_stop; /* new stop value */
139 /* last region data*/
145 int i_last_region_align;
147 bool b_last_region_saved;
150 /*****************************************************************************
151 * subsdelay_heap_t: Heap
152 *****************************************************************************/
156 vlc_mutex_t lock; /* heap global lock */
158 subsdelay_heap_entry_t *p_list[SUBSDELAY_MAX_ENTRIES]; /* subtitles entries array */
160 subsdelay_heap_entry_t *p_head; /* subtitles entries linked list */
162 int i_count; /* subtitles count */
168 /*****************************************************************************
169 * filter_sys_t: Subsdelay filter descriptor
170 *****************************************************************************/
174 int i_mode; /* delay calculation mode */
176 int i_factor; /* calculation factor */
178 int i_overlap; /* max overlap */
180 int i_min_alpha; /* oldest subtitle alpha value */
182 int64_t i_min_stops_interval;
184 int64_t i_min_stop_start_interval;
186 int64_t i_min_start_stop_interval;
188 subsdelay_heap_t heap; /* subpictures list */
192 /*****************************************************************************
194 *****************************************************************************/
196 static int SubsdelayCreate( vlc_object_t * );
198 static void SubsdelayDestroy( vlc_object_t * );
200 static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic );
202 static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
205 /*****************************************************************************
207 *****************************************************************************/
209 static void SubsdelayEnforceDelayRules( filter_t *p_filter );
211 static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry );
213 static void SubsdelayRecalculateDelays( filter_t *p_filter );
215 static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha );
217 static int SubsdelayGetTextRank( char *psz_text );
219 static bool SubsdelayIsTextEmpty( char *psz_text );
221 /*****************************************************************************
222 * Subpicture functions
223 *****************************************************************************/
225 static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
226 bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts );
228 static void SubpicUpdateWrapper( subpicture_t *p_subpic, const video_format_t *p_fmt_src,
229 const video_format_t *p_fmt_dst, mtime_t i_ts );
231 static void SubpicDestroyWrapper( subpicture_t *p_subpic );
233 static void SubpicLocalUpdate( subpicture_t* p_subpic, mtime_t i_ts );
235 static bool SubpicIsEmpty( subpicture_t* p_subpic );
237 static subpicture_t *SubpicClone( subpicture_t *p_source, subpicture_updater_t *updater );
239 static void SubpicDestroyClone( subpicture_t *p_subpic );
241 /*****************************************************************************
243 *****************************************************************************/
245 static void SubsdelayHeapInit( subsdelay_heap_t *p_heap );
247 static void SubsdelayHeapDestroy( subsdelay_heap_t *p_heap );
249 static subsdelay_heap_entry_t *SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter );
251 static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry );
253 static void SubsdelayRebuildList( subsdelay_heap_t *p_heap );
255 static void SubsdelayHeapLock( subsdelay_heap_t *p_heap );
257 static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap );
259 static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_subpic, filter_t *p_filter );
261 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry );
263 /* heap / entries special functionality */
265 static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date );
267 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry );
269 /*****************************************************************************
271 *****************************************************************************/
274 set_shortname( _("Subsdelay") )
275 set_description( _("Subtitle delay") )
276 set_help( SUBSDELAY_HELP )
277 set_capability( "sub filter", 0 )
278 set_callbacks( SubsdelayCreate, SubsdelayDestroy )
279 set_category( CAT_VIDEO )
280 set_subcategory( SUBCAT_VIDEO_SUBPIC )
282 add_integer( CFG_MODE, 1, MODE_TEXT, MODE_LONGTEXT, false )
283 change_integer_list( pi_mode_values, ppsz_mode_descriptions )
285 add_float_with_range( CFG_FACTOR, 2, 0, 20, FACTOR_TEXT, FACTOR_LONGTEXT, false )
287 add_integer_with_range( CFG_OVERLAP, 3, 1, 4, OVERLAP_TEXT, OVERLAP_LONGTEXT, false )
289 add_integer_with_range( CFG_MIN_ALPHA, 70, 0, 255, MIN_ALPHA_TEXT, MIN_ALPHA_LONGTEXT, false )
291 set_section( N_("Overlap fix"), NULL )
293 add_integer( CFG_MIN_STOPS_INTERVAL, 1000, MIN_STOPS_INTERVAL_TEXT, MIN_STOPS_INTERVAL_LONGTEXT, false )
295 add_integer( CFG_MIN_START_STOP_INTERVAL, 1000, MIN_START_STOP_INTERVAL_TEXT,
296 MIN_START_STOP_INTERVAL_LONGTEXT, false )
298 add_integer( CFG_MIN_STOP_START_INTERVAL, 1000, MIN_STOP_START_INTERVAL_TEXT,
299 MIN_STOP_START_INTERVAL_LONGTEXT, false )
303 static const char * const ppsz_filter_options[] = { "mode", "factor", "overlap", NULL };
305 /*****************************************************************************
306 * SubsdelayCreate: Create subsdelay filter
307 *****************************************************************************/
308 static int SubsdelayCreate( vlc_object_t *p_this )
310 filter_t *p_filter = (filter_t *) p_this;
313 /* allocate structure */
314 p_sys = (filter_sys_t*) malloc( sizeof(filter_sys_t) );
321 /* init parameters */
323 p_sys->i_mode = var_CreateGetIntegerCommand( p_filter, CFG_MODE );
324 var_AddCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
326 p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( var_CreateGetFloatCommand( p_filter, CFG_FACTOR ) );
327 var_AddCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
329 p_sys->i_overlap = var_CreateGetIntegerCommand( p_filter, CFG_OVERLAP );
330 var_AddCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
332 p_sys->i_min_alpha = var_CreateGetIntegerCommand( p_filter, CFG_MIN_ALPHA );
333 var_AddCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
335 p_sys->i_min_stops_interval
336 = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOPS_INTERVAL ) );
337 var_AddCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
339 p_sys->i_min_stop_start_interval
340 = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOP_START_INTERVAL ) );
341 var_AddCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
343 p_sys->i_min_start_stop_interval
344 = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_START_STOP_INTERVAL ) );
345 var_AddCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
347 p_filter->p_sys = p_sys;
348 p_filter->pf_sub_filter = SubsdelayFilter;
350 config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options, p_filter->p_cfg );
352 SubsdelayHeapInit( &p_sys->heap );
357 /*****************************************************************************
358 * SubsdelayDestroy: Destroy subsdelay filter
359 *****************************************************************************/
360 static void SubsdelayDestroy( vlc_object_t *p_this )
362 filter_t *p_filter = (filter_t *) p_this;
363 filter_sys_t *p_sys = p_filter->p_sys;
365 SubsdelayHeapDestroy( &p_sys->heap );
367 /* destroy parameters */
369 var_DelCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
370 var_Destroy( p_filter, CFG_MODE );
372 var_DelCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
373 var_Destroy( p_filter, CFG_FACTOR );
375 var_DelCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
376 var_Destroy( p_filter, CFG_OVERLAP );
378 var_DelCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
379 var_Destroy( p_filter, CFG_MIN_ALPHA );
381 var_DelCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
382 var_Destroy( p_filter, CFG_MIN_STOPS_INTERVAL );
384 var_DelCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
385 var_Destroy( p_filter, CFG_MIN_STOP_START_INTERVAL );
387 var_DelCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
388 var_Destroy( p_filter, CFG_MIN_START_STOP_INTERVAL );
393 /*****************************************************************************
394 * SubsdelayFilter: Filter new subpicture
395 *****************************************************************************/
396 static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic )
398 subsdelay_heap_t *p_heap;
399 subsdelay_heap_entry_t *p_entry;
401 if( !p_subpic->b_subtitle )
406 if( SubpicIsEmpty( p_subpic ) )
408 /* empty subtitles usually helps terminate ephemer subtitles, but this filter calculates the stop value anyway,
409 so this subtitle can be dropped */
411 subpicture_Delete( p_subpic );
416 p_heap = &p_filter->p_sys->heap;
418 /* add subpicture to the heap */
420 SubsdelayHeapLock( p_heap );
422 p_entry = SubsdelayHeapPush( p_heap, p_subpic, p_filter );
425 SubsdelayHeapUnlock( p_heap );
427 msg_Err(p_filter, "Can't add subpicture to the heap");
432 p_subpic = p_entry->p_subpic; /* get the local subpic */
434 if( p_subpic->b_ephemer )
436 /* set a relativly long delay in hope that the next subtitle
437 will arrive in this time and the real delay could be determined */
439 p_subpic->i_stop = p_subpic->i_start + 20000000; /* start + 20 sec */
440 p_subpic->b_ephemer = false;
444 SubsdelayEnforceDelayRules( p_filter );
446 SubsdelayHeapUnlock( p_heap );
451 /*****************************************************************************
452 * SubsdelayCallback: Subsdelay parameters callback
453 *****************************************************************************/
454 static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
457 filter_sys_t *p_sys = (filter_sys_t *) p_data;
459 VLC_UNUSED( oldval );
461 SubsdelayHeapLock( &p_sys->heap );
463 if( !strcmp( psz_var, CFG_MODE ) )
465 p_sys->i_mode = newval.i_int;
467 else if( !strcmp( psz_var, CFG_FACTOR ) )
469 p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( newval.f_float );
471 else if( !strcmp( psz_var, CFG_OVERLAP ) )
473 p_sys->i_overlap = newval.i_int;
475 else if( !strcmp( psz_var, CFG_MIN_ALPHA ) )
477 p_sys->i_min_alpha = newval.i_int;
479 else if( !strcmp( psz_var, CFG_MIN_STOPS_INTERVAL ) )
481 p_sys->i_min_stops_interval = MILLISEC_TO_MICROSEC( newval.i_int );
483 else if( !strcmp( psz_var, CFG_MIN_STOP_START_INTERVAL ) )
485 p_sys->i_min_stop_start_interval = MILLISEC_TO_MICROSEC( newval.i_int );
487 else if( !strcmp( psz_var, CFG_MIN_START_STOP_INTERVAL ) )
489 p_sys->i_min_start_stop_interval = MILLISEC_TO_MICROSEC( newval.i_int );
493 SubsdelayHeapUnlock( &p_sys->heap );
497 SubsdelayRecalculateDelays( (filter_t *) p_this );
499 SubsdelayHeapUnlock( &p_sys->heap );
503 /*****************************************************************************
504 * SubsdelayHeapInit: Initialize heap
505 *****************************************************************************/
506 static void SubsdelayHeapInit( subsdelay_heap_t *p_heap )
509 p_heap->p_head = NULL;
511 vlc_mutex_init( &p_heap->lock );
514 /*****************************************************************************
515 * SubsdelayHeapDestroy: Destroy the heap and remove its entries
516 *****************************************************************************/
517 static void SubsdelayHeapDestroy( subsdelay_heap_t *p_heap )
519 subsdelay_heap_entry_t *p_entry;
521 SubsdelayHeapLock( p_heap );
523 for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
525 p_entry->p_subpic->i_stop = p_entry->p_source->i_stop;
527 p_entry->p_filter = NULL;
530 SubsdelayHeapUnlock( p_heap );
532 vlc_mutex_destroy( &p_heap->lock );
535 /*****************************************************************************
536 * SubsdelayHeapPush: Add new subpicture to the heap
537 *****************************************************************************/
538 static subsdelay_heap_entry_t *SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter )
540 subsdelay_heap_entry_t *p_entry, *p_last, *p_new_entry;
542 if( p_heap->i_count >= SUBSDELAY_MAX_ENTRIES )
544 return NULL; /* the heap is full */
547 p_new_entry = SubsdelayEntryCreate( p_subpic, p_filter );
557 for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
559 if( p_entry->p_source->i_start > p_subpic->i_start )
561 /* the new entry should be inserted before p_entry */
570 p_new_entry->p_next = p_last->p_next;
571 p_last->p_next = p_new_entry;
574 if( p_last->b_update_ephemer )
576 /* the correct stop value can be determined */
578 p_last->p_source->i_stop = p_new_entry->p_source->i_start;
579 p_last->b_update_ephemer = false;
584 p_new_entry->p_next = p_heap->p_head;
585 p_heap->p_head = p_new_entry;
591 SubsdelayRebuildList( p_heap );
596 /*****************************************************************************
597 * SubsdelayHeapRemove: Remove entry
598 *****************************************************************************/
599 static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry )
601 subsdelay_heap_entry_t *p_curr, *p_prev;
605 for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
607 if( p_curr == p_entry )
617 p_prev->p_next = p_entry->p_next;
621 p_heap->p_head = p_entry->p_next;
624 p_entry->p_filter = NULL;
626 SubsdelayRebuildList( p_heap );
630 static void SubsdelayRebuildList( subsdelay_heap_t *p_heap )
632 subsdelay_heap_entry_t *p_curr;
636 for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
638 p_heap->p_list[i_index] = p_curr;
642 p_heap->i_count = i_index;
645 /*****************************************************************************
646 * SubsdelayHeapLock: Lock the heap
647 *****************************************************************************/
648 static void SubsdelayHeapLock( subsdelay_heap_t *p_heap )
650 vlc_mutex_lock( &p_heap->lock );
653 /*****************************************************************************
654 * SubsdelayHeapUnlock: Unlock the heap
655 *****************************************************************************/
656 static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap )
658 vlc_mutex_unlock( &p_heap->lock );
662 /*****************************************************************************
663 * SubsdelayHeapCreateEntry: Create new entry
664 *****************************************************************************/
665 static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_source, filter_t *p_filter )
667 subsdelay_heap_entry_t *p_entry;
669 subpicture_t *p_new_subpic;
671 subpicture_updater_t updater;
673 /* allocate structure */
675 p_entry = (subsdelay_heap_entry_t *) malloc( sizeof( subsdelay_heap_entry_t ) );
682 /* initialize local updater */
684 updater.p_sys = p_entry;
685 updater.pf_validate = SubpicValidateWrapper;
686 updater.pf_update = SubpicUpdateWrapper;
687 updater.pf_destroy = SubpicDestroyWrapper;
689 /* create new subpic */
691 p_new_subpic = SubpicClone( p_source, &updater );
699 /* initialize entry */
701 p_entry->p_subpic = p_new_subpic;
702 p_entry->p_source = p_source;
703 p_entry->p_filter = p_filter;
704 p_entry->p_next = NULL;
705 p_entry->b_update_stop = true;
706 p_entry->b_update_ephemer = p_source->b_ephemer;
707 p_entry->b_update_position = true;
708 p_entry->b_check_empty = true;
709 p_entry->i_new_stop = p_source->i_stop;
710 p_entry->b_last_region_saved = false;
711 p_entry->i_last_region_x = 0;
712 p_entry->i_last_region_y = 0;
713 p_entry->i_last_region_align = 0;
718 /*****************************************************************************
719 * SubsdelayEntryDestroy: Destroy entry
720 *****************************************************************************/
721 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry )
723 SubpicDestroyClone( p_entry->p_source );
727 /*****************************************************************************
728 * SubsdelayHeapCountOverlap: Count overlapping subtitles at a given time
729 *****************************************************************************/
730 static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date )
732 subsdelay_heap_entry_t *p_curr;
735 VLC_UNUSED( p_heap );
739 for( p_curr = p_entry->p_next; p_curr != NULL; p_curr = p_curr->p_next )
741 if( p_curr->p_source->i_start > i_date )
746 if( !p_curr->b_check_empty ) /* subtitle was checked, and it's not empty */
755 /*****************************************************************************
756 * SubsdelayEntryNewStopValueUpdated: Update source stop value after the new
757 * stop value was changed
758 *****************************************************************************/
759 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry )
761 if( !p_entry->b_update_stop )
763 p_entry->p_subpic->i_stop = p_entry->i_new_stop - 100000; /* 0.1 sec less */
767 /*****************************************************************************
768 * SubsdelayEnforceDelayRules: Update subtitles delay after adding new
769 * subtitle or changing subtitle stop value
770 *****************************************************************************/
771 static void SubsdelayEnforceDelayRules( filter_t *p_filter )
773 subsdelay_heap_entry_t ** p_list;
774 int i, j, i_count, i_overlap;
776 int64_t i_min_stops_interval;
777 int64_t i_min_stop_start_interval;
778 int64_t i_min_start_stop_interval;
780 p_list = p_filter->p_sys->heap.p_list;
781 i_count = p_filter->p_sys->heap.i_count;
783 i_overlap = p_filter->p_sys->i_overlap;
784 i_min_stops_interval = p_filter->p_sys->i_min_stops_interval;
785 i_min_stop_start_interval = p_filter->p_sys->i_min_stop_start_interval;
786 i_min_start_stop_interval = p_filter->p_sys->i_min_start_stop_interval;
788 /* step 1 - enforce min stops interval rule (extend delays) */
791 [subtitle 1 ..............]
792 [subtitle 2 ..............]
793 |<-MinStopsInterval->|
795 * and extend newer subtitle:
796 [subtitle 1 ..............]
797 [subtitle 2 ............................]
798 |<-MinStopsInterval->|
801 for( i = 0; i < i_count - 1; i++ )
803 p_list[i + 1]->i_new_stop = __MAX( p_list[i + 1]->i_new_stop,
804 p_list[i]->i_new_stop + i_min_stops_interval );
807 /* step 2 - enforce min stop start interval rule (extend delays) */
810 [subtitle 1 .........]
812 |<-MinStopStartInterval->|
815 [subtitle 1 ..................]
817 |<-MinStopStartInterval->|
820 for( i = 0; i < i_count; i++ )
822 for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
824 i_offset = p_list[j]->p_source->i_start - p_list[i]->i_new_stop;
831 if( i_offset < i_min_stop_start_interval )
833 p_list[i]->i_new_stop = p_list[j]->p_source->i_start;
840 /* step 3 - enforce min start stop interval rule (shorten delays) */
843 [subtitle 1 ............]
844 [subtitle 2 ....................]
845 |<-MinStartStopInterval->|
847 * and remove the overlapping part:
849 [subtitle 2 ....................]
850 |<-MinStartStopInterval->|
854 for( i = 0; i < i_count; i++ )
856 for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
858 i_offset = p_list[i]->i_new_stop - p_list[j]->p_source->i_start;
865 if( i_offset < i_min_start_stop_interval )
867 p_list[i]->i_new_stop = p_list[j]->p_source->i_start;
873 /* step 4 - enforce max overlapping rule (shorten delays)*/
875 /* look for: (overlap = 2)
876 [subtitle 1 ..............]
877 [subtitle 2 ..............]
878 [subtitle 3 ..............]
881 * and cut older subtitle:
883 [subtitle 2 ..............]
884 [subtitle 3 ..............]
887 for( i = 0; i < i_count - i_overlap; i++ )
889 if( p_list[i]->i_new_stop > p_list[i + i_overlap]->p_source->i_start )
891 p_list[i]->i_new_stop = p_list[i + i_overlap]->p_source->i_start;
895 /* finally - update all */
897 for( i = 0; i < i_count; i++ )
899 SubsdelayEntryNewStopValueUpdated( p_list[i] );
903 /*****************************************************************************
904 * SubsdelayRecalculateDelays: Recalculate subtitles delay after changing
905 * one of the filter's parameters
906 *****************************************************************************/
907 static void SubsdelayRecalculateDelays( filter_t *p_filter )
909 subsdelay_heap_entry_t *p_curr;
911 for( p_curr = p_filter->p_sys->heap.p_head; p_curr != NULL; p_curr = p_curr->p_next )
913 if( !p_curr->b_update_ephemer )
915 p_curr->i_new_stop = p_curr->p_source->i_start + SubsdelayEstimateDelay( p_filter, p_curr );
916 p_curr->b_update_stop = false;
920 SubsdelayEnforceDelayRules( p_filter );
923 /*****************************************************************************
924 * SubpicValidateWrapper: Subpicture validate callback wrapper
925 *****************************************************************************/
926 static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
927 bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts )
929 subsdelay_heap_entry_t *p_entry;
931 int i_result = VLC_SUCCESS;
933 p_entry = p_subpic->updater.p_sys;
939 /* call source validate */
940 if( p_entry->p_source->updater.pf_validate )
942 i_new_ts = p_entry->p_source->i_start +
943 ( (double)( p_entry->p_source->i_stop - p_entry->p_source->i_start ) * ( i_ts - p_entry->p_source->i_start ) ) /
944 ( p_entry->i_new_stop - p_entry->p_source->i_start );
946 i_result = p_entry->p_source->updater.pf_validate( p_entry->p_source, has_src_changed, p_fmt_src,
947 has_dst_changed, p_fmt_dst, i_new_ts );
951 p_entry->b_last_region_saved = false;
953 if( p_subpic->p_region )
956 p_entry->i_last_region_x = p_subpic->p_region->i_x;
957 p_entry->i_last_region_y = p_subpic->p_region->i_y;
958 p_entry->i_last_region_align = p_subpic->p_region->i_align;
960 p_entry->b_last_region_saved = true;
965 /* subpic update isn't necessary, so local update should be called here */
966 SubpicLocalUpdate( p_subpic, i_ts );
972 /*****************************************************************************
973 * SubpicUpdateWrapper: Subpicture update callback wrapper
974 *****************************************************************************/
975 static void SubpicUpdateWrapper( subpicture_t *p_subpic, const video_format_t *p_fmt_src,
976 const video_format_t *p_fmt_dst, mtime_t i_ts )
978 subsdelay_heap_entry_t *p_entry;
981 p_entry = p_subpic->updater.p_sys;
987 /* call source update */
988 if( p_entry->p_source->updater.pf_update )
990 i_new_ts = p_entry->p_source->i_start +
991 ( (double)( p_entry->p_source->i_stop - p_entry->p_source->i_start ) * ( i_ts - p_entry->p_source->i_start ) ) /
992 ( p_entry->i_new_stop - p_entry->p_source->i_start );
994 p_entry->p_source->p_region = p_entry->p_subpic->p_region;
996 p_entry->p_source->updater.pf_update( p_entry->p_source, p_fmt_src, p_fmt_dst, i_new_ts );
998 p_entry->p_subpic->p_region = p_entry->p_source->p_region;
1001 SubpicLocalUpdate( p_subpic, i_ts );
1004 /*****************************************************************************
1005 * SubpicDestroyCallback: Subpicture destroy callback
1006 *****************************************************************************/
1007 static void SubpicDestroyWrapper( subpicture_t *p_subpic )
1009 subsdelay_heap_entry_t *p_entry;
1010 subsdelay_heap_t *p_heap;
1012 p_entry = p_subpic->updater.p_sys;
1019 if( p_entry->p_filter )
1021 p_heap = &p_entry->p_filter->p_sys->heap;
1023 SubsdelayHeapLock( p_heap );
1024 SubsdelayHeapRemove( p_heap, p_entry );
1025 SubsdelayHeapUnlock( p_heap );
1028 SubsdelayEntryDestroy( p_entry );
1031 /*****************************************************************************
1032 * SubpicLocalUpdate: rewrite some of the subpicture parameters
1033 *****************************************************************************/
1034 static void SubpicLocalUpdate( subpicture_t* p_subpic, mtime_t i_ts )
1036 subsdelay_heap_entry_t *p_entry;
1037 subsdelay_heap_t *p_heap;
1042 p_entry = p_subpic->updater.p_sys;
1043 if( !p_entry || !p_entry->p_filter )
1048 p_filter = p_entry->p_filter;
1049 p_heap = &p_filter->p_sys->heap;
1051 SubsdelayHeapLock( p_heap );
1053 if( p_entry->b_check_empty && p_subpic->p_region )
1055 if( SubsdelayIsTextEmpty( p_subpic->p_region->psz_html ) ||
1056 SubsdelayIsTextEmpty( p_subpic->p_region->psz_text ) )
1058 /* remove empty subtitle */
1060 p_subpic->b_ephemer = false;
1061 p_subpic->i_stop = p_subpic->i_start;
1063 SubsdelayHeapRemove( p_heap, p_entry );
1065 SubsdelayHeapUnlock( p_heap );
1070 p_entry->b_check_empty = false;
1073 if( p_entry->b_update_stop && !p_entry->b_update_ephemer )
1075 p_entry->i_new_stop = p_entry->p_source->i_start + SubsdelayEstimateDelay( p_filter, p_entry );
1076 p_entry->b_update_stop = false;
1078 SubsdelayEnforceDelayRules( p_filter );
1081 i_overlapping = SubsdelayHeapCountOverlap( p_heap, p_entry, i_ts );
1083 p_subpic->i_alpha = SubsdelayCalculateAlpha( p_filter, i_overlapping, p_entry->p_source->i_alpha );
1085 if( p_entry->b_update_position )
1087 p_subpic->b_absolute = false;
1089 if( p_subpic->p_region )
1091 p_subpic->p_region->i_x = 0;
1092 p_subpic->p_region->i_y = 10;
1093 p_subpic->p_region->i_align = ( p_subpic->p_region->i_align & ( ~SUBPICTURE_ALIGN_MASK ) )
1094 | SUBPICTURE_ALIGN_BOTTOM;
1097 p_entry->b_update_position = false;
1099 else if( p_entry->b_last_region_saved )
1101 p_subpic->b_absolute = true;
1103 if( p_subpic->p_region )
1105 p_subpic->p_region->i_x = p_entry->i_last_region_x;
1106 p_subpic->p_region->i_y = p_entry->i_last_region_y;
1107 p_subpic->p_region->i_align = p_entry->i_last_region_align;
1111 SubsdelayHeapUnlock( p_heap );
1114 /*****************************************************************************
1115 * SubpicIsEmpty: subpic region contains empty string
1116 *****************************************************************************/
1117 static bool SubpicIsEmpty( subpicture_t* p_subpic )
1119 return ( p_subpic->p_region && ( SubsdelayIsTextEmpty( p_subpic->p_region->psz_html ) ||
1120 SubsdelayIsTextEmpty( p_subpic->p_region->psz_text ) ) );
1123 /*****************************************************************************
1124 * SubpicClone: Clone subpicture (shallow copy)
1125 *****************************************************************************/
1126 static subpicture_t *SubpicClone( subpicture_t *p_source, subpicture_updater_t *updater )
1128 subpicture_t *p_subpic;
1129 subpicture_updater_t subpic_updater;
1130 subpicture_private_t *p_subpic_private;
1132 p_subpic = subpicture_New( updater );
1139 /* save private members */
1140 subpic_updater = p_subpic->updater;
1141 p_subpic_private = p_subpic->p_private;
1143 /* copy the entire struct */
1144 memcpy( p_subpic, p_source, sizeof( subpicture_t ) );
1146 /* restore private members */
1147 p_subpic->updater = subpic_updater;
1148 p_subpic->p_private = p_subpic_private;
1153 /*****************************************************************************
1154 * SubpicDestroyClone: destroy cloned subpicture (shared references will not
1156 *****************************************************************************/
1157 static void SubpicDestroyClone( subpicture_t *p_subpic )
1159 p_subpic->p_region = NULL; /* don't destroy region */
1160 subpicture_Delete( p_subpic );
1163 /*****************************************************************************
1164 * SubsdelayEstimateDelay: Calculate new subtitle delay according to its
1165 * content and the calculation mode
1166 *****************************************************************************/
1167 static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry )
1173 i_mode = p_filter->p_sys->i_mode;
1174 i_factor = p_filter->p_sys->i_factor;
1176 if( i_mode == SUBSDELAY_MODE_ABSOLUTE )
1178 return ( p_entry->p_source->i_stop - p_entry->p_source->i_start + INT_FACTOR_TO_MICROSEC( i_factor ) );
1181 if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT )
1183 if( p_entry->p_subpic && p_entry->p_subpic->p_region && ( p_entry->p_subpic->p_region->psz_text
1184 || p_entry->p_subpic->p_region->psz_html ) )
1186 if( p_entry->p_subpic->p_region->psz_text )
1188 i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_text );
1192 i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_html );
1195 return ( i_rank * INT_FACTOR_TO_RANK_FACTOR( i_factor ) );
1198 /* content is unavailable, calculation mode should be based on source delay */
1199 i_mode = SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY;
1202 if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY )
1204 return ( ( i_factor * ( p_entry->p_source->i_stop - p_entry->p_source->i_start ) ) / INT_FACTOR_BASE );
1207 return 10000000; /* 10 sec */
1210 /*****************************************************************************
1211 * SubsdelayCalculateAlpha: Calculate subtitle alpha according to source alpha
1212 * value and number of overlapping subtitles
1213 *****************************************************************************/
1214 static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha )
1219 i_min_alpha = p_filter->p_sys->i_min_alpha;
1221 if( i_overlapping > p_filter->p_sys->i_overlap - 1)
1223 i_overlapping = p_filter->p_sys->i_overlap - 1;
1226 switch ( p_filter->p_sys->i_overlap )
1233 i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha );
1237 i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 2;
1241 i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 3;
1245 return ( i_source_alpha * i_new_alpha ) / 255;
1248 /*****************************************************************************
1249 * SubsdelayGetWordRank: Calculate single word rank according to its length
1250 *****************************************************************************/
1251 static int SubsdelayGetWordRank( int i_length )
1253 /* p_rank[0] = p_rank[1] = p_rank[2] = 300;
1254 for( i = 3; i < 20; i++ ) p_rank[i] = (int) ( 1.1 * p_rank[i - 1] ); */
1256 static const int p_rank[20] = { 300, 300, 300, 330, 363, 399, 438, 481, 529, 581,
1257 639, 702, 772, 849, 933, 1026, 1128, 1240, 1364, 1500 };
1269 return p_rank[i_length - 1];
1272 /*****************************************************************************
1273 * SubsdelayGetTextRank: Calculate text rank
1274 *****************************************************************************/
1275 static int SubsdelayGetTextRank( char *psz_text )
1281 int i, i_word_length, i_rank;
1290 while ( psz_text[i] != '\0' )
1295 if( c == '\\' && !b_skip_esc )
1301 if( psz_text[i] == '<' )
1307 if( !b_skip_esc && !b_skip_tag )
1309 if( c == ' ' || c == ',' || c == '.' || c == '-' || c == '?' || c == '!' ) /* common delimiters */
1311 if( i_word_length > 0 )
1313 i_rank += SubsdelayGetWordRank( i_word_length );
1333 if( i_word_length > 0 )
1335 i_rank += SubsdelayGetWordRank( i_word_length );
1341 /*****************************************************************************
1342 * SubsdelayIsTextEmpty: Check if the text contains spaces only
1343 *****************************************************************************/
1344 static bool SubsdelayIsTextEmpty( char *psz_text )
1351 psz_text += strspn( psz_text, " " );
1352 return !( *psz_text );