1 /*****************************************************************************
2 * subsdelay.c : Subsdelay plugin for vlc
3 *****************************************************************************
4 * Copyright © 2011 VideoLAN
6 * Authors: Yuval Tze <yuvaltze@gmail.com>
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 /*****************************************************************************
25 *****************************************************************************/
31 #include <vlc_common.h>
32 #include <vlc_plugin.h>
33 #include <vlc_filter.h>
34 #include <vlc_subpicture.h>
38 /*****************************************************************************
40 *****************************************************************************/
44 #define SUBSDELAY_HELP N_("Change subtitle delay")
46 #define MODE_TEXT N_( "Delay calculation mode" )
47 #define MODE_LONGTEXT N_( \
48 "Absolute delay - add absolute delay to each subtitle. " \
49 "Relative to source delay - multiply subtitle delay. " \
50 "Relative to source content - determine subtitle delay from its content (text)." )
52 #define FACTOR_TEXT N_( "Calculation factor" )
53 #define FACTOR_LONGTEXT N_( "Calculation factor. " \
54 "In Absolute delay mode the factor represents seconds.")
56 #define OVERLAP_TEXT N_( "Maximum overlapping subtitles" )
57 #define OVERLAP_LONGTEXT N_( "Maximum number of subtitles allowed at the same time." )
59 #define MIN_ALPHA_TEXT N_( "Minimum alpha value" )
60 #define MIN_ALPHA_LONGTEXT N_( \
61 "Alpha value of the earliest subtitle, where 0 is fully transparent and 255 is fully opaque." )
63 #define MIN_STOPS_INTERVAL_TEXT N_( "Interval between two disappearances" )
64 #define MIN_STOPS_INTERVAL_LONGTEXT N_( \
65 "Minimum time (in milliseconds) that subtitle should stay after its predecessor has disappeared " \
66 "(subtitle delay will be extended to meet this requirement)." )
68 #define MIN_STOP_START_INTERVAL_TEXT N_( "Interval between disappearance and appearance" )
69 #define MIN_STOP_START_INTERVAL_LONGTEXT N_( \
70 "Minimum time (in milliseconds) between subtitle disappearance and newer subtitle appearance " \
71 "(earlier subtitle delay will be extended to fill the gap)." )
73 #define MIN_START_STOP_INTERVAL_TEXT N_( "Interval between appearance and disappearance" )
74 #define MIN_START_STOP_INTERVAL_LONGTEXT N_( \
75 "Minimum time (in milliseconds) that subtitle should stay after newer subtitle has appeared " \
76 "(earlier subtitle delay will be shortened to avoid the overlap)." )
78 static const int pi_mode_values
[] = { 0, 1, 2 };
79 static const char * const ppsz_mode_descriptions
[] = { N_( "Absolute delay" ), N_( "Relative to source delay" ), N_(
80 "Relative to source content" ) };
84 #define CFG_PREFIX "subsdelay-"
86 #define CFG_MODE CFG_PREFIX "mode"
87 #define CFG_FACTOR CFG_PREFIX "factor"
88 #define CFG_OVERLAP CFG_PREFIX "overlap"
90 #define CFG_MIN_ALPHA CFG_PREFIX "min-alpha"
91 #define CFG_MIN_STOPS_INTERVAL CFG_PREFIX "min-stops"
92 #define CFG_MIN_STOP_START_INTERVAL CFG_PREFIX "min-stop-start"
93 #define CFG_MIN_START_STOP_INTERVAL CFG_PREFIX "min-start-stop"
96 /* max subtitles handled on the heap */
97 #define SUBSDELAY_MAX_ENTRIES 16
99 #define SUBSDELAY_MODE_ABSOLUTE 0
100 #define SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY 1
101 #define SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT 2
104 /*****************************************************************************
105 * subsdelay_heap_entry_t: Heap entry
106 *****************************************************************************/
108 typedef struct subsdelay_heap_entry_t subsdelay_heap_entry_t
;
110 struct subsdelay_heap_entry_t
112 subpicture_t
*p_subpic
; /* local subtitle */
114 subpicture_t
*p_source
; /* subtitle source */
116 filter_t
*p_filter
; /* assigned subsdelay filter */
118 subsdelay_heap_entry_t
*p_next
; /* next entry */
120 bool b_update_stop
; /* new stop value should be calculated */
122 bool b_update_ephemer
; /* actual stop value is unknown */
124 bool b_update_position
; /* subtitle position should be updated */
126 bool b_check_empty
; /* subtitle content should be checked */
128 vlc_tick_t i_new_stop
; /* new stop value */
130 /* last region data*/
136 int i_last_region_align
;
138 bool b_last_region_saved
;
141 /*****************************************************************************
142 * subsdelay_heap_t: Heap
143 *****************************************************************************/
147 vlc_mutex_t lock
; /* heap global lock */
149 subsdelay_heap_entry_t
*p_list
[SUBSDELAY_MAX_ENTRIES
]; /* subtitles entries array */
151 subsdelay_heap_entry_t
*p_head
; /* subtitles entries linked list */
153 int i_count
; /* subtitles count */
159 /*****************************************************************************
160 * filter_sys_t: Subsdelay filter descriptor
161 *****************************************************************************/
165 int i_mode
; /* delay calculation mode */
167 float f_factor
; /* calculation factor */
169 int i_overlap
; /* max overlap */
171 int i_min_alpha
; /* oldest subtitle alpha value */
173 vlc_tick_t i_min_stops_interval
;
175 vlc_tick_t i_min_stop_start_interval
;
177 vlc_tick_t i_min_start_stop_interval
;
179 subsdelay_heap_t heap
; /* subpictures list */
183 /*****************************************************************************
185 *****************************************************************************/
187 static int SubsdelayCreate( filter_t
* );
189 static void SubsdelayDestroy( filter_t
* );
191 static subpicture_t
* SubsdelayFilter( filter_t
*p_filter
, subpicture_t
* p_subpic
);
193 static int SubsdelayCallback( vlc_object_t
*p_this
, char const *psz_var
, vlc_value_t oldval
, vlc_value_t newval
,
196 /*****************************************************************************
198 *****************************************************************************/
200 static void SubsdelayEnforceDelayRules( filter_t
*p_filter
);
202 static vlc_tick_t
SubsdelayEstimateDelay( filter_t
*p_filter
, subsdelay_heap_entry_t
*p_entry
);
204 static void SubsdelayRecalculateDelays( filter_t
*p_filter
);
206 static int SubsdelayCalculateAlpha( filter_t
*p_filter
, int i_overlapping
, int i_source_alpha
);
208 static int SubsdelayGetTextRank( char *psz_text
);
210 static bool SubsdelayIsTextEmpty( const text_segment_t
* p_segment
);
212 /*****************************************************************************
213 * Subpicture functions
214 *****************************************************************************/
216 static int SubpicValidateWrapper( subpicture_t
*p_subpic
, bool has_src_changed
, const video_format_t
*p_fmt_src
,
217 bool has_dst_changed
, const video_format_t
*p_fmt_dst
, vlc_tick_t i_ts
);
219 static void SubpicUpdateWrapper( subpicture_t
*p_subpic
, const video_format_t
*p_fmt_src
,
220 const video_format_t
*p_fmt_dst
, vlc_tick_t i_ts
);
222 static void SubpicDestroyWrapper( subpicture_t
*p_subpic
);
224 static void SubpicLocalUpdate( subpicture_t
* p_subpic
, vlc_tick_t i_ts
);
226 static bool SubpicIsEmpty( subpicture_t
* p_subpic
);
228 static subpicture_t
*SubpicClone( subpicture_t
*p_source
, subpicture_updater_t
*updater
);
230 static void SubpicDestroyClone( subpicture_t
*p_subpic
);
232 /*****************************************************************************
234 *****************************************************************************/
236 static void SubsdelayHeapInit( subsdelay_heap_t
*p_heap
);
238 static void SubsdelayHeapDestroy( subsdelay_heap_t
*p_heap
);
240 static subsdelay_heap_entry_t
*SubsdelayHeapPush( subsdelay_heap_t
*p_heap
, subpicture_t
*p_subpic
, filter_t
*p_filter
);
242 static void SubsdelayHeapRemove( subsdelay_heap_t
*p_heap
, subsdelay_heap_entry_t
*p_entry
);
244 static void SubsdelayRebuildList( subsdelay_heap_t
*p_heap
);
246 static void SubsdelayHeapLock( subsdelay_heap_t
*p_heap
);
248 static void SubsdelayHeapUnlock( subsdelay_heap_t
*p_heap
);
250 static subsdelay_heap_entry_t
* SubsdelayEntryCreate( subpicture_t
*p_subpic
, filter_t
*p_filter
);
252 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t
*p_entry
);
254 /* heap / entries special functionality */
256 static int SubsdelayHeapCountOverlap( subsdelay_heap_t
*p_heap
, subsdelay_heap_entry_t
*p_entry
, vlc_tick_t i_date
);
258 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t
*p_entry
);
260 /*****************************************************************************
262 *****************************************************************************/
265 set_shortname( N_("Subsdelay") )
266 set_description( N_("Subtitle delay") )
267 set_help( SUBSDELAY_HELP
)
268 set_callback_sub_filter( SubsdelayCreate
)
269 set_category( CAT_VIDEO
)
270 set_subcategory( SUBCAT_VIDEO_SUBPIC
)
272 add_integer( CFG_MODE
, 1, MODE_TEXT
, MODE_LONGTEXT
, false )
273 change_integer_list( pi_mode_values
, ppsz_mode_descriptions
)
275 add_float_with_range( CFG_FACTOR
, 2, 0, 20, FACTOR_TEXT
, FACTOR_LONGTEXT
, false )
277 add_integer_with_range( CFG_OVERLAP
, 3, 1, 4, OVERLAP_TEXT
, OVERLAP_LONGTEXT
, false )
279 add_integer_with_range( CFG_MIN_ALPHA
, 70, 0, 255, MIN_ALPHA_TEXT
, MIN_ALPHA_LONGTEXT
, false )
281 set_section( N_("Overlap fix"), NULL
)
283 add_integer( CFG_MIN_STOPS_INTERVAL
, 1000, MIN_STOPS_INTERVAL_TEXT
, MIN_STOPS_INTERVAL_LONGTEXT
, false )
285 add_integer( CFG_MIN_START_STOP_INTERVAL
, 1000, MIN_START_STOP_INTERVAL_TEXT
,
286 MIN_START_STOP_INTERVAL_LONGTEXT
, false )
288 add_integer( CFG_MIN_STOP_START_INTERVAL
, 1000, MIN_STOP_START_INTERVAL_TEXT
,
289 MIN_STOP_START_INTERVAL_LONGTEXT
, false )
293 static const char * const ppsz_filter_options
[] = { "mode", "factor", "overlap", NULL
};
295 static const struct vlc_filter_operations filter_ops
= {
296 .filter_sub
= SubsdelayFilter
, .close
= SubsdelayDestroy
,
299 /*****************************************************************************
300 * SubsdelayCreate: Create subsdelay filter
301 *****************************************************************************/
302 static int SubsdelayCreate( filter_t
*p_filter
)
306 /* allocate structure */
307 p_sys
= (filter_sys_t
*) malloc( sizeof(filter_sys_t
) );
314 /* init parameters */
316 p_sys
->i_mode
= var_CreateGetIntegerCommand( p_filter
, CFG_MODE
);
317 var_AddCallback( p_filter
, CFG_MODE
, SubsdelayCallback
, p_sys
);
319 p_sys
->f_factor
= var_CreateGetFloatCommand( p_filter
, CFG_FACTOR
);
320 var_AddCallback( p_filter
, CFG_FACTOR
, SubsdelayCallback
, p_sys
);
322 p_sys
->i_overlap
= var_CreateGetIntegerCommand( p_filter
, CFG_OVERLAP
);
323 var_AddCallback( p_filter
, CFG_OVERLAP
, SubsdelayCallback
, p_sys
);
325 p_sys
->i_min_alpha
= var_CreateGetIntegerCommand( p_filter
, CFG_MIN_ALPHA
);
326 var_AddCallback( p_filter
, CFG_MIN_ALPHA
, SubsdelayCallback
, p_sys
);
328 p_sys
->i_min_stops_interval
329 = VLC_TICK_FROM_MS( var_CreateGetIntegerCommand( p_filter
, CFG_MIN_STOPS_INTERVAL
) );
330 var_AddCallback( p_filter
, CFG_MIN_STOPS_INTERVAL
, SubsdelayCallback
, p_sys
);
332 p_sys
->i_min_stop_start_interval
333 = VLC_TICK_FROM_MS( var_CreateGetIntegerCommand( p_filter
, CFG_MIN_STOP_START_INTERVAL
) );
334 var_AddCallback( p_filter
, CFG_MIN_STOP_START_INTERVAL
, SubsdelayCallback
, p_sys
);
336 p_sys
->i_min_start_stop_interval
337 = VLC_TICK_FROM_MS( var_CreateGetIntegerCommand( p_filter
, CFG_MIN_START_STOP_INTERVAL
) );
338 var_AddCallback( p_filter
, CFG_MIN_START_STOP_INTERVAL
, SubsdelayCallback
, p_sys
);
340 p_filter
->p_sys
= p_sys
;
341 p_filter
->ops
= &filter_ops
;
343 config_ChainParse( p_filter
, CFG_PREFIX
, ppsz_filter_options
, p_filter
->p_cfg
);
345 SubsdelayHeapInit( &p_sys
->heap
);
350 /*****************************************************************************
351 * SubsdelayDestroy: Destroy subsdelay filter
352 *****************************************************************************/
353 static void SubsdelayDestroy( filter_t
*p_filter
)
355 filter_sys_t
*p_sys
= p_filter
->p_sys
;
357 SubsdelayHeapDestroy( &p_sys
->heap
);
359 /* destroy parameters */
361 var_DelCallback( p_filter
, CFG_MODE
, SubsdelayCallback
, p_sys
);
362 var_Destroy( p_filter
, CFG_MODE
);
364 var_DelCallback( p_filter
, CFG_FACTOR
, SubsdelayCallback
, p_sys
);
365 var_Destroy( p_filter
, CFG_FACTOR
);
367 var_DelCallback( p_filter
, CFG_OVERLAP
, SubsdelayCallback
, p_sys
);
368 var_Destroy( p_filter
, CFG_OVERLAP
);
370 var_DelCallback( p_filter
, CFG_MIN_ALPHA
, SubsdelayCallback
, p_sys
);
371 var_Destroy( p_filter
, CFG_MIN_ALPHA
);
373 var_DelCallback( p_filter
, CFG_MIN_STOPS_INTERVAL
, SubsdelayCallback
, p_sys
);
374 var_Destroy( p_filter
, CFG_MIN_STOPS_INTERVAL
);
376 var_DelCallback( p_filter
, CFG_MIN_STOP_START_INTERVAL
, SubsdelayCallback
, p_sys
);
377 var_Destroy( p_filter
, CFG_MIN_STOP_START_INTERVAL
);
379 var_DelCallback( p_filter
, CFG_MIN_START_STOP_INTERVAL
, SubsdelayCallback
, p_sys
);
380 var_Destroy( p_filter
, CFG_MIN_START_STOP_INTERVAL
);
385 /*****************************************************************************
386 * SubsdelayFilter: Filter new subpicture
387 *****************************************************************************/
388 static subpicture_t
* SubsdelayFilter( filter_t
*p_filter
, subpicture_t
* p_subpic
)
390 subsdelay_heap_t
*p_heap
;
391 subsdelay_heap_entry_t
*p_entry
;
393 if( !p_subpic
->b_subtitle
)
398 if( SubpicIsEmpty( p_subpic
) )
400 /* empty subtitles usually helps terminate ephemer subtitles, but this filter calculates the stop value anyway,
401 so this subtitle can be dropped */
403 subpicture_Delete( p_subpic
);
408 filter_sys_t
*p_sys
= p_filter
->p_sys
;
409 p_heap
= &p_sys
->heap
;
411 /* add subpicture to the heap */
413 SubsdelayHeapLock( p_heap
);
415 p_entry
= SubsdelayHeapPush( p_heap
, p_subpic
, p_filter
);
418 SubsdelayHeapUnlock( p_heap
);
420 msg_Err(p_filter
, "Can't add subpicture to the heap");
425 p_subpic
= p_entry
->p_subpic
; /* get the local subpic */
427 if( p_subpic
->b_ephemer
)
429 /* set a relativly long delay in hope that the next subtitle
430 will arrive in this time and the real delay could be determined */
432 p_subpic
->i_stop
= p_subpic
->i_start
+ VLC_TICK_FROM_SEC(20); /* start + 20 sec */
433 p_subpic
->b_ephemer
= false;
437 SubsdelayEnforceDelayRules( p_filter
);
439 SubsdelayHeapUnlock( p_heap
);
444 /*****************************************************************************
445 * SubsdelayCallback: Subsdelay parameters callback
446 *****************************************************************************/
447 static int SubsdelayCallback( vlc_object_t
*p_this
, char const *psz_var
, vlc_value_t oldval
, vlc_value_t newval
,
450 filter_sys_t
*p_sys
= (filter_sys_t
*) p_data
;
452 VLC_UNUSED( oldval
);
454 SubsdelayHeapLock( &p_sys
->heap
);
456 if( !strcmp( psz_var
, CFG_MODE
) )
458 p_sys
->i_mode
= newval
.i_int
;
460 else if( !strcmp( psz_var
, CFG_FACTOR
) )
462 p_sys
->f_factor
= newval
.f_float
;
464 else if( !strcmp( psz_var
, CFG_OVERLAP
) )
466 p_sys
->i_overlap
= newval
.i_int
;
468 else if( !strcmp( psz_var
, CFG_MIN_ALPHA
) )
470 p_sys
->i_min_alpha
= newval
.i_int
;
472 else if( !strcmp( psz_var
, CFG_MIN_STOPS_INTERVAL
) )
474 p_sys
->i_min_stops_interval
= VLC_TICK_FROM_MS( newval
.i_int
);
476 else if( !strcmp( psz_var
, CFG_MIN_STOP_START_INTERVAL
) )
478 p_sys
->i_min_stop_start_interval
= VLC_TICK_FROM_MS( newval
.i_int
);
480 else if( !strcmp( psz_var
, CFG_MIN_START_STOP_INTERVAL
) )
482 p_sys
->i_min_start_stop_interval
= VLC_TICK_FROM_MS( newval
.i_int
);
486 vlc_assert_unreachable();
489 SubsdelayRecalculateDelays( (filter_t
*) p_this
);
491 SubsdelayHeapUnlock( &p_sys
->heap
);
495 /*****************************************************************************
496 * SubsdelayHeapInit: Initialize heap
497 *****************************************************************************/
498 static void SubsdelayHeapInit( subsdelay_heap_t
*p_heap
)
501 p_heap
->p_head
= NULL
;
503 vlc_mutex_init( &p_heap
->lock
);
506 /*****************************************************************************
507 * SubsdelayHeapDestroy: Destroy the heap and remove its entries
508 *****************************************************************************/
509 static void SubsdelayHeapDestroy( subsdelay_heap_t
*p_heap
)
511 SubsdelayHeapLock( p_heap
);
513 for( subsdelay_heap_entry_t
*p_entry
= p_heap
->p_head
;
514 p_entry
!= NULL
; p_entry
= p_entry
->p_next
)
516 p_entry
->p_subpic
->i_stop
= p_entry
->p_source
->i_stop
;
518 p_entry
->p_filter
= NULL
;
521 SubsdelayHeapUnlock( p_heap
);
524 /*****************************************************************************
525 * SubsdelayHeapPush: Add new subpicture to the heap
526 *****************************************************************************/
527 static subsdelay_heap_entry_t
*SubsdelayHeapPush( subsdelay_heap_t
*p_heap
, subpicture_t
*p_subpic
, filter_t
*p_filter
)
529 subsdelay_heap_entry_t
*p_last
, *p_new_entry
;
531 if( p_heap
->i_count
>= SUBSDELAY_MAX_ENTRIES
)
533 return NULL
; /* the heap is full */
536 p_new_entry
= SubsdelayEntryCreate( p_subpic
, p_filter
);
546 for( subsdelay_heap_entry_t
*p_entry
= p_heap
->p_head
; p_entry
!= NULL
;
547 p_entry
= p_entry
->p_next
)
549 if( p_entry
->p_source
->i_start
> p_subpic
->i_start
)
551 /* the new entry should be inserted before p_entry */
560 p_new_entry
->p_next
= p_last
->p_next
;
561 p_last
->p_next
= p_new_entry
;
564 if( p_last
->b_update_ephemer
)
566 /* the correct stop value can be determined */
568 p_last
->p_source
->i_stop
= p_new_entry
->p_source
->i_start
;
569 p_last
->b_update_ephemer
= false;
574 p_new_entry
->p_next
= p_heap
->p_head
;
575 p_heap
->p_head
= p_new_entry
;
581 SubsdelayRebuildList( p_heap
);
586 /*****************************************************************************
587 * SubsdelayHeapRemove: Remove entry
588 *****************************************************************************/
589 static void SubsdelayHeapRemove( subsdelay_heap_t
*p_heap
, subsdelay_heap_entry_t
*p_entry
)
591 subsdelay_heap_entry_t
*p_prev
;
595 for( subsdelay_heap_entry_t
*p_curr
= p_heap
->p_head
; p_curr
!= NULL
;
596 p_curr
= p_curr
->p_next
)
598 if( p_curr
== p_entry
)
608 p_prev
->p_next
= p_entry
->p_next
;
612 p_heap
->p_head
= p_entry
->p_next
;
615 p_entry
->p_filter
= NULL
;
617 SubsdelayRebuildList( p_heap
);
621 static void SubsdelayRebuildList( subsdelay_heap_t
*p_heap
)
626 for( subsdelay_heap_entry_t
*p_curr
= p_heap
->p_head
; p_curr
!= NULL
;
627 p_curr
= p_curr
->p_next
)
629 p_heap
->p_list
[i_index
] = p_curr
;
633 p_heap
->i_count
= i_index
;
636 /*****************************************************************************
637 * SubsdelayHeapLock: Lock the heap
638 *****************************************************************************/
639 static void SubsdelayHeapLock( subsdelay_heap_t
*p_heap
)
641 vlc_mutex_lock( &p_heap
->lock
);
644 /*****************************************************************************
645 * SubsdelayHeapUnlock: Unlock the heap
646 *****************************************************************************/
647 static void SubsdelayHeapUnlock( subsdelay_heap_t
*p_heap
)
649 vlc_mutex_unlock( &p_heap
->lock
);
653 /*****************************************************************************
654 * SubsdelayHeapCreateEntry: Create new entry
655 *****************************************************************************/
656 static subsdelay_heap_entry_t
* SubsdelayEntryCreate( subpicture_t
*p_source
, filter_t
*p_filter
)
658 subsdelay_heap_entry_t
*p_entry
;
660 subpicture_t
*p_new_subpic
;
662 subpicture_updater_t updater
;
664 /* allocate structure */
666 p_entry
= (subsdelay_heap_entry_t
*) malloc( sizeof( subsdelay_heap_entry_t
) );
673 /* initialize local updater */
675 updater
.p_sys
= p_entry
;
676 updater
.pf_validate
= SubpicValidateWrapper
;
677 updater
.pf_update
= SubpicUpdateWrapper
;
678 updater
.pf_destroy
= SubpicDestroyWrapper
;
680 /* create new subpic */
682 p_new_subpic
= SubpicClone( p_source
, &updater
);
690 /* initialize entry */
692 p_entry
->p_subpic
= p_new_subpic
;
693 p_entry
->p_source
= p_source
;
694 p_entry
->p_filter
= p_filter
;
695 p_entry
->p_next
= NULL
;
696 p_entry
->b_update_stop
= true;
697 p_entry
->b_update_ephemer
= p_source
->b_ephemer
;
698 p_entry
->b_update_position
= true;
699 p_entry
->b_check_empty
= true;
700 p_entry
->i_new_stop
= p_source
->i_stop
;
701 p_entry
->b_last_region_saved
= false;
702 p_entry
->i_last_region_x
= 0;
703 p_entry
->i_last_region_y
= 0;
704 p_entry
->i_last_region_align
= 0;
709 /*****************************************************************************
710 * SubsdelayEntryDestroy: Destroy entry
711 *****************************************************************************/
712 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t
*p_entry
)
714 SubpicDestroyClone( p_entry
->p_source
);
718 /*****************************************************************************
719 * SubsdelayHeapCountOverlap: Count overlapping subtitles at a given time
720 *****************************************************************************/
721 static int SubsdelayHeapCountOverlap( subsdelay_heap_t
*p_heap
, subsdelay_heap_entry_t
*p_entry
, vlc_tick_t i_date
)
725 VLC_UNUSED( p_heap
);
729 for( subsdelay_heap_entry_t
*p_curr
= p_entry
->p_next
; p_curr
!= NULL
;
730 p_curr
= p_curr
->p_next
)
732 if( p_curr
->p_source
->i_start
> i_date
)
737 if( !p_curr
->b_check_empty
) /* subtitle was checked, and it's not empty */
746 /*****************************************************************************
747 * SubsdelayEntryNewStopValueUpdated: Update source stop value after the new
748 * stop value was changed
749 *****************************************************************************/
750 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t
*p_entry
)
752 if( !p_entry
->b_update_stop
)
754 p_entry
->p_subpic
->i_stop
= p_entry
->i_new_stop
- VLC_TICK_FROM_MS(100); /* 0.1 sec less */
758 /*****************************************************************************
759 * SubsdelayEnforceDelayRules: Update subtitles delay after adding new
760 * subtitle or changing subtitle stop value
761 *****************************************************************************/
762 static void SubsdelayEnforceDelayRules( filter_t
*p_filter
)
764 subsdelay_heap_entry_t
** p_list
;
765 int i_count
, i_overlap
;
767 vlc_tick_t i_min_stops_interval
;
768 vlc_tick_t i_min_stop_start_interval
;
769 vlc_tick_t i_min_start_stop_interval
;
771 filter_sys_t
*p_sys
= p_filter
->p_sys
;
773 p_list
= p_sys
->heap
.p_list
;
774 i_count
= p_sys
->heap
.i_count
;
776 i_overlap
= p_sys
->i_overlap
;
777 i_min_stops_interval
= p_sys
->i_min_stops_interval
;
778 i_min_stop_start_interval
= p_sys
->i_min_stop_start_interval
;
779 i_min_start_stop_interval
= p_sys
->i_min_start_stop_interval
;
781 /* step 1 - enforce min stops interval rule (extend delays) */
784 [subtitle 1 ..............]
785 [subtitle 2 ..............]
786 |<-MinStopsInterval->|
788 * and extend newer subtitle:
789 [subtitle 1 ..............]
790 [subtitle 2 ............................]
791 |<-MinStopsInterval->|
794 for( int i
= 0; i
< i_count
- 1; i
++ )
796 p_list
[i
+ 1]->i_new_stop
= __MAX( p_list
[i
+ 1]->i_new_stop
,
797 p_list
[i
]->i_new_stop
+ i_min_stops_interval
);
800 /* step 2 - enforce min stop start interval rule (extend delays) */
803 [subtitle 1 .........]
805 |<-MinStopStartInterval->|
808 [subtitle 1 ..................]
810 |<-MinStopStartInterval->|
813 for( int i
= 0; i
< i_count
; i
++ )
815 for( int j
= i
+ 1; j
< __MIN( i_count
, i
+ 1 + i_overlap
); j
++ )
817 i_offset
= p_list
[j
]->p_source
->i_start
- p_list
[i
]->i_new_stop
;
824 if( i_offset
< i_min_stop_start_interval
)
826 p_list
[i
]->i_new_stop
= p_list
[j
]->p_source
->i_start
;
833 /* step 3 - enforce min start stop interval rule (shorten delays) */
836 [subtitle 1 ............]
837 [subtitle 2 ....................]
838 |<-MinStartStopInterval->|
840 * and remove the overlapping part:
842 [subtitle 2 ....................]
843 |<-MinStartStopInterval->|
847 for( int i
= 0; i
< i_count
; i
++ )
849 for( int j
= i
+ 1; j
< __MIN( i_count
, i
+ 1 + i_overlap
); j
++ )
851 i_offset
= p_list
[i
]->i_new_stop
- p_list
[j
]->p_source
->i_start
;
858 if( i_offset
< i_min_start_stop_interval
)
860 p_list
[i
]->i_new_stop
= p_list
[j
]->p_source
->i_start
;
866 /* step 4 - enforce max overlapping rule (shorten delays)*/
868 /* look for: (overlap = 2)
869 [subtitle 1 ..............]
870 [subtitle 2 ..............]
871 [subtitle 3 ..............]
874 * and cut older subtitle:
876 [subtitle 2 ..............]
877 [subtitle 3 ..............]
880 for( int i
= 0; i
< i_count
- i_overlap
; i
++ )
882 p_list
[i
]->i_new_stop
= __MIN(p_list
[i
]->i_new_stop
, p_list
[i
+ i_overlap
]->p_source
->i_start
);
885 /* finally - update all */
887 for( int i
= 0; i
< i_count
; i
++ )
889 SubsdelayEntryNewStopValueUpdated( p_list
[i
] );
893 /*****************************************************************************
894 * SubsdelayRecalculateDelays: Recalculate subtitles delay after changing
895 * one of the filter's parameters
896 *****************************************************************************/
897 static void SubsdelayRecalculateDelays( filter_t
*p_filter
)
899 filter_sys_t
*p_sys
= p_filter
->p_sys
;
900 for( subsdelay_heap_entry_t
*p_curr
= p_sys
->heap
.p_head
;
901 p_curr
!= NULL
; p_curr
= p_curr
->p_next
)
903 if( !p_curr
->b_update_ephemer
)
905 p_curr
->i_new_stop
= p_curr
->p_source
->i_start
+ SubsdelayEstimateDelay( p_filter
, p_curr
);
906 p_curr
->b_update_stop
= false;
910 SubsdelayEnforceDelayRules( p_filter
);
913 /*****************************************************************************
914 * SubpicValidateWrapper: Subpicture validate callback wrapper
915 *****************************************************************************/
916 static int SubpicValidateWrapper( subpicture_t
*p_subpic
, bool has_src_changed
, const video_format_t
*p_fmt_src
,
917 bool has_dst_changed
, const video_format_t
*p_fmt_dst
, vlc_tick_t i_ts
)
919 subsdelay_heap_entry_t
*p_entry
;
921 int i_result
= VLC_SUCCESS
;
923 p_entry
= p_subpic
->updater
.p_sys
;
929 /* call source validate */
930 if( p_entry
->p_source
->updater
.pf_validate
)
932 i_new_ts
= p_entry
->p_source
->i_start
+
933 ( (double)( p_entry
->p_source
->i_stop
- p_entry
->p_source
->i_start
) * ( i_ts
- p_entry
->p_source
->i_start
) ) /
934 ( p_entry
->i_new_stop
- p_entry
->p_source
->i_start
);
936 i_result
= p_entry
->p_source
->updater
.pf_validate( p_entry
->p_source
, has_src_changed
, p_fmt_src
,
937 has_dst_changed
, p_fmt_dst
, i_new_ts
);
941 p_entry
->b_last_region_saved
= false;
943 if( p_subpic
->p_region
)
946 p_entry
->i_last_region_x
= p_subpic
->p_region
->i_x
;
947 p_entry
->i_last_region_y
= p_subpic
->p_region
->i_y
;
948 p_entry
->i_last_region_align
= p_subpic
->p_region
->i_align
;
950 p_entry
->b_last_region_saved
= true;
955 /* subpic update isn't necessary, so local update should be called here */
956 SubpicLocalUpdate( p_subpic
, i_ts
);
962 /*****************************************************************************
963 * SubpicUpdateWrapper: Subpicture update callback wrapper
964 *****************************************************************************/
965 static void SubpicUpdateWrapper( subpicture_t
*p_subpic
, const video_format_t
*p_fmt_src
,
966 const video_format_t
*p_fmt_dst
, vlc_tick_t i_ts
)
968 subsdelay_heap_entry_t
*p_entry
;
971 p_entry
= p_subpic
->updater
.p_sys
;
977 /* call source update */
978 if( p_entry
->p_source
->updater
.pf_update
)
980 i_new_ts
= p_entry
->p_source
->i_start
+
981 ( (double)( p_entry
->p_source
->i_stop
- p_entry
->p_source
->i_start
) * ( i_ts
- p_entry
->p_source
->i_start
) ) /
982 ( p_entry
->i_new_stop
- p_entry
->p_source
->i_start
);
984 p_entry
->p_source
->p_region
= p_entry
->p_subpic
->p_region
;
986 p_entry
->p_source
->updater
.pf_update( p_entry
->p_source
, p_fmt_src
, p_fmt_dst
, i_new_ts
);
988 p_entry
->p_subpic
->p_region
= p_entry
->p_source
->p_region
;
991 SubpicLocalUpdate( p_subpic
, i_ts
);
994 /*****************************************************************************
995 * SubpicDestroyCallback: Subpicture destroy callback
996 *****************************************************************************/
997 static void SubpicDestroyWrapper( subpicture_t
*p_subpic
)
999 subsdelay_heap_entry_t
*p_entry
;
1000 subsdelay_heap_t
*p_heap
;
1002 p_entry
= p_subpic
->updater
.p_sys
;
1009 if( p_entry
->p_filter
)
1011 filter_sys_t
*p_sys
= p_entry
->p_filter
->p_sys
;
1012 p_heap
= &p_sys
->heap
;
1014 SubsdelayHeapLock( p_heap
);
1015 SubsdelayHeapRemove( p_heap
, p_entry
);
1016 SubsdelayHeapUnlock( p_heap
);
1019 SubsdelayEntryDestroy( p_entry
);
1022 /*****************************************************************************
1023 * SubpicLocalUpdate: rewrite some of the subpicture parameters
1024 *****************************************************************************/
1025 static void SubpicLocalUpdate( subpicture_t
* p_subpic
, vlc_tick_t i_ts
)
1027 subsdelay_heap_entry_t
*p_entry
;
1028 subsdelay_heap_t
*p_heap
;
1033 p_entry
= p_subpic
->updater
.p_sys
;
1034 if( !p_entry
|| !p_entry
->p_filter
)
1039 p_filter
= p_entry
->p_filter
;
1040 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1041 p_heap
= &p_sys
->heap
;
1043 SubsdelayHeapLock( p_heap
);
1045 if( p_entry
->b_check_empty
&& p_subpic
->p_region
)
1047 //FIXME: What if there is more than one segment?
1048 if( SubsdelayIsTextEmpty( p_subpic
->p_region
->p_text
) )
1050 /* remove empty subtitle */
1052 p_subpic
->b_ephemer
= false;
1053 p_subpic
->i_stop
= p_subpic
->i_start
;
1055 SubsdelayHeapRemove( p_heap
, p_entry
);
1057 SubsdelayHeapUnlock( p_heap
);
1062 p_entry
->b_check_empty
= false;
1065 if( p_entry
->b_update_stop
&& !p_entry
->b_update_ephemer
)
1067 p_entry
->i_new_stop
= p_entry
->p_source
->i_start
+ SubsdelayEstimateDelay( p_filter
, p_entry
);
1068 p_entry
->b_update_stop
= false;
1070 SubsdelayEnforceDelayRules( p_filter
);
1073 i_overlapping
= SubsdelayHeapCountOverlap( p_heap
, p_entry
, i_ts
);
1075 p_subpic
->i_alpha
= SubsdelayCalculateAlpha( p_filter
, i_overlapping
, p_entry
->p_source
->i_alpha
);
1077 if( p_entry
->b_update_position
)
1079 p_subpic
->b_absolute
= false;
1081 if( p_subpic
->p_region
)
1083 p_subpic
->p_region
->i_x
= 0;
1084 p_subpic
->p_region
->i_y
= 10;
1085 p_subpic
->p_region
->i_align
= ( p_subpic
->p_region
->i_align
& ( ~SUBPICTURE_ALIGN_MASK
) )
1086 | SUBPICTURE_ALIGN_BOTTOM
;
1089 p_entry
->b_update_position
= false;
1091 else if( p_entry
->b_last_region_saved
)
1093 p_subpic
->b_absolute
= true;
1095 if( p_subpic
->p_region
)
1097 p_subpic
->p_region
->i_x
= p_entry
->i_last_region_x
;
1098 p_subpic
->p_region
->i_y
= p_entry
->i_last_region_y
;
1099 p_subpic
->p_region
->i_align
= p_entry
->i_last_region_align
;
1103 SubsdelayHeapUnlock( p_heap
);
1106 /*****************************************************************************
1107 * SubpicIsEmpty: subpic region contains empty string
1108 *****************************************************************************/
1109 static bool SubpicIsEmpty( subpicture_t
* p_subpic
)
1111 return ( p_subpic
->p_region
&& ( SubsdelayIsTextEmpty( p_subpic
->p_region
->p_text
) ) );
1114 /*****************************************************************************
1115 * SubpicClone: Clone subpicture (shallow copy)
1116 *****************************************************************************/
1117 static subpicture_t
*SubpicClone( subpicture_t
*p_source
, subpicture_updater_t
*updater
)
1119 subpicture_t
*p_subpic
;
1120 subpicture_updater_t subpic_updater
;
1121 subpicture_private_t
*p_subpic_private
;
1123 p_subpic
= subpicture_New( updater
);
1130 /* save private members */
1131 subpic_updater
= p_subpic
->updater
;
1132 p_subpic_private
= p_subpic
->p_private
;
1134 /* copy the entire struct */
1135 memcpy( p_subpic
, p_source
, sizeof( subpicture_t
) );
1137 /* restore private members */
1138 p_subpic
->updater
= subpic_updater
;
1139 p_subpic
->p_private
= p_subpic_private
;
1144 /*****************************************************************************
1145 * SubpicDestroyClone: destroy cloned subpicture (shared references will not
1147 *****************************************************************************/
1148 static void SubpicDestroyClone( subpicture_t
*p_subpic
)
1150 p_subpic
->p_region
= NULL
; /* don't destroy region */
1151 subpicture_Delete( p_subpic
);
1154 /*****************************************************************************
1155 * SubsdelayEstimateDelay: Calculate new subtitle delay according to its
1156 * content and the calculation mode
1157 *****************************************************************************/
1158 static vlc_tick_t
SubsdelayEstimateDelay( filter_t
*p_filter
, subsdelay_heap_entry_t
*p_entry
)
1163 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1165 i_mode
= p_sys
->i_mode
;
1167 if( i_mode
== SUBSDELAY_MODE_ABSOLUTE
)
1169 return ( p_entry
->p_source
->i_stop
- p_entry
->p_source
->i_start
+ vlc_tick_from_sec( p_sys
->f_factor
) );
1172 if( i_mode
== SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT
)
1174 if( p_entry
->p_subpic
&& p_entry
->p_subpic
->p_region
&& ( p_entry
->p_subpic
->p_region
->p_text
) )
1176 //FIXME: We only use a single segment here
1177 i_rank
= SubsdelayGetTextRank( p_entry
->p_subpic
->p_region
->p_text
->psz_text
);
1179 return vlc_tick_from_sec( p_sys
->f_factor
* i_rank
);
1182 /* content is unavailable, calculation mode should be based on source delay */
1183 i_mode
= SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY
;
1186 if( likely(i_mode
== SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY
) )
1188 return (vlc_tick_t
)( p_sys
->f_factor
* ( p_entry
->p_source
->i_stop
- p_entry
->p_source
->i_start
) );
1191 return VLC_TICK_FROM_SEC(10); /* 10 sec */
1194 /*****************************************************************************
1195 * SubsdelayCalculateAlpha: Calculate subtitle alpha according to source alpha
1196 * value and number of overlapping subtitles
1197 *****************************************************************************/
1198 static int SubsdelayCalculateAlpha( filter_t
*p_filter
, int i_overlapping
, int i_source_alpha
)
1203 filter_sys_t
*p_sys
= p_filter
->p_sys
;
1205 i_min_alpha
= p_sys
->i_min_alpha
;
1207 if( i_overlapping
> p_sys
->i_overlap
- 1)
1209 i_overlapping
= p_sys
->i_overlap
- 1;
1212 switch ( p_sys
->i_overlap
)
1219 i_new_alpha
= 255 - i_overlapping
* ( 255 - i_min_alpha
);
1223 i_new_alpha
= 255 - i_overlapping
* ( 255 - i_min_alpha
) / 2;
1227 i_new_alpha
= 255 - i_overlapping
* ( 255 - i_min_alpha
) / 3;
1231 return ( i_source_alpha
* i_new_alpha
) / 255;
1234 /*****************************************************************************
1235 * SubsdelayGetWordRank: Calculate single word rank according to its length
1236 *****************************************************************************/
1237 static int SubsdelayGetWordRank( int i_length
)
1239 /* p_rank[0] = p_rank[1] = p_rank[2] = 300;
1240 for( i = 3; i < 20; i++ ) p_rank[i] = (int) ( 1.1 * p_rank[i - 1] ); */
1242 static const int p_rank
[20] = { 300, 300, 300, 330, 363, 399, 438, 481, 529, 581,
1243 639, 702, 772, 849, 933, 1026, 1128, 1240, 1364, 1500 };
1255 return p_rank
[i_length
- 1];
1258 /*****************************************************************************
1259 * SubsdelayGetTextRank: Calculate text rank
1260 *****************************************************************************/
1261 static int SubsdelayGetTextRank( char *psz_text
)
1267 int i
, i_word_length
, i_rank
;
1276 while ( psz_text
[i
] != '\0' )
1281 if( c
== '\\' && !b_skip_esc
)
1287 if( psz_text
[i
] == '<' )
1293 if( !b_skip_esc
&& !b_skip_tag
)
1295 if( c
== ' ' || c
== ',' || c
== '.' || c
== '-' || c
== '?' || c
== '!' ) /* common delimiters */
1297 if( i_word_length
> 0 )
1299 i_rank
+= SubsdelayGetWordRank( i_word_length
);
1319 if( i_word_length
> 0 )
1321 i_rank
+= SubsdelayGetWordRank( i_word_length
);
1327 /*****************************************************************************
1328 * SubsdelayIsTextEmpty: Check if the text contains spaces only
1329 *****************************************************************************/
1330 static bool SubsdelayIsTextEmpty( const text_segment_t
*p_segment
)
1334 if ( strlen( p_segment
->psz_text
) > 0 )
1336 size_t offset
= strspn( p_segment
->psz_text
, " " );
1337 if ( p_segment
->psz_text
[offset
] )
1340 p_segment
= p_segment
->p_next
;