Coverage Report

Created: 2026-06-09 09:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/demux/ttml.c
Line
Count
Source
1
/*****************************************************************************
2
 * ttml.c : TTML subtitles demux
3
 *****************************************************************************
4
 * Copyright (C) 2015-2017 VLC authors and VideoLAN
5
 *
6
 * Authors: Hugo Beauzée-Luyssen <hugo@beauzee.fr>
7
 *          Sushma Reddy <sushma.reddy@research.iiit.ac.in>
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
#ifdef HAVE_CONFIG_H
25
# include "config.h"
26
#endif
27
28
#include <vlc_common.h>
29
#include <vlc_demux.h>
30
#include <vlc_xml.h>
31
#include <vlc_strings.h>
32
#include <vlc_memstream.h>
33
#include <vlc_es_out.h>
34
#include <vlc_charset.h>          /* FromCharset */
35
36
#include <assert.h>
37
#include <stdlib.h>
38
#include <ctype.h>
39
40
#include "../codec/ttml/ttml.h"
41
42
//#define TTML_DEMUX_DEBUG
43
44
typedef struct
45
{
46
    xml_t*          p_xml;
47
    xml_reader_t*   p_reader;
48
    es_out_id_t*    p_es;
49
    vlc_tick_t      i_next_demux_time;
50
    bool            b_slave;
51
    bool            b_first_time;
52
53
    tt_node_t         *p_rootnode;
54
    tt_namespaces_t   namespaces;
55
56
    tt_timings_t    temporal_extent;
57
58
    /*
59
     * All timings are stored unique and ordered.
60
     * Being begin or end times of sub sequence,
61
     * we use them as 'point of change' for output filtering.
62
    */
63
    struct
64
    {
65
        tt_time_t *p_array;
66
        size_t   i_count;
67
        size_t   i_current;
68
    } times;
69
} demux_sys_t;
70
71
static int Control( demux_t* p_demux, int i_query, va_list args )
72
0
{
73
0
    demux_sys_t *p_sys = p_demux->p_sys;
74
0
    vlc_tick_t i64;
75
0
    double *pf, f;
76
0
    bool b;
77
78
0
    switch( i_query )
79
0
    {
80
0
        case DEMUX_CAN_SEEK:
81
0
            *va_arg( args, bool * ) = true;
82
0
            return VLC_SUCCESS;
83
0
        case DEMUX_GET_TIME:
84
0
            *va_arg( args, vlc_tick_t * ) = p_sys->i_next_demux_time;
85
0
            return VLC_SUCCESS;
86
0
        case DEMUX_SET_TIME:
87
0
            if( p_sys->times.i_count )
88
0
            {
89
0
                tt_time_t t = tt_time_Create( va_arg( args, vlc_tick_t ) - VLC_TICK_0 );
90
0
                size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
91
0
                                                            p_sys->times.i_count, t, &b );
92
0
                p_sys->times.i_current = i_index;
93
0
                p_sys->b_first_time = true;
94
0
                return VLC_SUCCESS;
95
0
            }
96
0
            break;
97
0
        case DEMUX_SET_NEXT_DEMUX_TIME:
98
0
            p_sys->i_next_demux_time = va_arg( args, vlc_tick_t );
99
0
            p_sys->b_slave = true;
100
0
            return VLC_SUCCESS;
101
0
        case DEMUX_GET_LENGTH:
102
0
            if( p_sys->times.i_count )
103
0
            {
104
0
                tt_time_t t = tt_time_Sub( p_sys->times.p_array[p_sys->times.i_count - 1],
105
0
                                           p_sys->temporal_extent.begin );
106
0
                *va_arg( args, vlc_tick_t * ) = tt_time_Convert( &t );
107
0
                return VLC_SUCCESS;
108
0
            }
109
0
            break;
110
0
        case DEMUX_GET_POSITION:
111
0
            pf = va_arg( args, double * );
112
0
            if( p_sys->times.i_current >= p_sys->times.i_count )
113
0
            {
114
0
                *pf = 1.0;
115
0
            }
116
0
            else if( p_sys->times.i_count > 0 )
117
0
            {
118
0
                i64 = tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
119
0
                *pf = (double) p_sys->i_next_demux_time / (i64 + VLC_TICK_FROM_MS(500));
120
0
            }
121
0
            else
122
0
            {
123
0
                *pf = 0.0;
124
0
            }
125
0
            return VLC_SUCCESS;
126
0
        case DEMUX_SET_POSITION:
127
0
            f = va_arg( args, double );
128
0
            if( p_sys->times.i_count )
129
0
            {
130
0
                i64 = f * tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_count - 1] );
131
0
                tt_time_t t = tt_time_Create( i64 );
132
0
                size_t i_index = tt_timings_FindLowerIndex( p_sys->times.p_array,
133
0
                                                            p_sys->times.i_count, t, &b );
134
0
                p_sys->times.i_current = i_index;
135
0
                p_sys->b_first_time = true;
136
0
                return VLC_SUCCESS;
137
0
            }
138
0
            break;
139
0
        case DEMUX_CAN_PAUSE:
140
0
        case DEMUX_SET_PAUSE_STATE:
141
0
        case DEMUX_CAN_CONTROL_PACE:
142
0
            return demux_vaControlHelper( p_demux->s, 0, -1, 0, 1, i_query, args );
143
144
0
        case DEMUX_GET_PTS_DELAY:
145
0
        case DEMUX_GET_FPS:
146
0
        case DEMUX_GET_META:
147
0
        case DEMUX_GET_ATTACHMENTS:
148
0
        case DEMUX_GET_TITLE_INFO:
149
0
        case DEMUX_HAS_UNSUPPORTED_META:
150
0
        case DEMUX_CAN_RECORD:
151
0
        default:
152
0
            break;
153
0
    }
154
155
0
    return VLC_EGENERIC;
156
0
}
157
158
static int ReadTTML( demux_t* p_demux )
159
1.95k
{
160
1.95k
    demux_sys_t* p_sys = p_demux->p_sys;
161
1.95k
    const char* psz_node_name, *psz_node_namespace;
162
163
1.95k
    do
164
3.89k
    {
165
3.89k
        int i_type = xml_ReaderNextNodeNS( p_sys->p_reader, &psz_node_name, &psz_node_namespace );
166
3.89k
        bool b_empty = xml_ReaderIsEmptyElement( p_sys->p_reader );
167
168
3.89k
        if( i_type <= XML_READER_NONE )
169
1.94k
            break;
170
171
1.94k
        switch(i_type)
172
1.94k
        {
173
0
            default:
174
0
                break;
175
176
1.94k
            case XML_READER_STARTELEM:
177
1.94k
                if( (psz_node_namespace && strcmp( psz_node_namespace, TT_NS )) ||
178
1.94k
                    strcmp( tt_LocalName( psz_node_name ), "tt" ) ||
179
1.94k
                    p_sys->p_rootnode != NULL )
180
8
                    return VLC_EGENERIC;
181
182
1.94k
                p_sys->p_rootnode = tt_node_NewRead( p_sys->p_reader, &p_sys->namespaces, NULL,
183
1.94k
                                                     psz_node_name,
184
1.94k
                                                     psz_node_namespace );
185
1.94k
                if( b_empty )
186
0
                    break;
187
1.94k
                if( !p_sys->p_rootnode ||
188
1.94k
                    tt_nodes_Read( p_sys->p_reader,
189
1.94k
                                  &p_sys->namespaces, p_sys->p_rootnode ) != VLC_SUCCESS )
190
0
                    return VLC_EGENERIC;
191
1.94k
                break;
192
193
1.94k
            case XML_READER_ENDELEM:
194
0
                if( !p_sys->p_rootnode ||
195
0
                    strcmp( psz_node_name, p_sys->p_rootnode->psz_node_name ) )
196
0
                    return VLC_EGENERIC;
197
0
                break;
198
1.94k
        }
199
200
1.94k
    } while( 1 );
201
202
1.94k
    if( p_sys->p_rootnode == NULL )
203
1
        return VLC_EGENERIC;
204
205
1.94k
    return VLC_SUCCESS;
206
1.94k
}
207
208
static int Demux( demux_t* p_demux )
209
245M
{
210
245M
    demux_sys_t* p_sys = p_demux->p_sys;
211
212
    /* Last one must be an end time */
213
245M
    while( p_sys->times.i_current + 1 < p_sys->times.i_count &&
214
245M
           tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] ) <= p_sys->i_next_demux_time )
215
21.2k
    {
216
21.2k
        const vlc_tick_t i_playbacktime =
217
21.2k
                tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current] );
218
21.2k
        const vlc_tick_t i_playbackendtime =
219
21.2k
                tt_time_Convert( &p_sys->times.p_array[p_sys->times.i_current + 1] ) - 1;
220
221
21.2k
        if ( !p_sys->b_slave && p_sys->b_first_time )
222
1.92k
        {
223
1.92k
            es_out_SetPCR( p_demux->out, VLC_TICK_0 + i_playbacktime );
224
1.92k
            p_sys->b_first_time = false;
225
1.92k
        }
226
227
21.2k
        struct vlc_memstream stream;
228
229
21.2k
        if( vlc_memstream_open( &stream ) )
230
0
            return VLC_DEMUXER_EGENERIC;
231
232
21.2k
        tt_node_ToText( &stream, (tt_basenode_t *) p_sys->p_rootnode,
233
21.2k
                        &p_sys->times.p_array[p_sys->times.i_current] );
234
235
21.2k
        if( vlc_memstream_close( &stream ) == 0 )
236
21.2k
        {
237
21.2k
            block_t* p_block = block_heap_Alloc( stream.ptr, stream.length );
238
21.2k
            if( p_block )
239
21.2k
            {
240
21.2k
                p_block->i_dts =
241
21.2k
                    p_block->i_pts = VLC_TICK_0 + i_playbacktime;
242
21.2k
                p_block->i_length = i_playbackendtime - i_playbacktime;
243
244
21.2k
                es_out_Send( p_demux->out, p_sys->p_es, p_block );
245
21.2k
            }
246
21.2k
        }
247
248
21.2k
        p_sys->times.i_current++;
249
21.2k
    }
250
251
245M
    if ( !p_sys->b_slave )
252
245M
    {
253
245M
        es_out_SetPCR( p_demux->out, VLC_TICK_0 + p_sys->i_next_demux_time );
254
245M
        p_sys->i_next_demux_time += VLC_TICK_FROM_MS(125);
255
245M
    }
256
257
245M
    if( p_sys->times.i_current + 1 >= p_sys->times.i_count )
258
1.94k
        return VLC_DEMUXER_EOF;
259
260
245M
    return VLC_DEMUXER_SUCCESS;
261
245M
}
262
263
int tt_OpenDemux( vlc_object_t* p_this )
264
2.43k
{
265
2.43k
    demux_t     *p_demux = (demux_t*)p_this;
266
2.43k
    demux_sys_t *p_sys;
267
268
2.43k
    const uint8_t *p_peek;
269
2.43k
    ssize_t i_peek = vlc_stream_Peek( p_demux->s, &p_peek, 2048 );
270
2.43k
    if( unlikely( i_peek <= 32 ) )
271
0
        return VLC_EGENERIC;
272
273
2.43k
    const char *psz_xml = (const char *) p_peek;
274
2.43k
    size_t i_xml  = i_peek;
275
276
    /* Try to probe without xml module/loading the full document */
277
2.43k
    char *psz_alloc = NULL;
278
2.43k
    switch( GetQWBE(p_peek) )
279
2.43k
    {
280
        /* See RFC 3023 Part 4 */
281
0
        case UINT64_C(0xFFFE3C003F007800): /* UTF16 BOM<? */
282
0
        case UINT64_C(0xFFFE3C003F007400): /* UTF16 BOM<t */
283
0
        case UINT64_C(0xFEFF003C003F0078): /* UTF16 BOM<? */
284
0
        case UINT64_C(0xFEFF003C003F0074): /* UTF16 BOM<t */
285
0
            psz_alloc = FromCharset( "UTF-16", p_peek, i_peek );
286
0
            break;
287
0
        case UINT64_C(0x3C003F0078006D00): /* UTF16-LE <?xm */
288
8
        case UINT64_C(0x3C003F0074007400): /* UTF16-LE <tt */
289
8
            psz_alloc = FromCharset( "UTF-16LE", p_peek, i_peek );
290
8
            break;
291
0
        case UINT64_C(0x003C003F0078006D): /* UTF16-BE <?xm */
292
2
        case UINT64_C(0x003C003F00740074): /* UTF16-BE <tt */
293
2
            psz_alloc = FromCharset( "UTF-16BE", p_peek, i_peek );
294
2
            break;
295
0
        case UINT64_C(0xEFBBBF3C3F786D6C): /* UTF8 BOM<?xml */
296
1.41k
        case UINT64_C(0x3C3F786D6C207665): /* UTF8 <?xml ve */
297
1.41k
        case UINT64_C(0xEFBBBF3C74742078): /* UTF8 BOM<tt x*/
298
1.41k
            break;
299
1.00k
        default:
300
1.00k
            if(GetDWBE(p_peek) != UINT32_C(0x3C747420)) /* tt node without xml document marker */
301
472
                return VLC_EGENERIC;
302
2.43k
    }
303
304
1.96k
    if( psz_alloc )
305
10
    {
306
10
        psz_xml = psz_alloc;
307
10
        i_xml = strlen( psz_alloc );
308
10
    }
309
310
    /* Simplified probing. Valid TTML must have a namespace declaration */
311
1.96k
    const char *psz_tt = strnstr( psz_xml, "tt", i_xml );
312
1.96k
    if( !psz_tt || psz_tt == psz_xml ||
313
1.96k
        ((size_t)(&psz_tt[2] - (const char*)p_peek)) == i_xml || isalpha(psz_tt[2]) ||
314
1.96k
        (psz_tt[-1] != ':' && psz_tt[-1] != '<') )
315
10
    {
316
10
        free( psz_alloc );
317
10
        return VLC_EGENERIC;
318
10
    }
319
1.95k
    else
320
1.95k
    {
321
1.95k
        const char * const rgsz[] =
322
1.95k
        {
323
1.95k
            "=\"http://www.w3.org/ns/ttml\"",
324
1.95k
            "=\"http://www.w3.org/2004/11/ttaf1\"",
325
1.95k
            "=\"http://www.w3.org/2006/04/ttaf1\"",
326
1.95k
            "=\"http://www.w3.org/2006/10/ttaf1\"",
327
1.95k
        };
328
1.95k
        const char *psz_ns = NULL;
329
3.93k
        for( size_t i=0; i<ARRAY_SIZE(rgsz) && !psz_ns; i++ )
330
1.98k
        {
331
1.98k
            psz_ns = strnstr( psz_xml, rgsz[i],
332
1.98k
                              i_xml - (psz_tt - psz_xml) );
333
1.98k
        }
334
1.95k
        free( psz_alloc );
335
1.95k
        if( !psz_ns )
336
1
            return VLC_EGENERIC;
337
1.95k
    }
338
339
1.95k
    p_demux->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
340
1.95k
    if( unlikely( p_sys == NULL ) )
341
0
        return VLC_ENOMEM;
342
343
1.95k
    p_sys->b_first_time = true;
344
1.95k
    p_sys->temporal_extent.i_type = TT_TIMINGS_PARALLEL;
345
1.95k
    tt_time_Init( &p_sys->temporal_extent.begin );
346
1.95k
    tt_time_Init( &p_sys->temporal_extent.end );
347
1.95k
    tt_time_Init( &p_sys->temporal_extent.dur );
348
1.95k
    p_sys->temporal_extent.begin.base = 0;
349
1.95k
    tt_namespaces_Init( &p_sys->namespaces );
350
351
1.95k
    p_sys->p_xml = xml_Create( p_demux );
352
1.95k
    if( !p_sys->p_xml )
353
0
        goto error;
354
355
1.95k
    p_sys->p_reader = xml_ReaderCreate( p_sys->p_xml, p_demux->s );
356
1.95k
    if( !p_sys->p_reader )
357
0
        goto error;
358
359
1.95k
#ifndef TTML_DEMUX_DEBUG
360
1.95k
    p_sys->p_reader->obj.logger = NULL;
361
1.95k
#endif
362
363
1.95k
    if( ReadTTML( p_demux ) != VLC_SUCCESS )
364
9
        goto error;
365
366
1.94k
    tt_timings_Resolve( (tt_basenode_t *) p_sys->p_rootnode, &p_sys->temporal_extent,
367
1.94k
                        &p_sys->times.p_array, &p_sys->times.i_count );
368
369
#ifdef TTML_DEMUX_DEBUG
370
    {
371
        struct vlc_memstream stream;
372
373
        if( vlc_memstream_open( &stream ) )
374
            goto error;
375
376
        tt_time_t t;
377
        tt_time_Init( &t );
378
        tt_node_ToText( &stream, (tt_basenode_t*)p_sys->p_rootnode, &t /* invalid */ );
379
380
        vlc_memstream_putc( &stream, '\0' );
381
382
        if( vlc_memstream_close( &stream ) == 0 )
383
        {
384
            msg_Dbg( p_demux, "%s", stream.ptr );
385
            free( stream.ptr );
386
        }
387
    }
388
#endif
389
390
1.94k
    p_demux->pf_demux = Demux;
391
1.94k
    p_demux->pf_control = Control;
392
393
1.94k
    es_format_t fmt;
394
1.94k
    es_format_Init( &fmt, SPU_ES, VLC_CODEC_TTML );
395
1.94k
    fmt.i_id = 0;
396
1.94k
    p_sys->p_es = es_out_Add( p_demux->out, &fmt );
397
1.94k
    if( !p_sys->p_es )
398
0
        goto error;
399
400
1.94k
    es_format_Clean( &fmt );
401
402
1.94k
    return VLC_SUCCESS;
403
404
9
error:
405
9
    tt_CloseDemux( p_this );
406
407
9
    return VLC_EGENERIC;
408
1.94k
}
409
410
void tt_CloseDemux( vlc_object_t* p_this )
411
1.95k
{
412
1.95k
    demux_t *p_demux = (demux_t *)p_this;
413
1.95k
    demux_sys_t* p_sys = p_demux->p_sys;
414
415
1.95k
    if( p_sys->p_rootnode )
416
1.94k
        tt_node_RecursiveDelete( p_sys->p_rootnode );
417
418
1.95k
    if( p_sys->p_es )
419
1.94k
        es_out_Del( p_demux->out, p_sys->p_es );
420
421
1.95k
    if( p_sys->p_reader )
422
1.95k
        xml_ReaderDelete( p_sys->p_reader );
423
424
1.95k
    if( p_sys->p_xml )
425
1.95k
        xml_Delete( p_sys->p_xml );
426
427
1.95k
    tt_namespaces_Clean( &p_sys->namespaces );
428
429
1.95k
    free( p_sys->times.p_array );
430
431
1.95k
    free( p_sys );
432
1.95k
}