Coverage Report

Created: 2023-03-26 06:11

/src/vlc/modules/codec/ttml/ttml.c
Line
Count
Source (jump to first uncovered line)
1
/*****************************************************************************
2
 * ttml.c : TTML helpers
3
 *****************************************************************************
4
 * Copyright (C) 2017 VLC authors and VideoLAN
5
 *
6
 * This program is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU Lesser General Public License as published by
8
 * the Free Software Foundation; either version 2.1 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public License
17
 * along with this program; if not, write to the Free Software Foundation,
18
 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19
 *****************************************************************************/
20
#ifdef HAVE_CONFIG_H
21
# include "config.h"
22
#endif
23
24
#include <vlc_common.h>
25
#include <vlc_plugin.h>
26
#include <vlc_xml.h>
27
#include <vlc_strings.h>
28
#include <vlc_charset.h>
29
30
#include <assert.h>
31
#include <stdlib.h>
32
#include <limits.h>
33
34
#include "ttml.h"
35
36
#define ALIGN_TEXT N_("Subtitle justification")
37
#define ALIGN_LONGTEXT N_("Set the justification of subtitles")
38
39
/*****************************************************************************
40
 * Modules descriptor.
41
 *****************************************************************************/
42
43
4
vlc_module_begin ()
44
2
    set_capability( "spu decoder", 10 )
45
2
    set_shortname( N_("TTML decoder"))
46
2
    set_description( N_("TTML subtitles decoder") )
47
2
    set_callback( tt_OpenDecoder )
48
2
    set_subcategory( SUBCAT_INPUT_SCODEC )
49
2
    add_integer( "ttml-align", 0, ALIGN_TEXT, ALIGN_LONGTEXT )
50
51
2
    add_submodule()
52
2
        set_shortname( N_("TTML") )
53
2
        set_description( N_("TTML demuxer") )
54
2
        set_capability( "demux", 11 )
55
2
        set_subcategory( SUBCAT_INPUT_DEMUX )
56
2
        set_callbacks( tt_OpenDemux, tt_CloseDemux )
57
2
        add_shortcut( "ttml" )
58
59
2
vlc_module_end ()
60
61
62
int tt_node_NameCompare( const char* psz_tagname, const char* psz_pattern )
63
0
{
64
0
    if( !strncasecmp( "tt:", psz_tagname, 3 ) )
65
0
        psz_tagname += 3;
66
0
    return strcasecmp( psz_tagname, psz_pattern );
67
0
}
68
69
bool tt_node_HasChild( const tt_node_t *p_node )
70
0
{
71
0
    return p_node->p_child;
72
0
}
73
74
static inline bool tt_ScanReset( unsigned *a, unsigned *b, unsigned *c,
75
                                 char *d,  unsigned *e )
76
0
{
77
0
    *a = *b = *c = *d = *e = 0;
78
0
    return false;
79
0
}
80
81
static tt_time_t tt_ParseTime( const char *s )
82
0
{
83
0
    tt_time_t t = {-1, 0};
84
0
    unsigned h1 = 0, m1 = 0, s1 = 0, d1 = 0;
85
0
    char c = 0;
86
87
    /* Clock time */
88
0
    if( sscanf( s, "%u:%2u:%2u%c%u",     &h1, &m1, &s1, &c, &d1 ) == 5 ||
89
0
                           tt_ScanReset( &h1, &m1, &s1, &c, &d1 )      ||
90
0
        sscanf( s, "%u:%2u:%2u",         &h1, &m1, &s1          ) == 3 ||
91
0
                           tt_ScanReset( &h1, &m1, &s1, &c, &d1 ) )
92
0
    {
93
0
        t.base = vlc_tick_from_sec(h1 * 3600 + m1 * 60 + s1);
94
0
        if( c == '.' && d1 > 0 )
95
0
        {
96
0
            unsigned i_den = 1;
97
0
            for( const char *p = strchr( s, '.' ) + 1; *p && (i_den < UINT_MAX / 10); p++ )
98
0
                i_den *= 10;
99
0
            t.base += vlc_tick_from_samples(d1, i_den);
100
0
        }
101
0
        else if( c == ':' )
102
0
        {
103
0
            t.frames = d1;
104
0
        }
105
0
    }
106
0
    else /* Offset Time */
107
0
    {
108
0
        char *psz_end = (char *) s;
109
0
        double v = vlc_strtod_c( s, &psz_end );
110
0
        if( psz_end != s && *psz_end )
111
0
        {
112
0
            if( *psz_end == 'm' )
113
0
            {
114
0
                if( *(psz_end + 1) == 's' )
115
0
                    t.base = VLC_TICK_FROM_MS(v);
116
0
                else
117
0
                    t.base = vlc_tick_from_sec(60 * v);
118
0
            }
119
0
            else if( *psz_end == 's' )
120
0
            {
121
0
                t.base = vlc_tick_from_sec(v);
122
0
            }
123
0
            else if( *psz_end == 'h' )
124
0
            {
125
0
                t.base = vlc_tick_from_sec(v * 3600);
126
0
            }
127
0
            else if( *psz_end == 'f' )
128
0
            {
129
0
                t.base = 0;
130
0
                t.frames = v;
131
0
            }
132
            //else if( *psz_end == 't' );
133
0
        }
134
0
    }
135
136
0
    return t;
137
0
}
138
139
bool tt_timings_Contains( const tt_timings_t *p_range, const tt_time_t *time )
140
0
{
141
0
    if( tt_time_Valid( &p_range->end ) &&
142
0
        tt_time_Compare( &p_range->end, time ) <= 0 )
143
0
        return false;
144
145
0
    if( tt_time_Valid( &p_range->begin ) &&
146
0
        tt_time_Compare( &p_range->begin, time ) > 0 )
147
0
        return false;
148
149
0
    return true;
150
0
}
151
152
static void tt_textnode_Delete( tt_textnode_t *p_node )
153
0
{
154
0
    free( p_node->psz_text );
155
0
    free( p_node );
156
0
}
157
158
static void tt_node_FreeDictValue( void* p_value, void* p_obj )
159
0
{
160
0
    VLC_UNUSED( p_obj );
161
0
    free( p_value );
162
0
}
163
164
static void tt_node_Delete( tt_node_t *p_node )
165
0
{
166
0
    free( p_node->psz_node_name );
167
0
    vlc_dictionary_clear( &p_node->attr_dict, tt_node_FreeDictValue, NULL );
168
0
    free( p_node );
169
0
}
170
171
void tt_node_RecursiveDelete( tt_node_t *p_node )
172
0
{
173
0
    for( ; p_node->p_child ; )
174
0
    {
175
0
        tt_basenode_t *p_child = p_node->p_child;
176
0
        p_node->p_child = p_child->p_next;
177
178
0
        if( p_child->i_type == TT_NODE_TYPE_TEXT )
179
0
            tt_textnode_Delete( (tt_textnode_t *) p_child );
180
0
        else
181
0
            tt_node_RecursiveDelete( (tt_node_t *) p_child );
182
0
    }
183
0
    tt_node_Delete( p_node );
184
0
}
185
186
static void tt_node_ParentAddChild( tt_node_t* p_parent, tt_basenode_t *p_child )
187
0
{
188
0
    tt_basenode_t **pp_node = &p_parent->p_child;
189
0
    while( *pp_node != NULL )
190
0
        pp_node = &((*pp_node)->p_next);
191
0
    *pp_node = p_child;
192
0
}
193
194
static tt_textnode_t *tt_textnode_New( tt_node_t *p_parent, const char *psz_text )
195
0
{
196
0
    tt_textnode_t *p_node = calloc( 1, sizeof( *p_node ) );
197
0
    if( !p_node )
198
0
        return NULL;
199
0
    p_node->i_type = TT_NODE_TYPE_TEXT;
200
0
    p_node->p_parent = p_parent;
201
0
    if( p_parent )
202
0
        tt_node_ParentAddChild( p_parent, (tt_basenode_t *) p_node );
203
0
    p_node->psz_text = strdup( psz_text );
204
0
    return p_node;
205
0
}
206
207
tt_node_t * tt_node_New( xml_reader_t* reader, tt_node_t* p_parent, const char* psz_node_name )
208
0
{
209
0
    tt_node_t *p_node = calloc( 1, sizeof( *p_node ) );
210
0
    if( !p_node )
211
0
        return NULL;
212
213
0
    p_node->i_type = TT_NODE_TYPE_ELEMENT;
214
0
    p_node->psz_node_name = strdup( psz_node_name );
215
0
    if( unlikely( p_node->psz_node_name == NULL ) )
216
0
    {
217
0
        free( p_node );
218
0
        return NULL;
219
0
    }
220
0
    vlc_dictionary_init( &p_node->attr_dict, 0 );
221
0
    tt_time_Init( &p_node->timings.begin );
222
0
    tt_time_Init( &p_node->timings.end );
223
0
    tt_time_Init( &p_node->timings.dur );
224
0
    p_node->p_parent = p_parent;
225
0
    if( p_parent )
226
0
        tt_node_ParentAddChild( p_parent, (tt_basenode_t *) p_node );
227
228
0
    const char* psz_value = NULL;
229
0
    for( const char* psz_key = xml_ReaderNextAttr( reader, &psz_value );
230
0
         psz_key != NULL;
231
0
         psz_key = xml_ReaderNextAttr( reader, &psz_value ) )
232
0
    {
233
0
        char *psz_val = strdup( psz_value );
234
0
        if( psz_val )
235
0
        {
236
0
            vlc_dictionary_insert( &p_node->attr_dict, psz_key, psz_val );
237
238
0
            if( !strcasecmp( psz_key, "begin" ) )
239
0
                p_node->timings.begin = tt_ParseTime( psz_val );
240
0
            else if( ! strcasecmp( psz_key, "end" ) )
241
0
                p_node->timings.end = tt_ParseTime( psz_val );
242
0
            else if( ! strcasecmp( psz_key, "dur" ) )
243
0
                p_node->timings.dur = tt_ParseTime( psz_val );
244
0
            else if( ! strcasecmp( psz_key, "timeContainer" ) )
245
0
                p_node->timings.i_type = strcmp( psz_val, "seq" ) ? TT_TIMINGS_PARALLEL
246
0
                                                                  : TT_TIMINGS_SEQUENTIAL;
247
0
        }
248
0
    }
249
0
    return p_node;
250
0
}
251
#if 0
252
static int tt_node_Skip( xml_reader_t *p_reader, const char *psz_skipped )
253
{
254
    size_t i_depth = 1;
255
    const char *psz_cur;
256
257
    /* need a copy as psz_skipped would point to next node after NextNode */
258
    char *psz_skip = strdup( psz_skipped );
259
    if(!psz_skip)
260
        return VLC_EGENERIC;
261
262
    for( ;; )
263
    {
264
        int i_type = xml_ReaderNextNode( p_reader, &psz_cur );
265
        switch( i_type )
266
        {
267
            case XML_READER_STARTELEM:
268
                if( i_depth == SIZE_MAX )
269
                {
270
                    free( psz_skip );
271
                    return VLC_EGENERIC;
272
                }
273
                if( !xml_ReaderIsEmptyElement( p_reader ) )
274
                    i_depth++;
275
                break;
276
277
            case XML_READER_ENDELEM:
278
                if( !strcmp( psz_cur, psz_skip ) )
279
                {
280
                    free( psz_skip );
281
                    if( i_depth != 1 )
282
                        return VLC_EGENERIC;
283
                    return VLC_SUCCESS;
284
                }
285
                else
286
                {
287
                    if( i_depth == 1 )
288
                    {
289
                        free( psz_skip );
290
                        return VLC_EGENERIC;
291
                    }
292
                    i_depth--;
293
                }
294
                break;
295
296
            default:
297
                if( i_type <= XML_READER_NONE )
298
                {
299
                    free( psz_skip );
300
                    return VLC_EGENERIC;
301
                }
302
                break;
303
        }
304
    }
305
    vlc_assert_unreachable();
306
    return VLC_EGENERIC;
307
}
308
#endif
309
int tt_nodes_Read( xml_reader_t *p_reader, tt_node_t *p_root_node )
310
0
{
311
0
    size_t i_depth = 0;
312
0
    tt_node_t *p_node = p_root_node;
313
314
0
    do
315
0
    {
316
0
        const char* psz_node_name;
317
0
        int i_type = xml_ReaderNextNode( p_reader, &psz_node_name );
318
        /* !warn read empty state now as attributes reading will **** it up */
319
0
        bool b_empty = xml_ReaderIsEmptyElement( p_reader );
320
321
0
        if( i_type <= XML_READER_NONE )
322
0
            break;
323
324
0
        switch( i_type )
325
0
        {
326
0
            default:
327
0
                break;
328
329
0
            case XML_READER_STARTELEM:
330
0
            {
331
0
                tt_node_t *p_newnode = tt_node_New( p_reader, p_node, psz_node_name );
332
0
                if( !p_newnode )
333
0
                    return VLC_EGENERIC;
334
0
                if( !b_empty )
335
0
                {
336
0
                    p_node = p_newnode;
337
0
                    i_depth++;
338
0
                }
339
0
                break;
340
0
            }
341
342
0
            case XML_READER_TEXT:
343
0
            {
344
0
                tt_textnode_t *p_textnode = tt_textnode_New( p_node, psz_node_name );
345
0
                VLC_UNUSED(p_textnode);
346
0
            }
347
0
            break;
348
349
0
            case XML_READER_ENDELEM:
350
0
            {
351
0
                if( strcmp( psz_node_name, p_node->psz_node_name ) )
352
0
                    return VLC_EGENERIC;
353
354
0
                if( i_depth == 0 )
355
0
                {
356
0
                    if( p_node != p_root_node )
357
0
                        return VLC_EGENERIC;
358
0
                    break; /* END */
359
0
                }
360
0
                i_depth--;
361
0
                p_node = p_node->p_parent;
362
0
                break;
363
0
            }
364
0
        }
365
0
    } while( 1 );
366
367
0
    return VLC_SUCCESS;
368
0
}
369
370
371
/* Timings storage */
372
static int tt_bsearch_searchkey_Compare( const void *key, const void *other )
373
0
{
374
0
    struct tt_searchkey *p_key = (struct tt_searchkey *) key;
375
0
    tt_time_t time = *((tt_time_t *) other);
376
0
    p_key->p_last = (tt_time_t *) other;
377
0
    return tt_time_Compare( &p_key->time, &time );
378
0
}
379
380
size_t tt_timings_FindLowerIndex( const tt_time_t *p_times, size_t i_times, tt_time_t time, bool *pb_found )
381
0
{
382
0
    size_t i_index = 0;
383
0
    if( p_times )
384
0
    {
385
0
        struct tt_searchkey key;
386
0
        key.time = time;
387
0
        key.p_last = NULL;
388
389
0
        tt_time_t *lookup = bsearch( &key, p_times, i_times,
390
0
                                     sizeof(tt_time_t), tt_bsearch_searchkey_Compare );
391
0
        if( lookup )
392
0
            key.p_last = lookup;
393
0
        *pb_found = !!lookup;
394
395
        /* Compute index from last visited */
396
0
        i_index = (key.p_last - p_times);
397
0
        if( tt_time_Compare( &p_times[i_index], &time ) < 0 )
398
0
            i_index++;
399
0
    }
400
0
    else *pb_found = false;
401
0
    return i_index;
402
0
}
403
404
static void tt_bsearch_Insert( tt_time_t **pp_times, size_t *pi_times, tt_time_t time )
405
0
{
406
0
    bool b_exists;
407
0
    size_t i_index = tt_timings_FindLowerIndex( *pp_times, *pi_times, time, &b_exists );
408
0
    if( b_exists )
409
0
        return;
410
411
0
    if( SIZE_MAX / sizeof(tt_time_t) < (*pi_times + 1) )
412
0
        return;
413
414
0
    tt_time_t *p_array = realloc( *pp_times, (*pi_times + 1) * sizeof(tt_time_t) );
415
0
    if( !p_array )
416
0
        return;
417
0
    *pp_times = p_array;
418
419
0
    if( *pi_times > 0 )
420
0
    {
421
0
        memmove( &p_array[i_index + 1],
422
0
                 &p_array[i_index],
423
0
                 (*pi_times - i_index) * sizeof(tt_time_t) );
424
0
    }
425
426
0
    p_array[i_index] = time;
427
0
    *pi_times += 1;
428
0
}
429
430
431
/* Timings storage */
432
static void tt_timings_MergeParallel( const tt_timings_t *p_ref, tt_timings_t *p_local )
433
0
{
434
0
    if( tt_time_Valid( &p_local->begin ) )
435
0
        p_local->begin = tt_time_Add( p_local->begin, p_ref->begin );
436
0
    else
437
0
        p_local->begin = p_ref->begin;
438
439
0
    if( tt_time_Valid( &p_local->end ) )
440
0
    {
441
0
        p_local->end = tt_time_Add( p_local->end, p_ref->begin );
442
0
    }
443
0
    else if( tt_time_Valid( &p_local->dur ) && tt_time_Valid( &p_local->begin ) )
444
0
    {
445
0
        p_local->end = tt_time_Add( p_local->begin, p_local->dur );
446
0
    }
447
0
    else p_local->end = p_ref->end;
448
449
    /* Enforce contained duration */
450
451
0
    if( tt_time_Valid( &p_ref->end ) && tt_time_Compare( &p_local->end, &p_ref->end ) > 0 )
452
0
        p_local->end = p_ref->end;
453
454
    /* Just for consistency */
455
0
    if( tt_time_Valid( &p_local->begin ) && tt_time_Valid( &p_local->end ) )
456
0
        p_local->dur = tt_time_Sub( p_local->end, p_local->begin );
457
0
}
458
459
static void tt_timings_MergeSequential( const tt_timings_t *p_restrict,
460
                                       const tt_timings_t *p_prevref, tt_timings_t *p_local )
461
0
{
462
0
    if( tt_time_Valid( &p_local->begin ) )
463
0
        p_local->begin = tt_time_Add( p_local->begin, p_prevref->end );
464
0
    else
465
0
        p_local->begin = p_prevref->end;
466
467
0
    if( tt_time_Valid( &p_local->end ) )
468
0
    {
469
0
        p_local->end = tt_time_Add( p_local->end, p_prevref->end );
470
0
    }
471
0
    else if( tt_time_Valid( &p_local->dur ) && tt_time_Valid( &p_local->begin ) )
472
0
    {
473
0
        p_local->end = tt_time_Add( p_local->begin, p_local->dur );
474
0
    }
475
476
    /* Enforce contained duration */
477
0
    if( tt_time_Valid( &p_restrict->end ) && tt_time_Compare( &p_local->end, &p_restrict->end ) > 0 )
478
0
        p_local->end = p_restrict->end;
479
480
    /* Just for consistency */
481
0
    if( tt_time_Valid( &p_local->begin ) && tt_time_Valid( &p_local->end ) )
482
0
        p_local->dur = tt_time_Sub( p_local->end, p_local->begin );
483
0
}
484
485
void tt_timings_Resolve( tt_basenode_t *p_child, const tt_timings_t *p_container_timings,
486
                         tt_time_t **pp_array, size_t *pi_count )
487
0
{
488
0
    const tt_node_t *p_prevnode = NULL;
489
0
    for(  ; p_child; p_child = p_child->p_next )
490
0
    {
491
0
        if( p_child->i_type != TT_NODE_TYPE_ELEMENT )
492
0
            continue;
493
494
0
        tt_node_t *p_childnode = (tt_node_t *) p_child;
495
0
        if( p_container_timings->i_type == TT_TIMINGS_SEQUENTIAL )
496
0
        {
497
0
            if( p_prevnode == NULL ) /* First */
498
0
                tt_timings_MergeParallel( p_container_timings, &p_childnode->timings );
499
0
            else
500
0
                tt_timings_MergeSequential( p_container_timings,
501
0
                                        &p_prevnode->timings, &p_childnode->timings );
502
0
        }
503
0
        else
504
0
        {
505
0
            tt_timings_MergeParallel( p_container_timings, &p_childnode->timings );
506
0
        }
507
508
0
        if( tt_time_Valid( &p_childnode->timings.begin ) )
509
0
            tt_bsearch_Insert( pp_array, pi_count, p_childnode->timings.begin );
510
511
0
        if( tt_time_Valid( &p_childnode->timings.end ) )
512
0
            tt_bsearch_Insert( pp_array, pi_count, p_childnode->timings.end );
513
514
0
        p_prevnode = p_childnode;
515
516
0
        tt_timings_Resolve( p_childnode->p_child, &p_childnode->timings,
517
0
                            pp_array, pi_count );
518
0
    }
519
0
}