]> git.sesse.net Git - vlc/blob - modules/video_filter/subsdelay.c
Use str_format_time() instead of str_format() in outputs
[vlc] / modules / video_filter / subsdelay.c
1 /*****************************************************************************
2  * subsdelay.c : Subsdelay plugin for vlc
3  *****************************************************************************
4  * Copyright © 2011 VideoLAN
5  * $Id$
6  *
7  * Authors: Yuval Tze <yuvaltze@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
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 General Public License for more details.
18  *
19  * You should have received a copy of the GNU 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  *****************************************************************************/
23
24 /*****************************************************************************
25  * Preamble
26  *****************************************************************************/
27
28 #ifdef HAVE_CONFIG_H
29 #include "config.h"
30 #endif
31
32 #include <vlc_common.h>
33 #include <vlc_plugin.h>
34 #include <vlc_filter.h>
35 #include <vlc_subpicture.h>
36 #include <vlc_osd.h>
37 #include <vlc_es.h>
38 #include <stdlib.h>
39
40 /*****************************************************************************
41  * Local constants
42  *****************************************************************************/
43
44 /* descriptions */
45
46 #define SUBSDELAY_HELP N_("Change subtitles delay")
47
48 #define MODE_TEXT N_( "Delay calculation mode" )
49 #define MODE_LONGTEXT N_( \
50     "Absolute delay - add absolute delay to each subtitle. " \
51     "Relative to source delay - multiply subtitle delay. " \
52     "Relative to source content - determine subtitle delay from its content (text)." )
53
54 #define FACTOR_TEXT N_( "Calculation factor" )
55 #define FACTOR_LONGTEXT N_( "Calculation factor. " \
56     "In Absolute delay mode the factor represents seconds.")
57
58 #define OVERLAP_TEXT N_( "Maximum overlapping subtitles" )
59 #define OVERLAP_LONGTEXT N_( "Maximum number of subtitles allowed at the same time." )
60
61 #define MIN_ALPHA_TEXT N_( "Minimum alpha value" )
62 #define MIN_ALPHA_LONGTEXT N_( \
63     "Alpha value of the earliest subtitle, where 0 is fully transparent and 255 is fully opaque." )
64
65 #define MIN_STOPS_INTERVAL_TEXT N_( "Interval between two disappearances" )
66 #define MIN_STOPS_INTERVAL_LONGTEXT N_( \
67     "Minimum time (in milliseconds) that subtitle should stay after its predecessor has disappeared " \
68     "(subtitle delay will be extended to meet this requirement)." )
69
70 #define MIN_STOP_START_INTERVAL_TEXT N_( "Interval between disappearance and appearance" )
71 #define MIN_STOP_START_INTERVAL_LONGTEXT N_( \
72     "Minimum time (in milliseconds) between subtitle disappearance and newer subtitle appearance " \
73     "(earlier subtitle delay will be extended to fill the gap)." )
74
75 #define MIN_START_STOP_INTERVAL_TEXT N_( "Interval between appearance and disappearance" )
76 #define MIN_START_STOP_INTERVAL_LONGTEXT N_( \
77     "Minimum time (in milliseconds) that subtitle should stay after newer subtitle has appeared " \
78     "(earlier subtitle delay will be shortened to avoid the overlap)." )
79
80 static const int pi_mode_values[] = { 0, 1, 2 };
81 static const char * const ppsz_mode_descriptions[] = { N_( "Absolute delay" ), N_( "Relative to source delay" ), N_(
82         "Relative to source content" ) };
83
84 /* parameters */
85
86 #define CFG_PREFIX "subsdelay-"
87
88 #define CFG_MODE                    CFG_PREFIX "mode"
89 #define CFG_FACTOR                  CFG_PREFIX "factor"
90 #define CFG_OVERLAP                 CFG_PREFIX "overlap"
91
92 #define CFG_MIN_ALPHA               CFG_PREFIX "min-alpha"
93 #define CFG_MIN_STOPS_INTERVAL      CFG_PREFIX "min-stops"
94 #define CFG_MIN_STOP_START_INTERVAL CFG_PREFIX "min-stop-start"
95 #define CFG_MIN_START_STOP_INTERVAL CFG_PREFIX "min-start-stop"
96
97
98 /* max subtitles handled on the heap */
99 #define SUBSDELAY_MAX_ENTRIES 16
100
101 /* factor convert macros */
102 #define INT_FACTOR_BASE                  1000
103 #define FLOAT_FACTOR_TO_INT_FACTOR( x )  (int)( ( x ) * INT_FACTOR_BASE )
104 #define INT_FACTOR_TO_MICROSEC( x )      ( ( x ) * ( 1000000 / INT_FACTOR_BASE ) )
105 #define INT_FACTOR_TO_RANK_FACTOR( x )   ( x )
106 #define MILLISEC_TO_MICROSEC( x )        ( ( x ) * 1000 )
107
108
109 #define SUBSDELAY_MODE_ABSOLUTE                0
110 #define SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY   1
111 #define SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT 2
112
113
114 /*****************************************************************************
115  * subsdelay_heap_entry_t: Heap entry
116  *****************************************************************************/
117
118 typedef subpicture_updater_sys_t subsdelay_heap_entry_t;
119
120 struct subpicture_updater_sys_t
121 {
122     subpicture_t *p_subpic; /* local subtitle */
123
124     subpicture_t *p_source; /* subtitle source */
125
126     filter_t *p_filter; /* assigned subsdelay filter */
127
128     subsdelay_heap_entry_t *p_next; /* next entry */
129
130     bool b_update_stop; /* new stop value should be calculated */
131
132     bool b_update_ephemer; /* actual stop value is unknown */
133
134     bool b_update_position; /* subtitle position should be updated */
135
136     bool b_check_empty; /* subtitle content should be checked */
137
138     mtime_t i_new_stop; /* new stop value */
139
140     /* last region data*/
141
142     int i_last_region_x;
143
144     int i_last_region_y;
145
146     int i_last_region_align;
147
148     bool b_last_region_saved;
149 };
150
151 /*****************************************************************************
152  * subsdelay_heap_t: Heap
153  *****************************************************************************/
154
155 typedef struct
156 {
157     vlc_mutex_t lock; /* heap global lock */
158
159     subsdelay_heap_entry_t *p_list[SUBSDELAY_MAX_ENTRIES]; /* subtitles entries array */
160
161     subsdelay_heap_entry_t *p_head; /* subtitles entries linked list */
162
163     int i_count; /* subtitles count */
164
165 } subsdelay_heap_t;
166
167
168
169 /*****************************************************************************
170 * filter_sys_t: Subsdelay filter descriptor
171  *****************************************************************************/
172
173 struct filter_sys_t
174 {
175     int i_mode; /* delay calculation mode */
176
177     int i_factor; /* calculation factor */
178
179     int i_overlap; /* max overlap */
180
181     int i_min_alpha; /* oldest subtitle alpha value */
182
183     int64_t i_min_stops_interval;
184
185     int64_t i_min_stop_start_interval;
186
187     int64_t i_min_start_stop_interval;
188
189     subsdelay_heap_t heap; /* subpictures list */
190 };
191
192
193 /*****************************************************************************
194  * Filter functions
195  *****************************************************************************/
196
197 static int SubsdelayCreate( vlc_object_t * );
198
199 static void SubsdelayDestroy( vlc_object_t * );
200
201 static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic );
202
203 static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
204         void *p_data );
205
206 /*****************************************************************************
207  * Helper functions
208  *****************************************************************************/
209
210 static void SubsdelayEnforceDelayRules( filter_t *p_filter );
211
212 static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry );
213
214 static void SubsdelayRecalculateDelays( filter_t *p_filter );
215
216 static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha );
217
218 static int SubsdelayGetTextRank( char *psz_text );
219
220 static bool SubsdelayIsTextEmpty( char *psz_text );
221
222 /*****************************************************************************
223  * Subpicture functions
224  *****************************************************************************/
225
226 static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
227                                   bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts );
228
229 static void SubpicUpdateWrapper( subpicture_t *p_subpic, const video_format_t *p_fmt_src,
230                                   const video_format_t *p_fmt_dst, mtime_t i_ts );
231
232 static void SubpicDestroyWrapper( subpicture_t *p_subpic );
233
234 static void SubpicLocalUpdate( subpicture_t* p_subpic, mtime_t i_ts );
235
236 static bool SubpicIsEmpty( subpicture_t* p_subpic );
237
238 static subpicture_t *SubpicClone( subpicture_t *p_source, subpicture_updater_t *updater );
239
240 static void SubpicDestroyClone( subpicture_t *p_subpic );
241
242 /*****************************************************************************
243  * Heap functions
244  *****************************************************************************/
245
246 static void SubsdelayHeapInit( subsdelay_heap_t *p_heap );
247
248 static void SubsdelayHeapDestroy( subsdelay_heap_t *p_heap );
249
250 static subsdelay_heap_entry_t *SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter );
251
252 static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry );
253
254 static void SubsdelayRebuildList( subsdelay_heap_t *p_heap );
255
256 static void SubsdelayHeapLock( subsdelay_heap_t *p_heap );
257
258 static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap );
259
260 static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_subpic, filter_t *p_filter );
261
262 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry );
263
264 /* heap / entries special functionality */
265
266 static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date );
267
268 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry );
269
270 /*****************************************************************************
271  * Module descriptor
272  *****************************************************************************/
273
274 vlc_module_begin()
275         set_shortname( _("Subsdelay") )
276         set_description( _("Subtitles delay") )
277         set_help( SUBSDELAY_HELP )
278         set_capability( "sub filter", 0 )
279         set_callbacks( SubsdelayCreate, SubsdelayDestroy )
280         set_category( CAT_VIDEO )
281         set_subcategory( SUBCAT_VIDEO_SUBPIC )
282
283         add_integer( CFG_MODE, 1, MODE_TEXT, MODE_LONGTEXT, false )
284         change_integer_list( pi_mode_values, ppsz_mode_descriptions )
285
286         add_float_with_range( CFG_FACTOR, 2, 0, 20, FACTOR_TEXT, FACTOR_LONGTEXT, false )
287
288         add_integer_with_range( CFG_OVERLAP, 3, 1, 4, OVERLAP_TEXT, OVERLAP_LONGTEXT, false )
289
290         add_integer_with_range( CFG_MIN_ALPHA, 70, 0, 255, MIN_ALPHA_TEXT, MIN_ALPHA_LONGTEXT, false )
291
292         set_section( N_("Overlap fix"), NULL )
293
294         add_integer( CFG_MIN_STOPS_INTERVAL, 1000, MIN_STOPS_INTERVAL_TEXT, MIN_STOPS_INTERVAL_LONGTEXT, false )
295
296         add_integer( CFG_MIN_START_STOP_INTERVAL, 1000, MIN_START_STOP_INTERVAL_TEXT,
297                      MIN_START_STOP_INTERVAL_LONGTEXT, false )
298
299         add_integer( CFG_MIN_STOP_START_INTERVAL, 1000, MIN_STOP_START_INTERVAL_TEXT,
300                      MIN_STOP_START_INTERVAL_LONGTEXT, false )
301
302     vlc_module_end ()
303
304 static const char * const ppsz_filter_options[] = { "mode", "factor", "overlap", NULL };
305
306 /*****************************************************************************
307  * SubsdelayCreate: Create subsdelay filter
308  *****************************************************************************/
309 static int SubsdelayCreate( vlc_object_t *p_this )
310 {
311     filter_t *p_filter = (filter_t *) p_this;
312     filter_sys_t *p_sys;
313
314     /* allocate structure */
315     p_sys = (filter_sys_t*) malloc( sizeof(filter_sys_t) );
316
317     if( !p_sys )
318     {
319         return VLC_ENOMEM;
320     }
321
322     /* init parameters */
323
324     p_sys->i_mode = var_CreateGetIntegerCommand( p_filter, CFG_MODE );
325     var_AddCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
326
327     p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( var_CreateGetFloatCommand( p_filter, CFG_FACTOR ) );
328     var_AddCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
329
330     p_sys->i_overlap = var_CreateGetIntegerCommand( p_filter, CFG_OVERLAP );
331     var_AddCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
332
333     p_sys->i_min_alpha = var_CreateGetIntegerCommand( p_filter, CFG_MIN_ALPHA );
334     var_AddCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
335
336     p_sys->i_min_stops_interval
337             = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOPS_INTERVAL ) );
338     var_AddCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
339
340     p_sys->i_min_stop_start_interval
341             = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_STOP_START_INTERVAL ) );
342     var_AddCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
343
344     p_sys->i_min_start_stop_interval
345             = MILLISEC_TO_MICROSEC( var_CreateGetIntegerCommand( p_filter, CFG_MIN_START_STOP_INTERVAL ) );
346     var_AddCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
347
348     p_filter->p_sys = p_sys;
349     p_filter->pf_sub_filter = SubsdelayFilter;
350
351     config_ChainParse( p_filter, CFG_PREFIX, ppsz_filter_options, p_filter->p_cfg );
352
353     SubsdelayHeapInit( &p_sys->heap );
354
355     return VLC_SUCCESS;
356 }
357
358 /*****************************************************************************
359  * SubsdelayDestroy: Destroy subsdelay filter
360  *****************************************************************************/
361 static void SubsdelayDestroy( vlc_object_t *p_this )
362 {
363     filter_t *p_filter = (filter_t *) p_this;
364     filter_sys_t *p_sys = p_filter->p_sys;
365
366     SubsdelayHeapDestroy( &p_sys->heap );
367
368     /* destroy parameters */
369
370     var_DelCallback( p_filter, CFG_MODE, SubsdelayCallback, p_sys );
371     var_Destroy( p_filter, CFG_MODE );
372
373     var_DelCallback( p_filter, CFG_FACTOR, SubsdelayCallback, p_sys );
374     var_Destroy( p_filter, CFG_FACTOR );
375
376     var_DelCallback( p_filter, CFG_OVERLAP, SubsdelayCallback, p_sys );
377     var_Destroy( p_filter, CFG_OVERLAP );
378
379     var_DelCallback( p_filter, CFG_MIN_ALPHA, SubsdelayCallback, p_sys );
380     var_Destroy( p_filter, CFG_MIN_ALPHA );
381
382     var_DelCallback( p_filter, CFG_MIN_STOPS_INTERVAL, SubsdelayCallback, p_sys );
383     var_Destroy( p_filter, CFG_MIN_STOPS_INTERVAL );
384
385     var_DelCallback( p_filter, CFG_MIN_STOP_START_INTERVAL, SubsdelayCallback, p_sys );
386     var_Destroy( p_filter, CFG_MIN_STOP_START_INTERVAL );
387
388     var_DelCallback( p_filter, CFG_MIN_START_STOP_INTERVAL, SubsdelayCallback, p_sys );
389     var_Destroy( p_filter, CFG_MIN_START_STOP_INTERVAL );
390
391     free( p_sys );
392 }
393
394 /*****************************************************************************
395  * SubsdelayFilter: Filter new subpicture
396  *****************************************************************************/
397 static subpicture_t * SubsdelayFilter( filter_t *p_filter, subpicture_t* p_subpic )
398 {
399     subsdelay_heap_t *p_heap;
400     subsdelay_heap_entry_t *p_entry;
401
402     if( !p_subpic->b_subtitle )
403     {
404         return p_subpic;
405     }
406
407     if( SubpicIsEmpty( p_subpic ) )
408     {
409         /* empty subtitles usually helps terminate ephemer subtitles, but this filter calculates the stop value anyway,
410            so this subtitle can be dropped */
411
412         subpicture_Delete( p_subpic );
413
414         return NULL;
415     }
416
417     p_heap = &p_filter->p_sys->heap;
418
419     /* add subpicture to the heap */
420
421     SubsdelayHeapLock( p_heap );
422
423     p_entry = SubsdelayHeapPush( p_heap, p_subpic, p_filter );
424     if( !p_entry )
425     {
426         SubsdelayHeapUnlock( p_heap );
427
428         msg_Err(p_filter, "Can't add subpicture to the heap");
429
430         return p_subpic;
431     }
432
433     p_subpic = p_entry->p_subpic; /* get the local subpic */
434
435     if( p_subpic->b_ephemer )
436     {
437         /* set a relativly long delay in hope that the next subtitle
438            will arrive in this time and the real delay could be determined */
439
440         p_subpic->i_stop = p_subpic->i_start + 20000000; /* start + 20 sec */
441         p_subpic->b_ephemer = false;
442     }
443
444
445     SubsdelayEnforceDelayRules( p_filter );
446
447     SubsdelayHeapUnlock( p_heap );
448
449     return p_subpic;
450 }
451
452 /*****************************************************************************
453  * SubsdelayCallback: Subsdelay parameters callback
454  *****************************************************************************/
455 static int SubsdelayCallback( vlc_object_t *p_this, char const *psz_var, vlc_value_t oldval, vlc_value_t newval,
456         void *p_data )
457 {
458     filter_sys_t *p_sys = (filter_sys_t *) p_data;
459
460     VLC_UNUSED( oldval );
461
462     SubsdelayHeapLock( &p_sys->heap );
463
464     if( !strcmp( psz_var, CFG_MODE ) )
465     {
466         p_sys->i_mode = newval.i_int;
467     }
468     else if( !strcmp( psz_var, CFG_FACTOR ) )
469     {
470         p_sys->i_factor = FLOAT_FACTOR_TO_INT_FACTOR( newval.f_float );
471     }
472     else if( !strcmp( psz_var, CFG_OVERLAP ) )
473     {
474         p_sys->i_overlap = newval.i_int;
475     }
476     else if( !strcmp( psz_var, CFG_MIN_ALPHA ) )
477     {
478         p_sys->i_min_alpha = newval.i_int;
479     }
480     else if( !strcmp( psz_var, CFG_MIN_STOPS_INTERVAL ) )
481     {
482         p_sys->i_min_stops_interval = MILLISEC_TO_MICROSEC( newval.i_int );
483     }
484     else if( !strcmp( psz_var, CFG_MIN_STOP_START_INTERVAL ) )
485     {
486         p_sys->i_min_stop_start_interval = MILLISEC_TO_MICROSEC( newval.i_int );
487     }
488     else if( !strcmp( psz_var, CFG_MIN_START_STOP_INTERVAL ) )
489     {
490         p_sys->i_min_start_stop_interval = MILLISEC_TO_MICROSEC( newval.i_int );
491     }
492     else
493     {
494         SubsdelayHeapUnlock( &p_sys->heap );
495         return VLC_ENOVAR;
496     }
497
498     SubsdelayRecalculateDelays( (filter_t *) p_this );
499
500     SubsdelayHeapUnlock( &p_sys->heap );
501     return VLC_SUCCESS;
502 }
503
504 /*****************************************************************************
505  * SubsdelayHeapInit: Initialize heap
506  *****************************************************************************/
507 static void SubsdelayHeapInit( subsdelay_heap_t *p_heap )
508 {
509     p_heap->i_count = 0;
510     p_heap->p_head = NULL;
511
512     vlc_mutex_init( &p_heap->lock );
513 }
514
515 /*****************************************************************************
516  * SubsdelayHeapDestroy: Destroy the heap and remove its entries
517  *****************************************************************************/
518 static void SubsdelayHeapDestroy( subsdelay_heap_t *p_heap )
519 {
520     subsdelay_heap_entry_t *p_entry;
521
522     SubsdelayHeapLock( p_heap );
523
524     for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
525     {
526         p_entry->p_subpic->i_stop = p_entry->p_source->i_stop;
527
528         p_entry->p_filter = NULL;
529     }
530
531     SubsdelayHeapUnlock( p_heap );
532
533     vlc_mutex_destroy( &p_heap->lock );
534 }
535
536 /*****************************************************************************
537  * SubsdelayHeapPush: Add new subpicture to the heap
538  *****************************************************************************/
539 static subsdelay_heap_entry_t *SubsdelayHeapPush( subsdelay_heap_t *p_heap, subpicture_t *p_subpic, filter_t *p_filter )
540 {
541     subsdelay_heap_entry_t *p_entry, *p_last, *p_new_entry;
542
543     if( p_heap->i_count >= SUBSDELAY_MAX_ENTRIES )
544     {
545         return NULL; /* the heap is full */
546     }
547
548     p_new_entry = SubsdelayEntryCreate( p_subpic, p_filter );
549
550     if( !p_new_entry )
551     {
552         return NULL;
553     }
554
555
556     p_last = NULL;
557
558     for( p_entry = p_heap->p_head; p_entry != NULL; p_entry = p_entry->p_next )
559     {
560         if( p_entry->p_source->i_start > p_subpic->i_start )
561         {
562             /* the new entry should be inserted before p_entry */
563             break;
564         }
565
566         p_last = p_entry;
567     }
568
569     if( p_last )
570     {
571         p_new_entry->p_next = p_last->p_next;
572         p_last->p_next = p_new_entry;
573
574
575         if( p_last->b_update_ephemer )
576         {
577             /* the correct stop value can be determined */
578
579             p_last->p_source->i_stop = p_new_entry->p_source->i_start;
580             p_last->b_update_ephemer = false;
581         }
582     }
583     else
584     {
585         p_new_entry->p_next = p_heap->p_head;
586         p_heap->p_head = p_new_entry;
587     }
588
589
590     /* rebuild list */
591
592     SubsdelayRebuildList( p_heap );
593
594     return p_new_entry;
595 }
596
597 /*****************************************************************************
598  * SubsdelayHeapRemove: Remove entry
599  *****************************************************************************/
600 static void SubsdelayHeapRemove( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry )
601 {
602     subsdelay_heap_entry_t *p_curr, *p_prev;
603
604     p_prev = NULL;
605
606     for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
607     {
608         if( p_curr == p_entry )
609         {
610             break;
611         }
612
613         p_prev = p_curr;
614     }
615
616     if( p_prev )
617     {
618         p_prev->p_next = p_entry->p_next;
619     }
620     else
621     {
622         p_heap->p_head = p_entry->p_next;
623     }
624
625     p_entry->p_filter = NULL;
626
627     SubsdelayRebuildList( p_heap );
628 }
629
630
631 static void SubsdelayRebuildList( subsdelay_heap_t *p_heap )
632 {
633     subsdelay_heap_entry_t *p_curr;
634     int i_index;
635
636     i_index = 0;
637     for( p_curr = p_heap->p_head; p_curr != NULL; p_curr = p_curr->p_next )
638     {
639         p_heap->p_list[i_index] = p_curr;
640         i_index++;
641     }
642
643     p_heap->i_count = i_index;
644 }
645
646 /*****************************************************************************
647  * SubsdelayHeapLock: Lock the heap
648  *****************************************************************************/
649 static void SubsdelayHeapLock( subsdelay_heap_t *p_heap )
650 {
651     vlc_mutex_lock( &p_heap->lock );
652 }
653
654 /*****************************************************************************
655  * SubsdelayHeapUnlock: Unlock the heap
656  *****************************************************************************/
657 static void SubsdelayHeapUnlock( subsdelay_heap_t *p_heap )
658 {
659     vlc_mutex_unlock( &p_heap->lock );
660 }
661
662
663 /*****************************************************************************
664  * SubsdelayHeapCreateEntry: Create new entry
665  *****************************************************************************/
666 static subsdelay_heap_entry_t * SubsdelayEntryCreate( subpicture_t *p_source, filter_t *p_filter )
667 {
668     subsdelay_heap_entry_t *p_entry;
669
670     subpicture_t *p_new_subpic;
671
672     subpicture_updater_t updater;
673
674     /* allocate structure */
675
676     p_entry = (subsdelay_heap_entry_t *) malloc( sizeof( subsdelay_heap_entry_t ) );
677
678     if( !p_entry )
679     {
680         return NULL;
681     }
682
683     /* initialize local updater */
684
685     updater.p_sys = p_entry;
686     updater.pf_validate = SubpicValidateWrapper;
687     updater.pf_update = SubpicUpdateWrapper;
688     updater.pf_destroy = SubpicDestroyWrapper;
689
690     /* create new subpic */
691
692     p_new_subpic = SubpicClone( p_source,  &updater );
693
694     if( !p_new_subpic )
695     {
696         free( p_entry );
697         return NULL;
698     }
699
700     /* initialize entry */
701
702     p_entry->p_subpic = p_new_subpic;
703     p_entry->p_source = p_source;
704     p_entry->p_filter = p_filter;
705     p_entry->p_next = NULL;
706     p_entry->b_update_stop = true;
707     p_entry->b_update_ephemer = p_source->b_ephemer;
708     p_entry->b_update_position = true;
709     p_entry->b_check_empty = true;
710     p_entry->i_new_stop = p_source->i_stop;
711     p_entry->b_last_region_saved = false;
712     p_entry->i_last_region_x = 0;
713     p_entry->i_last_region_y = 0;
714     p_entry->i_last_region_align = 0;
715
716     return p_entry;
717 }
718
719 /*****************************************************************************
720  * SubsdelayEntryDestroy: Destroy entry
721  *****************************************************************************/
722 static void SubsdelayEntryDestroy( subsdelay_heap_entry_t *p_entry )
723 {
724     SubpicDestroyClone( p_entry->p_source );
725     free( p_entry );
726 }
727
728 /*****************************************************************************
729  * SubsdelayHeapCountOverlap: Count overlapping subtitles at a given time
730  *****************************************************************************/
731 static int SubsdelayHeapCountOverlap( subsdelay_heap_t *p_heap, subsdelay_heap_entry_t *p_entry, mtime_t i_date )
732 {
733     subsdelay_heap_entry_t *p_curr;
734     int i_overlaps;
735
736     VLC_UNUSED( p_heap );
737
738     i_overlaps = 0;
739
740     for( p_curr = p_entry->p_next; p_curr != NULL; p_curr = p_curr->p_next )
741     {
742         if( p_curr->p_source->i_start > i_date )
743         {
744             break;
745         }
746
747         if( !p_curr->b_check_empty ) /* subtitle was checked, and it's not empty */
748         {
749             i_overlaps++;
750         }
751     }
752
753     return i_overlaps;
754 }
755
756 /*****************************************************************************
757  * SubsdelayEntryNewStopValueUpdated: Update source stop value after the new
758  *     stop value was changed
759  *****************************************************************************/
760 static void SubsdelayEntryNewStopValueUpdated( subsdelay_heap_entry_t *p_entry )
761 {
762     if( !p_entry->b_update_stop )
763     {
764         p_entry->p_subpic->i_stop = p_entry->i_new_stop - 100000; /* 0.1 sec less */
765     }
766 }
767
768 /*****************************************************************************
769  * SubsdelayEnforceDelayRules: Update subtitles delay after adding new
770  *     subtitle or changing subtitle stop value
771  *****************************************************************************/
772 static void SubsdelayEnforceDelayRules( filter_t *p_filter )
773 {
774     subsdelay_heap_entry_t ** p_list;
775     int i, j, i_count, i_overlap;
776     int64_t i_offset;
777     int64_t i_min_stops_interval;
778     int64_t i_min_stop_start_interval;
779     int64_t i_min_start_stop_interval;
780
781     p_list = p_filter->p_sys->heap.p_list;
782     i_count = p_filter->p_sys->heap.i_count;
783
784     i_overlap = p_filter->p_sys->i_overlap;
785     i_min_stops_interval = p_filter->p_sys->i_min_stops_interval;
786     i_min_stop_start_interval = p_filter->p_sys->i_min_stop_start_interval;
787     i_min_start_stop_interval = p_filter->p_sys->i_min_start_stop_interval;
788
789     /* step 1 - enforce min stops interval rule (extend delays) */
790
791     /* look for:
792     [subtitle 1 ..............]
793            [subtitle 2 ..............]
794                               |<-MinStopsInterval->|
795
796      * and extend newer subtitle:
797     [subtitle 1 ..............]
798            [subtitle 2 ............................]
799                               |<-MinStopsInterval->|
800     */
801
802     for( i = 0; i < i_count - 1; i++ )
803     {
804         p_list[i + 1]->i_new_stop = __MAX( p_list[i + 1]->i_new_stop,
805                 p_list[i]->i_new_stop + i_min_stops_interval );
806     }
807
808     /* step 2 - enforce min stop start interval rule (extend delays) */
809
810     /* look for:
811     [subtitle 1 .........]
812                                    [subtitle 2 ....]
813           |<-MinStopStartInterval->|
814
815      * and fill the gap:
816     [subtitle 1 ..................]
817                                    [subtitle 2 ....]
818           |<-MinStopStartInterval->|
819     */
820
821     for( i = 0; i < i_count; i++ )
822     {
823         for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
824         {
825             i_offset = p_list[j]->p_source->i_start - p_list[i]->i_new_stop;
826
827             if( i_offset <= 0 )
828             {
829                 continue;
830             }
831
832             if( i_offset < i_min_stop_start_interval )
833             {
834                 p_list[i]->i_new_stop = p_list[j]->p_source->i_start;
835             }
836
837             break;
838         }
839     }
840
841     /* step 3 - enforce min start stop interval rule (shorten delays) */
842
843     /* look for:
844     [subtitle 1 ............]
845                     [subtitle 2 ....................]
846                     |<-MinStartStopInterval->|
847
848      * and remove the overlapping part:
849     [subtitle 1 ...]
850                     [subtitle 2 ....................]
851                     |<-MinStartStopInterval->|
852     */
853
854
855     for( i = 0; i < i_count; i++ )
856     {
857         for( j = i + 1; j < __MIN( i_count, i + 1 + i_overlap ); j++ )
858         {
859             i_offset = p_list[i]->i_new_stop - p_list[j]->p_source->i_start;
860
861             if( i_offset <= 0 )
862             {
863                 break;
864             }
865
866             if( i_offset < i_min_start_stop_interval )
867             {
868                 p_list[i]->i_new_stop = p_list[j]->p_source->i_start;
869                 break;
870             }
871         }
872     }
873
874     /* step 4 - enforce max overlapping rule (shorten delays)*/
875
876     /* look for: (overlap = 2)
877     [subtitle 1 ..............]
878              [subtitle 2 ..............]
879                       [subtitle 3 ..............]
880
881
882      * and cut older subtitle:
883     [subtitle 1 .....]
884              [subtitle 2 ..............]
885                       [subtitle 3 ..............]
886     */
887
888     for( i = 0; i < i_count - i_overlap; i++ )
889     {
890         if( p_list[i]->i_new_stop > p_list[i + i_overlap]->p_source->i_start )
891         {
892             p_list[i]->i_new_stop = p_list[i + i_overlap]->p_source->i_start;
893         }
894     }
895
896     /* finally - update all */
897
898     for( i = 0; i < i_count; i++ )
899     {
900         SubsdelayEntryNewStopValueUpdated( p_list[i] );
901     }
902 }
903
904 /*****************************************************************************
905  * SubsdelayRecalculateDelays: Recalculate subtitles delay after changing
906  *     one of the filter's parameters
907  *****************************************************************************/
908 static void SubsdelayRecalculateDelays( filter_t *p_filter )
909 {
910     subsdelay_heap_entry_t *p_curr;
911
912     for( p_curr = p_filter->p_sys->heap.p_head; p_curr != NULL; p_curr = p_curr->p_next )
913     {
914         if( !p_curr->b_update_ephemer )
915         {
916             p_curr->i_new_stop = p_curr->p_source->i_start + SubsdelayEstimateDelay( p_filter, p_curr );
917             p_curr->b_update_stop = false;
918         }
919     }
920
921     SubsdelayEnforceDelayRules( p_filter );
922 }
923
924 /*****************************************************************************
925  * SubpicValidateWrapper: Subpicture validate callback wrapper
926  *****************************************************************************/
927 static int SubpicValidateWrapper( subpicture_t *p_subpic, bool has_src_changed, const video_format_t *p_fmt_src,
928                                   bool has_dst_changed, const video_format_t *p_fmt_dst, mtime_t i_ts )
929 {
930     subsdelay_heap_entry_t *p_entry;
931     mtime_t i_new_ts;
932     int i_result = VLC_SUCCESS;
933
934     p_entry = p_subpic->updater.p_sys;
935     if( !p_entry )
936     {
937         return VLC_SUCCESS;
938     }
939
940     /* call source validate */
941     if( p_entry->p_source->updater.pf_validate )
942     {
943         i_new_ts = p_entry->p_source->i_start +
944                    ( (double)( p_entry->p_source->i_stop - p_entry->p_source->i_start ) * ( i_ts - p_entry->p_source->i_start ) ) /
945                    ( p_entry->i_new_stop - p_entry->p_source->i_start );
946
947         i_result = p_entry->p_source->updater.pf_validate( p_entry->p_source, has_src_changed, p_fmt_src,
948                                                         has_dst_changed, p_fmt_dst, i_new_ts );
949     }
950
951
952     p_entry->b_last_region_saved = false;
953
954     if( p_subpic->p_region )
955     {
956         /* save copy */
957         p_entry->i_last_region_x = p_subpic->p_region->i_x;
958         p_entry->i_last_region_y = p_subpic->p_region->i_y;
959         p_entry->i_last_region_align = p_subpic->p_region->i_align;
960
961         p_entry->b_last_region_saved = true;
962     }
963
964     if( !i_result )
965     {
966         /* subpic update isn't necessary, so local update should be called here */
967         SubpicLocalUpdate( p_subpic, i_ts );
968     }
969
970     return i_result;
971 }
972
973 /*****************************************************************************
974  * SubpicUpdateWrapper: Subpicture update callback wrapper
975  *****************************************************************************/
976 static void SubpicUpdateWrapper( subpicture_t *p_subpic, const video_format_t *p_fmt_src,
977                                   const video_format_t *p_fmt_dst, mtime_t i_ts )
978 {
979     subsdelay_heap_entry_t *p_entry;
980     mtime_t i_new_ts;
981
982     p_entry = p_subpic->updater.p_sys;
983     if( !p_entry )
984     {
985         return;
986     }
987
988     /* call source update */
989     if( p_entry->p_source->updater.pf_update )
990     {
991         i_new_ts = p_entry->p_source->i_start +
992                    ( (double)( p_entry->p_source->i_stop - p_entry->p_source->i_start ) * ( i_ts - p_entry->p_source->i_start ) ) /
993                    ( p_entry->i_new_stop - p_entry->p_source->i_start );
994
995         p_entry->p_source->p_region = p_entry->p_subpic->p_region;
996
997         p_entry->p_source->updater.pf_update( p_entry->p_source, p_fmt_src, p_fmt_dst, i_new_ts );
998
999         p_entry->p_subpic->p_region = p_entry->p_source->p_region;
1000     }
1001
1002     SubpicLocalUpdate( p_subpic, i_ts );
1003 }
1004
1005 /*****************************************************************************
1006  * SubpicDestroyCallback: Subpicture destroy callback
1007  *****************************************************************************/
1008 static void SubpicDestroyWrapper( subpicture_t *p_subpic )
1009 {
1010     subsdelay_heap_entry_t *p_entry;
1011     subsdelay_heap_t *p_heap;
1012
1013     p_entry = p_subpic->updater.p_sys;
1014
1015     if( !p_entry )
1016     {
1017         return;
1018     }
1019
1020     if( p_entry->p_filter )
1021     {
1022         p_heap = &p_entry->p_filter->p_sys->heap;
1023
1024         SubsdelayHeapLock( p_heap );
1025         SubsdelayHeapRemove( p_heap, p_entry );
1026         SubsdelayHeapUnlock( p_heap );
1027     }
1028
1029     SubsdelayEntryDestroy( p_entry );
1030 }
1031
1032 /*****************************************************************************
1033  * SubpicLocalUpdate: rewrite some of the subpicture parameters
1034  *****************************************************************************/
1035 static void SubpicLocalUpdate( subpicture_t* p_subpic, mtime_t i_ts )
1036 {
1037     subsdelay_heap_entry_t *p_entry;
1038     subsdelay_heap_t *p_heap;
1039     filter_t *p_filter;
1040
1041     int i_overlapping;
1042
1043     p_entry = p_subpic->updater.p_sys;
1044     if( !p_entry || !p_entry->p_filter )
1045     {
1046         return;
1047     }
1048
1049     p_filter = p_entry->p_filter;
1050     p_heap = &p_filter->p_sys->heap;
1051
1052     SubsdelayHeapLock( p_heap );
1053
1054     if( p_entry->b_check_empty && p_subpic->p_region )
1055     {
1056         if( SubsdelayIsTextEmpty( p_subpic->p_region->psz_html ) ||
1057             SubsdelayIsTextEmpty( p_subpic->p_region->psz_text ) )
1058         {
1059             /* remove empty subtitle */
1060
1061             p_subpic->b_ephemer = false;
1062             p_subpic->i_stop = p_subpic->i_start;
1063
1064             SubsdelayHeapRemove( p_heap, p_entry );
1065
1066             SubsdelayHeapUnlock( p_heap );
1067
1068             return;
1069         }
1070
1071         p_entry->b_check_empty = false;
1072     }
1073
1074     if( p_entry->b_update_stop && !p_entry->b_update_ephemer )
1075     {
1076         p_entry->i_new_stop = p_entry->p_source->i_start + SubsdelayEstimateDelay( p_filter, p_entry );
1077         p_entry->b_update_stop = false;
1078
1079         SubsdelayEnforceDelayRules( p_filter );
1080     }
1081
1082     i_overlapping = SubsdelayHeapCountOverlap( p_heap, p_entry, i_ts );
1083
1084     p_subpic->i_alpha = SubsdelayCalculateAlpha( p_filter, i_overlapping, p_entry->p_source->i_alpha );
1085
1086     if( p_entry->b_update_position )
1087     {
1088         p_subpic->b_absolute = false;
1089
1090         if( p_subpic->p_region )
1091         {
1092             p_subpic->p_region->i_x = 0;
1093             p_subpic->p_region->i_y = 10;
1094             p_subpic->p_region->i_align = ( p_subpic->p_region->i_align & ( ~SUBPICTURE_ALIGN_MASK ) )
1095                     | SUBPICTURE_ALIGN_BOTTOM;
1096         }
1097
1098         p_entry->b_update_position = false;
1099     }
1100     else if( p_entry->b_last_region_saved )
1101     {
1102         p_subpic->b_absolute = true;
1103
1104         if( p_subpic->p_region )
1105         {
1106             p_subpic->p_region->i_x = p_entry->i_last_region_x;
1107             p_subpic->p_region->i_y = p_entry->i_last_region_y;
1108             p_subpic->p_region->i_align = p_entry->i_last_region_align;
1109         }
1110     }
1111
1112     SubsdelayHeapUnlock( p_heap );
1113 }
1114
1115 /*****************************************************************************
1116  * SubpicIsEmpty: subpic region contains empty string
1117  *****************************************************************************/
1118 static bool SubpicIsEmpty( subpicture_t* p_subpic )
1119 {
1120     return ( p_subpic->p_region && ( SubsdelayIsTextEmpty( p_subpic->p_region->psz_html ) ||
1121                                      SubsdelayIsTextEmpty( p_subpic->p_region->psz_text ) ) );
1122 }
1123
1124 /*****************************************************************************
1125  * SubpicClone: Clone subpicture (shallow copy)
1126  *****************************************************************************/
1127 static subpicture_t *SubpicClone( subpicture_t *p_source, subpicture_updater_t *updater )
1128 {
1129     subpicture_t *p_subpic;
1130     subpicture_updater_t subpic_updater;
1131     subpicture_private_t *p_subpic_private;
1132
1133     p_subpic = subpicture_New( updater );
1134
1135     if( !p_subpic )
1136     {
1137         return NULL;
1138     }
1139
1140     /* save private members */
1141     subpic_updater = p_subpic->updater;
1142     p_subpic_private = p_subpic->p_private;
1143
1144     /* copy the entire struct */
1145     memcpy( p_subpic, p_source, sizeof( subpicture_t ) );
1146
1147     /* restore private members */
1148     p_subpic->updater = subpic_updater;
1149     p_subpic->p_private = p_subpic_private;
1150
1151     return p_subpic;
1152 }
1153
1154 /*****************************************************************************
1155  * SubpicDestroyClone: destroy cloned subpicture (shared references will not
1156  *     be destroyed)
1157  *****************************************************************************/
1158 static void SubpicDestroyClone( subpicture_t *p_subpic )
1159 {
1160     p_subpic->p_region = NULL; /* don't destroy region */
1161     subpicture_Delete( p_subpic );
1162 }
1163
1164 /*****************************************************************************
1165  * SubsdelayEstimateDelay: Calculate new subtitle delay according to its
1166  *     content and the calculation mode
1167  *****************************************************************************/
1168 static int64_t SubsdelayEstimateDelay( filter_t *p_filter, subsdelay_heap_entry_t *p_entry )
1169 {
1170     int i_mode;
1171     int i_factor;
1172     int i_rank;
1173
1174     i_mode = p_filter->p_sys->i_mode;
1175     i_factor = p_filter->p_sys->i_factor;
1176
1177     if( i_mode == SUBSDELAY_MODE_ABSOLUTE )
1178     {
1179         return ( p_entry->p_source->i_stop - p_entry->p_source->i_start + INT_FACTOR_TO_MICROSEC( i_factor ) );
1180     }
1181
1182     if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_CONTENT )
1183     {
1184         if( p_entry->p_subpic && p_entry->p_subpic->p_region && ( p_entry->p_subpic->p_region->psz_text
1185                 || p_entry->p_subpic->p_region->psz_html ) )
1186         {
1187             if( p_entry->p_subpic->p_region->psz_text )
1188             {
1189                 i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_text );
1190             }
1191             else
1192             {
1193                 i_rank = SubsdelayGetTextRank( p_entry->p_subpic->p_region->psz_html );
1194             }
1195
1196             return ( i_rank * INT_FACTOR_TO_RANK_FACTOR( i_factor ) );
1197         }
1198
1199         /* content is unavailable, calculation mode should be based on source delay */
1200         i_mode = SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY;
1201     }
1202
1203     if( i_mode == SUBSDELAY_MODE_RELATIVE_SOURCE_DELAY )
1204     {
1205         return ( ( i_factor * ( p_entry->p_source->i_stop - p_entry->p_source->i_start ) ) / INT_FACTOR_BASE );
1206     }
1207
1208     return 10000000; /* 10 sec */
1209 }
1210
1211 /*****************************************************************************
1212  * SubsdelayCalculateAlpha: Calculate subtitle alpha according to source alpha
1213  *     value and number of overlapping subtitles
1214  *****************************************************************************/
1215 static int SubsdelayCalculateAlpha( filter_t *p_filter, int i_overlapping, int i_source_alpha )
1216 {
1217     int i_new_alpha;
1218     int i_min_alpha;
1219
1220     i_min_alpha = p_filter->p_sys->i_min_alpha;
1221
1222     if( i_overlapping > p_filter->p_sys->i_overlap - 1)
1223     {
1224         i_overlapping = p_filter->p_sys->i_overlap - 1;
1225     }
1226
1227     switch ( p_filter->p_sys->i_overlap )
1228     {
1229     case 1:
1230         i_new_alpha = 255;
1231         break;
1232
1233     case 2:
1234         i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha );
1235         break;
1236
1237     case 3:
1238         i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 2;
1239         break;
1240
1241     default:
1242         i_new_alpha = 255 - i_overlapping * ( 255 - i_min_alpha ) / 3;
1243         break;
1244     }
1245
1246     return ( i_source_alpha * i_new_alpha ) / 255;
1247 }
1248
1249 /*****************************************************************************
1250  * SubsdelayGetWordRank: Calculate single word rank according to its length
1251  *****************************************************************************/
1252 static int SubsdelayGetWordRank( int i_length )
1253 {
1254     /* p_rank[0] = p_rank[1] = p_rank[2] = 300;
1255     for( i = 3; i < 20; i++ ) p_rank[i] = (int) ( 1.1 * p_rank[i - 1] ); */
1256
1257     static const int p_rank[20] = { 300, 300, 300, 330, 363, 399, 438, 481, 529, 581,
1258                                     639, 702, 772, 849, 933, 1026, 1128, 1240, 1364, 1500 };
1259
1260     if( i_length < 1 )
1261     {
1262         return 0;
1263     }
1264
1265     if( i_length > 20 )
1266     {
1267         i_length = 20;
1268     }
1269
1270     return p_rank[i_length - 1];
1271 }
1272
1273 /*****************************************************************************
1274  * SubsdelayGetTextRank: Calculate text rank
1275  *****************************************************************************/
1276 static int SubsdelayGetTextRank( char *psz_text )
1277 {
1278     bool b_skip_esc;
1279     bool b_skip_tag;
1280     char c;
1281
1282     int i, i_word_length, i_rank;
1283
1284     i = 0;
1285     i_word_length = 0;
1286     i_rank = 0;
1287
1288     b_skip_esc = false;
1289     b_skip_tag = false;
1290
1291     while ( psz_text[i] != '\0' )
1292     {
1293         c = psz_text[i];
1294         i++;
1295
1296         if( c == '\\' && !b_skip_esc )
1297         {
1298             b_skip_esc = true;
1299             continue;
1300         }
1301
1302         if( psz_text[i] == '<' )
1303         {
1304             b_skip_tag = true;
1305             continue;
1306         }
1307
1308         if( !b_skip_esc && !b_skip_tag )
1309         {
1310             if( c == ' ' || c == ',' || c == '.' || c == '-' || c == '?' || c == '!' ) /* common delimiters */
1311             {
1312                 if( i_word_length > 0 )
1313                 {
1314                     i_rank += SubsdelayGetWordRank( i_word_length );
1315                 }
1316
1317                 i_word_length = 0;
1318             }
1319             else
1320             {
1321                 i_word_length++;
1322             }
1323         }
1324
1325         b_skip_esc = false;
1326
1327         if( c == '>' )
1328         {
1329             b_skip_tag = false;
1330         }
1331
1332     }
1333
1334     if( i_word_length > 0 )
1335     {
1336         i_rank += SubsdelayGetWordRank( i_word_length );
1337     }
1338
1339     return i_rank;
1340 }
1341
1342 /*****************************************************************************
1343  * SubsdelayIsTextEmpty: Check if the text contains spaces only
1344  *****************************************************************************/
1345 static bool SubsdelayIsTextEmpty( char *psz_text )
1346 {
1347     if( !psz_text )
1348     {
1349         return false;
1350     }
1351
1352     psz_text += strspn( psz_text, " " );
1353     return !( *psz_text );
1354 }