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