Coverage Report

Created: 2025-10-12 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/vlc/modules/codec/webvtt/subsvtt.c
Line
Count
Source
1
/*****************************************************************************
2
 * subsvtt.c: Decoder for WEBVTT as ISO1446-30 payload
3
 *****************************************************************************
4
 * Copyright (C) 2017 VideoLabs, 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
21
/*****************************************************************************
22
 * Preamble
23
 *****************************************************************************/
24
25
#ifdef HAVE_CONFIG_H
26
# include "config.h"
27
#endif
28
29
#include <vlc_common.h>
30
#include <vlc_arrays.h>
31
#include <vlc_charset.h>
32
#include <vlc_subpicture.h>
33
#include <vlc_codec.h>
34
#include <vlc_stream.h>
35
#include <vlc_memstream.h>
36
#include <assert.h>
37
38
#include "../substext.h"
39
#include "../../demux/mp4/minibox.h"
40
#include "webvtt.h"
41
42
43
#ifdef HAVE_CSS
44
#  include "css_parser.h"
45
#  include "css_style.h"
46
#endif
47
48
#include <ctype.h>
49
50
//#define SUBSVTT_DEBUG
51
52
// maximum recursions in GetTimedTags()
53
173k
#define MAX_TIMED_TAGS_RECURSION           50
54
// maximum recursions in ConvertNodesToSegments()
55
228k
#define MAX_TIMED_NODE_SEGMENTS_RECURSION  50
56
57
/*****************************************************************************
58
 * Local prototypes
59
 *****************************************************************************/
60
61
typedef struct webvtt_region_t webvtt_region_t;
62
typedef struct webvtt_dom_node_t webvtt_dom_node_t;
63
typedef struct webvtt_dom_cue_t webvtt_dom_cue_t;
64
65
16.8k
#define WEBVTT_REGION_LINES_COUNT          18
66
29.2k
#define WEBVTT_DEFAULT_LINE_HEIGHT_VH    5.33f
67
19.7k
#define WEBVTT_LINE_TO_HEIGHT_RATIO      1.06f
68
110k
#define WEBVTT_MAX_DEPTH                 20 /* recursion prevention for now */
69
70
enum webvtt_align_e
71
{
72
    WEBVTT_ALIGN_AUTO,
73
    WEBVTT_ALIGN_LEFT,
74
    WEBVTT_ALIGN_CENTER,
75
    WEBVTT_ALIGN_RIGHT,
76
    WEBVTT_ALIGN_START,
77
    WEBVTT_ALIGN_END,
78
};
79
80
typedef struct
81
{
82
    float value;
83
    bool b_auto;
84
} webvtt_auto_value_t;
85
86
typedef struct
87
{
88
    char *psz_region;
89
    enum webvtt_align_e vertical;
90
    bool b_snap_to_lines;
91
    webvtt_auto_value_t line;
92
    enum webvtt_align_e linealign;
93
    float position;
94
    enum webvtt_align_e positionalign;
95
    webvtt_auto_value_t size;
96
    enum webvtt_align_e align;
97
} webvtt_cue_settings_t;
98
99
enum webvtt_node_type_e
100
{
101
    NODE_TAG,
102
    NODE_TEXT,
103
    NODE_CUE,
104
    NODE_REGION,
105
};
106
107
#define WEBVTT_NODE_BASE_MEMBERS \
108
    enum webvtt_node_type_e type;\
109
    webvtt_dom_node_t *p_parent;\
110
    webvtt_dom_node_t *p_next;
111
112
struct webvtt_region_t
113
{
114
    WEBVTT_NODE_BASE_MEMBERS
115
    char *psz_id;
116
    float f_width;
117
    unsigned i_lines_max_scroll;
118
    float anchor_x;
119
    float anchor_y;
120
    float viewport_anchor_x;
121
    float viewport_anchor_y;
122
    bool b_scroll_up;
123
    text_style_t *p_cssstyle;
124
    webvtt_dom_node_t *p_child;
125
};
126
127
struct webvtt_dom_cue_t
128
{
129
    WEBVTT_NODE_BASE_MEMBERS
130
    char *psz_id;
131
    vlc_tick_t i_nzstart;
132
    vlc_tick_t i_nzstop;
133
    webvtt_cue_settings_t settings;
134
    unsigned i_lines;
135
    text_style_t *p_cssstyle;
136
    webvtt_dom_node_t *p_child;
137
};
138
139
typedef struct
140
{
141
    WEBVTT_NODE_BASE_MEMBERS
142
    char *psz_text;
143
} webvtt_dom_text_t;
144
145
typedef struct
146
{
147
    WEBVTT_NODE_BASE_MEMBERS
148
    vlc_tick_t i_nzstart;
149
    char *psz_tag;
150
    char *psz_attrs;
151
    text_style_t *p_cssstyle;
152
    webvtt_dom_node_t *p_child;
153
} webvtt_dom_tag_t;
154
155
struct webvtt_dom_node_t
156
{
157
    WEBVTT_NODE_BASE_MEMBERS
158
};
159
160
typedef struct
161
{
162
    webvtt_dom_tag_t *p_root;
163
#ifdef HAVE_CSS
164
    /* CSS */
165
    vlc_css_rule_t *p_css_rules;
166
#endif
167
} decoder_sys_t;
168
169
/*****************************************************************************
170
 *
171
 *****************************************************************************/
172
173
static bool parse_percent( const char *psz, float *value )
174
14.3k
{
175
14.3k
    char *psz_end;
176
14.3k
    float d = vlc_strtof_c( psz, &psz_end );
177
14.3k
    if( d >= 0.0 && d <= 100.0 && *psz_end == '%' )
178
3.46k
        *value = d / 100.0;
179
14.3k
    return psz_end != psz;
180
14.3k
}
181
182
static bool parse_percent_tuple( const char *psz, float *x, float *y )
183
3.84k
{
184
3.84k
    char *psz_end;
185
3.84k
    float a = vlc_strtof_c( psz, &psz_end );
186
3.84k
    if( psz_end != psz &&
187
3.58k
        a >= 0.0 && a <= 100.0 && psz_end && *psz_end == '%' )
188
2.57k
    {
189
2.57k
        psz = strchr( psz_end, ',' );
190
2.57k
        if( psz )
191
2.33k
        {
192
2.33k
            float b = vlc_strtof_c( ++psz, &psz_end );
193
2.33k
            if( psz_end != psz &&
194
1.95k
                b >= 0.0 && b <= 100.0 && psz_end && *psz_end == '%' )
195
851
            {
196
851
                *x = a / 100.0;
197
851
                *y = b / 100.0;
198
851
                return true;
199
851
            }
200
2.33k
        }
201
2.57k
    }
202
2.99k
    return false;
203
3.84k
}
204
205
typedef struct
206
{
207
    float x,y,w,h;
208
} webvtt_rect_t;
209
210
static void webvtt_get_cueboxrect( const webvtt_cue_settings_t *p_settings,
211
                                   webvtt_rect_t *p_rect )
212
3.27k
{
213
3.27k
    float extent;
214
3.27k
    float indent_anchor_position;
215
3.27k
    enum webvtt_align_e alignment_on_indent_anchor;
216
217
    /* Position of top or left depending on writing direction */
218
3.27k
    float line_offset;
219
3.27k
    if( !p_settings->line.b_auto ) /* numerical */
220
3.27k
    {
221
3.27k
        if( p_settings->b_snap_to_lines ) /* line # */
222
1.34k
            line_offset = p_settings->line.value /
223
1.34k
                          (WEBVTT_REGION_LINES_COUNT * WEBVTT_LINE_TO_HEIGHT_RATIO);
224
1.92k
        else
225
1.92k
            line_offset = p_settings->line.value;
226
3.27k
    }
227
0
    else line_offset = 1.0;
228
229
3.27k
    if( p_settings->position < 0 )
230
2.77k
    {
231
2.77k
        if( p_settings->align == WEBVTT_ALIGN_LEFT )
232
210
            indent_anchor_position = 0;
233
2.56k
        else if( p_settings->align == WEBVTT_ALIGN_RIGHT )
234
116
            indent_anchor_position = 1.0;
235
2.44k
        else
236
2.44k
            indent_anchor_position = 0.5; /* center */
237
2.77k
    }
238
501
    else indent_anchor_position = p_settings->position;
239
240
3.27k
    if( p_settings->positionalign == WEBVTT_ALIGN_AUTO )
241
3.14k
    {
242
        /* text align */
243
3.14k
        if( p_settings->align == WEBVTT_ALIGN_LEFT ||
244
2.83k
            p_settings->align == WEBVTT_ALIGN_RIGHT )
245
430
            alignment_on_indent_anchor = p_settings->align;
246
2.71k
        else
247
2.71k
            alignment_on_indent_anchor = WEBVTT_ALIGN_CENTER;
248
3.14k
    }
249
127
    else alignment_on_indent_anchor = p_settings->positionalign;
250
251
3.27k
    if( !p_settings->size.b_auto )
252
653
        extent = p_settings->size.value;
253
2.62k
    else
254
2.62k
        extent = 0.0;
255
256
    /* apply */
257
258
    /* we need 100% or size for inner_align to work on writing direction */
259
3.27k
    if( p_settings->vertical == WEBVTT_ALIGN_AUTO ) /* Horizontal text */
260
2.87k
    {
261
2.87k
        p_rect->y = line_offset > 0 ? line_offset : 1.0 + line_offset;
262
2.87k
        p_rect->w = (extent) ? extent : 1.0;
263
2.87k
        if( indent_anchor_position > 0 &&
264
2.52k
            (alignment_on_indent_anchor == WEBVTT_ALIGN_LEFT ||
265
2.40k
             alignment_on_indent_anchor == WEBVTT_ALIGN_START) )
266
126
        {
267
126
            p_rect->x  = indent_anchor_position;
268
126
            p_rect->w -= p_rect->x;
269
126
        }
270
2.87k
    }
271
401
    else /* Vertical text */
272
401
    {
273
401
        if( p_settings->vertical == WEBVTT_ALIGN_LEFT )
274
167
            p_rect->x = line_offset > 0 ? 1.0 - line_offset : -line_offset;
275
234
        else
276
234
            p_rect->x = line_offset > 0 ? line_offset : 1.0 + line_offset;
277
401
        p_rect->y = (extent) ? extent : 1.0;
278
279
401
        if( indent_anchor_position > 0 &&
280
330
            alignment_on_indent_anchor == WEBVTT_ALIGN_START )
281
0
        {
282
0
            p_rect->y  = indent_anchor_position;
283
0
            p_rect->h -= p_rect->y;
284
0
        }
285
401
    }
286
3.27k
}
287
288
static void webvtt_cue_settings_ParseTuple( webvtt_cue_settings_t *p_settings,
289
                                            const char *psz_key, const char *psz_value )
290
92.1k
{
291
92.1k
    if( !strcmp( psz_key, "vertical" ) )
292
2.73k
    {
293
2.73k
        if( !strcmp( psz_value, "rl" ) )
294
1.43k
            p_settings->vertical = WEBVTT_ALIGN_RIGHT;
295
1.29k
        else if( !strcmp( psz_value, "lr" ) )
296
606
            p_settings->vertical = WEBVTT_ALIGN_LEFT;
297
691
        else
298
691
            p_settings->vertical = WEBVTT_ALIGN_AUTO;
299
2.73k
    }
300
89.4k
    else if( !strcmp( psz_key, "line" ) )
301
7.55k
    {
302
7.55k
        p_settings->line.b_auto = false;
303
7.55k
        if( strchr( psz_value, '%' ) )
304
4.72k
        {
305
4.72k
            parse_percent( psz_value, &p_settings->line.value );
306
4.72k
            p_settings->b_snap_to_lines = false;
307
4.72k
        }
308
2.82k
        else
309
2.82k
            p_settings->line.value = vlc_strtof_c( psz_value, NULL );
310
        /* else auto */
311
312
7.55k
        const char *psz_align = strchr( psz_value, ',' );
313
7.55k
        if( psz_align++ )
314
3.38k
        {
315
3.38k
            if( !strcmp( psz_align, "center" ) )
316
69
                p_settings->linealign = WEBVTT_ALIGN_CENTER;
317
3.31k
            else if( !strcmp( psz_align, "end" ) )
318
501
                p_settings->linealign = WEBVTT_ALIGN_END;
319
2.81k
            else
320
2.81k
                p_settings->linealign = WEBVTT_ALIGN_START;
321
3.38k
        }
322
7.55k
    }
323
81.9k
    else if( !strcmp( psz_key, "position" ) )
324
4.94k
    {
325
4.94k
        parse_percent( psz_value, &p_settings->position );
326
4.94k
        const char *psz_align = strchr( psz_value, ',' );
327
4.94k
        if( psz_align++ )
328
2.23k
        {
329
2.23k
            if( !strcmp( psz_align, "line-left" ) )
330
331
                p_settings->positionalign = WEBVTT_ALIGN_LEFT;
331
1.90k
            else if( !strcmp( psz_align, "line-right" ) )
332
82
                p_settings->positionalign = WEBVTT_ALIGN_RIGHT;
333
1.82k
            else if( !strcmp( psz_align, "center" ) )
334
619
                p_settings->positionalign = WEBVTT_ALIGN_CENTER;
335
1.20k
            else
336
1.20k
                p_settings->positionalign = WEBVTT_ALIGN_AUTO;
337
2.23k
        }
338
4.94k
    }
339
76.9k
    else if( !strcmp( psz_key, "size" ) )
340
2.51k
    {
341
2.51k
        parse_percent( psz_value, &p_settings->size.value );
342
2.51k
        p_settings->size.b_auto = false;
343
2.51k
    }
344
74.4k
    else if( !strcmp( psz_key, "region" ) )
345
21.7k
    {
346
21.7k
        free( p_settings->psz_region );
347
21.7k
        p_settings->psz_region = strdup( psz_value );
348
21.7k
    }
349
52.7k
    else if( !strcmp( psz_key, "align" ) )
350
14.8k
    {
351
14.8k
        if( !strcmp( psz_value, "start" ) )
352
961
            p_settings->align = WEBVTT_ALIGN_START;
353
13.9k
        else  if( !strcmp( psz_value, "end" ) )
354
530
            p_settings->align = WEBVTT_ALIGN_END;
355
13.3k
        else  if( !strcmp( psz_value, "left" ) )
356
10.2k
            p_settings->align = WEBVTT_ALIGN_LEFT;
357
3.16k
        else  if( !strcmp( psz_value, "right" ) )
358
1.37k
            p_settings->align = WEBVTT_ALIGN_RIGHT;
359
1.79k
        else
360
1.79k
            p_settings->align = WEBVTT_ALIGN_CENTER;
361
14.8k
    }
362
92.1k
}
363
364
static void webvtt_cue_settings_Parse( webvtt_cue_settings_t *p_settings,
365
                                       char *p_str )
366
148k
{
367
148k
    char *p_save;
368
148k
    char *psz_tuple;
369
148k
    do
370
399k
    {
371
399k
        psz_tuple = strtok_r( p_str, " ", &p_save );
372
399k
        p_str = NULL;
373
399k
        if( psz_tuple )
374
251k
        {
375
251k
            const char *psz_split = strchr( psz_tuple, ':' );
376
251k
            if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple )
377
92.1k
            {
378
92.1k
                char *psz_key = strndup( psz_tuple, psz_split - psz_tuple );
379
92.1k
                if( psz_key )
380
92.1k
                {
381
92.1k
                    webvtt_cue_settings_ParseTuple( p_settings, psz_key, psz_split + 1 );
382
92.1k
                    free( psz_key );
383
92.1k
                }
384
92.1k
            }
385
251k
        }
386
399k
    } while( psz_tuple );
387
148k
}
388
389
static void webvtt_cue_settings_Clean( webvtt_cue_settings_t *p_settings )
390
537k
{
391
537k
    free( p_settings->psz_region );
392
537k
}
393
394
static void webvtt_cue_settings_Init( webvtt_cue_settings_t *p_settings )
395
539k
{
396
539k
    p_settings->psz_region = NULL;
397
539k
    p_settings->vertical = WEBVTT_ALIGN_AUTO;
398
539k
    p_settings->b_snap_to_lines = true;
399
539k
    p_settings->line.b_auto = true;
400
539k
    p_settings->line.value = 1.0;
401
539k
    p_settings->linealign = WEBVTT_ALIGN_START;
402
539k
    p_settings->position = -1;
403
539k
    p_settings->positionalign = WEBVTT_ALIGN_AUTO;
404
539k
    p_settings->size.value = 1.0; /* 100% */
405
539k
    p_settings->size.b_auto = true;
406
539k
    p_settings->align = WEBVTT_ALIGN_CENTER;
407
539k
}
408
409
/*****************************************************************************
410
 *
411
 *****************************************************************************/
412
#ifdef SUBSVTT_DEBUG
413
static void webvtt_domnode_Debug( webvtt_dom_node_t *p_node, int i_depth )
414
{
415
    for( ; p_node ; p_node = p_node->p_next )
416
    {
417
        for( int i=0; i<i_depth; i++) printf(" ");
418
        if( p_node->type == NODE_TEXT )
419
        {
420
            printf("TEXT %s\n", ((webvtt_dom_text_t *)p_node)->psz_text );
421
        }
422
        else if( p_node->type == NODE_TAG )
423
        {
424
            webvtt_dom_tag_t *p_tag = (webvtt_dom_tag_t *)p_node;
425
            printf("TAG%s (%s)\n", p_tag->psz_tag, p_tag->psz_attrs );
426
            webvtt_domnode_Debug( p_tag->p_child, i_depth + 1 );
427
        }
428
        else if( p_node->type == NODE_CUE )
429
        {
430
            webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *)p_node;
431
            printf("CUE %s\n", p_cue->psz_id );
432
            webvtt_domnode_Debug( p_cue->p_child, i_depth + 1 );
433
        }
434
        else if( p_node->type == NODE_REGION )
435
        {
436
            webvtt_region_t *p_region = (webvtt_region_t *)p_node;
437
            printf("REGION %s\n", p_region->psz_id );
438
            webvtt_domnode_Debug( p_region->p_child, i_depth + 1 );
439
        }
440
    }
441
}
442
#define webvtt_domnode_Debug(a,b) webvtt_domnode_Debug((webvtt_dom_node_t *)a,b)
443
#endif
444
445
static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue );
446
447
static void webvtt_domnode_AppendLast( webvtt_dom_node_t **pp_append,
448
                                       webvtt_dom_node_t *p_node )
449
33.2k
{
450
120k
    while( *pp_append )
451
87.4k
        pp_append = &((*pp_append)->p_next);
452
33.2k
    *pp_append = p_node;
453
33.2k
}
454
455
#define webvtt_domnode_AppendLast( a, b ) \
456
33.2k
    webvtt_domnode_AppendLast( (webvtt_dom_node_t **) a, (webvtt_dom_node_t *) b )
457
458
459
static webvtt_dom_node_t *webvtt_domnode_DeleteNode( webvtt_dom_node_t *p_node )
460
1.41M
{
461
1.41M
    webvtt_dom_node_t *p_child = NULL;
462
1.41M
    if( p_node->type == NODE_TAG )
463
595k
    {
464
595k
        webvtt_dom_tag_t *p_tag_node = (webvtt_dom_tag_t *) p_node;
465
595k
        text_style_Delete( p_tag_node->p_cssstyle );
466
595k
        free( p_tag_node->psz_attrs );
467
595k
        free( p_tag_node->psz_tag );
468
595k
        p_child = p_tag_node->p_child;
469
595k
    }
470
814k
    else if( p_node->type == NODE_TEXT )
471
809k
    {
472
809k
        webvtt_dom_text_t *p_text_node = (webvtt_dom_text_t *)p_node;
473
809k
        free( p_text_node->psz_text );
474
809k
    }
475
5.79k
    else if( p_node->type == NODE_CUE )
476
4.30k
    {
477
4.30k
        webvtt_dom_cue_t *p_cue_node = (webvtt_dom_cue_t *)p_node;
478
4.30k
        text_style_Delete( p_cue_node->p_cssstyle );
479
4.30k
        webvtt_cue_settings_Clean( &p_cue_node->settings );
480
4.30k
        free( p_cue_node->psz_id );
481
4.30k
        p_child = p_cue_node->p_child;
482
4.30k
    }
483
1.48k
    else if( p_node->type == NODE_REGION )
484
1.48k
    {
485
1.48k
        webvtt_region_t *p_region_node = (webvtt_region_t *)p_node;
486
1.48k
        text_style_Delete( p_region_node->p_cssstyle );
487
1.48k
        free( p_region_node->psz_id );
488
1.48k
        p_child = p_region_node->p_child;
489
1.48k
    }
490
1.41M
    free( p_node );
491
1.41M
    return p_child;
492
1.41M
}
493
494
static void webvtt_domnode_ChainDelete( webvtt_dom_node_t *p_node )
495
542k
{
496
1.61M
    while( p_node )
497
1.06M
    {
498
1.06M
        webvtt_dom_node_t *p_next = p_node->p_next;
499
1.06M
        webvtt_dom_node_t *p_child = webvtt_domnode_DeleteNode( p_node );
500
1.41M
        while ( p_child )
501
343k
            p_child = webvtt_domnode_DeleteNode( p_child );
502
503
1.06M
        p_node = p_next;
504
1.06M
    }
505
542k
}
506
507
static webvtt_dom_text_t * webvtt_dom_text_New( webvtt_dom_node_t *p_parent )
508
876k
{
509
876k
    webvtt_dom_text_t *p_node = calloc( 1, sizeof(*p_node) );
510
876k
    if( p_node )
511
876k
    {
512
876k
        p_node->type = NODE_TEXT;
513
876k
        p_node->p_parent = p_parent;
514
876k
    }
515
876k
    return p_node;
516
876k
}
517
518
static webvtt_dom_tag_t * webvtt_dom_tag_New( webvtt_dom_node_t *p_parent )
519
2.73M
{
520
2.73M
    webvtt_dom_tag_t *p_node = calloc( 1, sizeof(*p_node) );
521
2.73M
    if( p_node )
522
2.73M
    {
523
2.73M
        p_node->i_nzstart = -1;
524
2.73M
        p_node->type = NODE_TAG;
525
2.73M
        p_node->p_parent = p_parent;
526
2.73M
    }
527
2.73M
    return p_node;
528
2.73M
}
529
530
static webvtt_dom_node_t * webvtt_domnode_getParentByTag( webvtt_dom_node_t *p_parent,
531
                                                         const char *psz_tag )
532
274k
{
533
558k
    for( ; p_parent ; p_parent = p_parent->p_parent )
534
301k
    {
535
301k
        if( p_parent->type == NODE_TAG )
536
301k
        {
537
301k
            webvtt_dom_tag_t *p_node = (webvtt_dom_tag_t *) p_parent;
538
301k
            if( p_node->psz_tag && psz_tag && !strcmp( p_node->psz_tag, psz_tag ) )
539
17.2k
                break;
540
301k
        }
541
301k
    }
542
274k
    return p_parent;
543
274k
}
544
545
static webvtt_dom_node_t * webvtt_domnode_getFirstChild( webvtt_dom_node_t *p_node )
546
4.83M
{
547
4.83M
    webvtt_dom_node_t *p_child = NULL;
548
4.83M
    switch( p_node->type )
549
4.83M
    {
550
119k
        case NODE_CUE:
551
119k
            p_child  = ((webvtt_dom_cue_t *)p_node)->p_child;
552
119k
            break;
553
55.5k
        case NODE_REGION:
554
55.5k
            p_child  = ((webvtt_region_t *)p_node)->p_child;
555
55.5k
            break;
556
4.32M
        case NODE_TAG:
557
4.32M
            p_child  = ((webvtt_dom_tag_t *)p_node)->p_child;
558
4.32M
            break;
559
335k
        default:
560
335k
            break;
561
4.83M
    }
562
4.83M
    return p_child;
563
4.83M
}
564
4.83M
#define webvtt_domnode_getFirstChild(a) webvtt_domnode_getFirstChild((webvtt_dom_node_t *)a)
565
566
static void webvtt_domnode_mergeCues( webvtt_dom_cue_t *p_dst, webvtt_dom_cue_t *p_src )
567
501k
{
568
501k
    p_dst->i_nzstop = p_src->i_nzstop;
569
    /* needs more work */
570
501k
    webvtt_dom_cue_Delete( p_src );
571
501k
}
572
573
static webvtt_dom_cue_t * webvtt_domnode_getCue( webvtt_dom_node_t *p_node,
574
                                                 vlc_tick_t i_nzstart,
575
                                                 const char *psz_id )
576
539k
{
577
842k
    for( ; p_node ; p_node = p_node->p_next )
578
804k
    {
579
804k
        if( p_node->type != NODE_CUE )
580
38.8k
            continue;
581
766k
        webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *) p_node;
582
766k
        if( p_cue->i_nzstart != i_nzstart )
583
0
            continue;
584
766k
        if( !psz_id != !p_cue->psz_id ||
585
601k
            (psz_id && strcmp(psz_id, p_cue->psz_id)) )
586
264k
            continue;
587
501k
        return p_cue;
588
766k
    }
589
37.3k
    return NULL;
590
539k
}
591
592
#ifdef HAVE_CSS
593
static vlc_tick_t webvtt_domnode_GetPlaybackTime( const webvtt_dom_node_t *p_node, bool b_end )
594
244k
{
595
666k
    for( ; p_node; p_node = p_node->p_parent )
596
647k
    {
597
647k
        if( p_node->type == NODE_TAG )
598
422k
        {
599
422k
            vlc_tick_t i_nzstart = ((const webvtt_dom_tag_t *) p_node)->i_nzstart;
600
422k
            if( i_nzstart > -1 && !b_end )
601
110k
                return i_nzstart;
602
422k
        }
603
225k
        else if( p_node->type == NODE_CUE )
604
115k
        {
605
115k
            break;
606
115k
        }
607
647k
    }
608
133k
    if( p_node )
609
115k
        return b_end ? ((const webvtt_dom_cue_t *) p_node)->i_nzstop:
610
115k
                       ((const webvtt_dom_cue_t *) p_node)->i_nzstart;
611
18.5k
    return VLC_TICK_INVALID;
612
133k
}
613
614
static bool webvtt_domnode_Match_Class( const webvtt_dom_node_t *p_node, const char *psz )
615
24.2k
{
616
24.2k
    const size_t i_len = strlen( psz );
617
24.2k
    if( i_len && p_node->type == NODE_TAG )
618
11.7k
    {
619
11.7k
        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
620
14.5k
        for( const char *p = p_tagnode->psz_attrs; p && *p; p++ )
621
9.96k
        {
622
9.96k
            p = strstr( p, psz );
623
9.96k
            if( !p )
624
6.70k
                return false;
625
3.26k
            if( p > p_tagnode->psz_attrs && p[-1] == '.' && !isalnum(p[i_len]) )
626
502
                return true;
627
3.26k
        }
628
11.7k
    }
629
17.0k
    return false;
630
24.2k
}
631
632
static bool webvtt_domnode_Match_Id( const webvtt_dom_node_t *p_node, const char *psz_id )
633
43.9k
{
634
43.9k
    if( !psz_id )
635
0
        return false;
636
43.9k
    if( *psz_id == '#' )
637
43.9k
        psz_id++;
638
43.9k
    if( p_node->type == NODE_REGION )
639
2.47k
        return ((webvtt_region_t *)p_node)->psz_id &&
640
2.47k
                !strcmp( ((webvtt_region_t *)p_node)->psz_id, psz_id );
641
41.4k
    else if( p_node->type == NODE_CUE )
642
5.67k
        return ((webvtt_dom_cue_t *)p_node)->psz_id &&
643
5.10k
                !strcmp( ((webvtt_dom_cue_t *)p_node)->psz_id, psz_id );
644
35.8k
    return false;
645
43.9k
}
646
647
static bool webvtt_domnode_Match_Tag( const webvtt_dom_node_t *p_node, const char *psz_tag )
648
91.6k
{
649
91.6k
    if( p_node->type == NODE_TAG && psz_tag )
650
44.5k
    {
651
        /* special case, not allowed to match anywhere but root */
652
44.5k
        if( !strcmp(psz_tag, "video") && p_node->p_parent )
653
820
            return false;
654
43.7k
        return ((webvtt_dom_tag_t *)p_node)->psz_tag &&
655
43.7k
                !strcmp( ((webvtt_dom_tag_t *)p_node)->psz_tag, psz_tag );
656
44.5k
    }
657
47.0k
    else return false;
658
91.6k
}
659
660
static bool webvtt_domnode_Match_PseudoClass( const webvtt_dom_node_t *p_node, const char *psz,
661
                                              vlc_tick_t i_nzplaybacktime )
662
320k
{
663
320k
    if( !strcmp(psz, "past") || !strcmp(psz, "future") )
664
244k
    {
665
244k
        vlc_tick_t i_nzstart = webvtt_domnode_GetPlaybackTime( p_node, false );
666
244k
        return ( *psz == 'p' ) ? i_nzstart < i_nzplaybacktime : i_nzstart > i_nzplaybacktime;
667
244k
    }
668
76.0k
    return false;
669
320k
}
670
671
static bool webvtt_domnode_Match_PseudoElement( const webvtt_dom_node_t *p_node, const char *psz )
672
150k
{
673
150k
    if( !strcmp(psz, "cue") )
674
122k
        return p_node->type == NODE_CUE;
675
28.1k
    else if( !strcmp(psz, "cue-region") )
676
16.0k
        return p_node->type == NODE_REGION;
677
12.1k
    return false;
678
150k
}
679
680
static bool MatchAttribute( const char *psz_attr, const char *psz_lookup, enum vlc_css_match_e match )
681
13.4k
{
682
13.4k
    switch( match )
683
13.4k
    {
684
13.4k
        case MATCH_EQUALS:
685
13.4k
            return !strcmp( psz_attr, psz_lookup );
686
0
        case MATCH_INCLUDES:
687
0
        {
688
0
            const char *p = strstr( psz_attr, psz_lookup );
689
0
            if( p && ( p == psz_attr || isspace(p[-1]) ) )
690
0
            {
691
0
                const char *end = p + strlen( psz_lookup );
692
0
                return (*end == 0 || isspace(*end));
693
0
            }
694
0
            break;
695
0
        }
696
0
        case MATCH_DASHMATCH:
697
0
        {
698
0
            size_t i_len = strlen(psz_lookup);
699
0
            if( !strncmp( psz_attr, psz_lookup, i_len ) )
700
0
            {
701
0
                const char *end = psz_attr + i_len;
702
0
                return (*end == 0 || !isalnum(*end) );
703
0
            }
704
0
            break;
705
0
        }
706
0
        case MATCH_BEGINSWITH:
707
0
            return !strncmp( psz_attr, psz_lookup, strlen(psz_lookup) );
708
0
        case MATCH_ENDSWITH:
709
0
        {
710
0
            const char *p = strstr( psz_attr, psz_lookup );
711
0
            return (p && *p && p[1] == 0);
712
0
        }
713
0
        case MATCH_CONTAINS:
714
0
            return !!strstr( psz_attr, psz_lookup );
715
0
        default:
716
0
            break;
717
13.4k
    }
718
0
    return false;
719
13.4k
}
720
721
static bool webvtt_domnode_Match_Attribute( const webvtt_dom_node_t *p_node,
722
                                            const char *psz, const vlc_css_selector_t *p_matchsel )
723
28.9k
{
724
28.9k
    if( p_node->type == NODE_TAG && p_matchsel )
725
22.3k
    {
726
22.3k
        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
727
728
22.3k
        if( p_tagnode->psz_attrs != NULL &&
729
20.6k
          ( ( !strcmp( p_tagnode->psz_tag, "v" ) && !strcmp( psz, "voice" ) ) || /* v = only voice */
730
7.41k
            ( !strcmp( p_tagnode->psz_tag, "lang" ) && !strcmp( psz, "lang" ) ) ) )
731
13.4k
        {
732
13.4k
            const char *psz_start = NULL;
733
            /* skip classes decl */
734
71.7k
            for( const char *p = p_tagnode->psz_attrs; *p; p++ )
735
60.2k
            {
736
60.2k
                if( isspace(*p) )
737
2.08k
                {
738
2.08k
                    psz_start = p + 1;
739
2.08k
                }
740
58.1k
                else if( psz_start != NULL )
741
1.84k
                {
742
1.84k
                    break;
743
1.84k
                }
744
60.2k
            }
745
746
13.4k
            if( psz_start == NULL || *psz_start == 0 )
747
11.5k
                psz_start = p_tagnode->psz_attrs;
748
749
13.4k
            if( !p_matchsel ) /* attribute check only */
750
0
                return *psz_start != '\0';
751
752
13.4k
            return MatchAttribute( psz_start, p_matchsel->psz_name, p_matchsel->match );
753
13.4k
        }
754
22.3k
    }
755
15.5k
    return false;
756
28.9k
}
757
758
static bool webvtt_domnode_MatchType( const webvtt_dom_node_t *p_node,
759
                                      const vlc_css_selector_t *p_sel, vlc_tick_t i_nzplaybacktime )
760
660k
{
761
660k
    switch( p_sel->type )
762
660k
    {
763
91.6k
        case SELECTOR_SIMPLE:
764
91.6k
            return webvtt_domnode_Match_Tag( p_node, p_sel->psz_name );
765
320k
        case SELECTOR_PSEUDOCLASS:
766
320k
            return webvtt_domnode_Match_PseudoClass( p_node, p_sel->psz_name,
767
320k
                                                     i_nzplaybacktime );
768
150k
        case SELECTOR_PSEUDOELEMENT:
769
150k
            return webvtt_domnode_Match_PseudoElement( p_node, p_sel->psz_name );
770
43.9k
        case SPECIFIER_ID:
771
43.9k
            return webvtt_domnode_Match_Id( p_node, p_sel->psz_name );
772
24.2k
        case SPECIFIER_CLASS:
773
24.2k
            return webvtt_domnode_Match_Class( p_node, p_sel->psz_name );
774
28.9k
        case SPECIFIER_ATTRIB:
775
28.9k
            return webvtt_domnode_Match_Attribute( p_node, p_sel->psz_name, p_sel->p_matchsel );
776
660k
    }
777
0
    return false;
778
660k
}
779
#endif
780
781
static text_style_t ** get_ppCSSStyle( webvtt_dom_node_t *p_node )
782
9.24M
{
783
9.24M
    switch( p_node->type )
784
9.24M
    {
785
196k
        case NODE_CUE:
786
196k
            return &((webvtt_dom_cue_t *)p_node)->p_cssstyle;
787
39.4k
        case NODE_REGION:
788
39.4k
            return &((webvtt_region_t *)p_node)->p_cssstyle;
789
8.76M
        case NODE_TAG:
790
8.76M
            return &((webvtt_dom_tag_t *)p_node)->p_cssstyle;
791
246k
        default:
792
246k
            return NULL;
793
9.24M
    }
794
9.24M
}
795
796
static text_style_t * webvtt_domnode_getCSSStyle( webvtt_dom_node_t *p_node )
797
1.00M
{
798
1.00M
    text_style_t **pp_style = get_ppCSSStyle( p_node );
799
1.00M
    if( pp_style )
800
864k
        return *pp_style;
801
144k
    return NULL;
802
1.00M
}
803
1.00M
#define webvtt_domnode_getCSSStyle(a) webvtt_domnode_getCSSStyle((webvtt_dom_node_t *)a)
804
805
static bool webvtt_domnode_supportsCSSStyle( webvtt_dom_node_t *p_node )
806
4.18M
{
807
4.18M
    return get_ppCSSStyle( p_node ) != NULL;
808
4.18M
}
809
810
static void webvtt_domnode_setCSSStyle( webvtt_dom_node_t *p_node, text_style_t *p_style )
811
4.05M
{
812
4.05M
    text_style_t **pp_style = get_ppCSSStyle( p_node );
813
4.05M
    if( !pp_style )
814
0
    {
815
0
        assert( pp_style );
816
0
        if( p_style )
817
0
            text_style_Delete( p_style );
818
0
        return;
819
0
    }
820
4.05M
    if( *pp_style )
821
14.6k
        text_style_Delete( *pp_style );
822
4.05M
    *pp_style = p_style;
823
4.05M
}
824
825
#ifdef HAVE_CSS
826
static void webvtt_domnode_SelectNodesInTree( const webvtt_dom_node_t *p_tree,
827
                                              const vlc_css_selector_t *p_sel, int i_max_depth,
828
                                              vlc_tick_t i_nzplaybacktime, vlc_array_t *p_results );
829
830
static void webvtt_domnode_SelectChildNodesInTree( const webvtt_dom_node_t *p_tree,
831
                                                   const vlc_css_selector_t *p_sel, int i_max_depth,
832
                                                   vlc_tick_t i_nzplaybacktime, vlc_array_t *p_results )
833
684k
{
834
684k
    const webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_tree );
835
684k
    if( i_max_depth > 0 )
836
650k
    {
837
1.20M
        for( ; p_child; p_child = p_child->p_next )
838
557k
            webvtt_domnode_SelectNodesInTree( p_child, p_sel, i_max_depth - 1,
839
557k
                                              i_nzplaybacktime, p_results );
840
650k
    }
841
684k
}
842
843
static void webvtt_domnode_SelectNodesBySpeficier( const webvtt_dom_node_t *p_node,
844
                                                   const vlc_css_selector_t *p_spec,
845
                                                   vlc_tick_t i_nzplaybacktime, vlc_array_t *p_results )
846
37.4k
{
847
37.4k
    if( p_spec == NULL )
848
0
        return;
849
850
37.4k
    switch( p_spec->combinator )
851
37.4k
    {
852
7.82k
        case RELATION_DESCENDENT:
853
7.82k
            webvtt_domnode_SelectChildNodesInTree( p_node, p_spec, WEBVTT_MAX_DEPTH,
854
7.82k
                                                   i_nzplaybacktime, p_results );
855
7.82k
            break;
856
3.18k
        case RELATION_DIRECTADJACENT:
857
9.53k
            for( const webvtt_dom_node_t *p_adj = p_node->p_next; p_adj; p_adj = p_adj->p_next )
858
6.35k
                webvtt_domnode_SelectChildNodesInTree( p_adj, p_spec, 1,
859
6.35k
                                                       i_nzplaybacktime, p_results );
860
3.18k
            break;
861
7.93k
        case RELATION_INDIRECTADJACENT:
862
7.93k
            for( const webvtt_dom_node_t *p_adj = webvtt_domnode_getFirstChild( p_node->p_parent );
863
14.9k
                                          p_adj && p_adj != p_node; p_adj = p_adj->p_next )
864
7.00k
                webvtt_domnode_SelectChildNodesInTree( p_adj, p_spec, 1,
865
7.00k
                                                       i_nzplaybacktime, p_results );
866
7.93k
            break;
867
3.13k
        case RELATION_CHILD:
868
3.13k
            webvtt_domnode_SelectChildNodesInTree( p_node, p_spec, 1,
869
3.13k
                                                   i_nzplaybacktime, p_results );
870
3.13k
            break;
871
15.4k
        case RELATION_SELF:
872
15.4k
            webvtt_domnode_SelectNodesInTree( p_node, p_spec, WEBVTT_MAX_DEPTH,
873
15.4k
                                              i_nzplaybacktime, p_results );
874
37.4k
    }
875
37.4k
}
876
877
static void webvtt_domnode_SelectNodesInTree( const webvtt_dom_node_t *p_node,
878
                                              const vlc_css_selector_t *p_sel, int i_max_depth,
879
                                              vlc_tick_t i_nzplaybacktime, vlc_array_t *p_results )
880
660k
{
881
660k
    if( p_node == NULL )
882
0
        return;
883
884
660k
    if( webvtt_domnode_MatchType( p_node, p_sel, i_nzplaybacktime ) )
885
124k
    {
886
124k
        if( p_sel->specifiers.p_first == NULL )
887
87.2k
        {
888
            /* End of matching, this node is part of results */
889
87.2k
            (void) vlc_array_append( p_results, (void *) p_node );
890
87.2k
        }
891
37.4k
        else webvtt_domnode_SelectNodesBySpeficier( p_node, p_sel->specifiers.p_first,
892
37.4k
                                                    i_nzplaybacktime, p_results );
893
124k
    }
894
895
    /* lookup other subnodes */
896
660k
    webvtt_domnode_SelectChildNodesInTree( p_node, p_sel, i_max_depth - 1,
897
660k
                                           i_nzplaybacktime, p_results );
898
660k
}
899
900
static void webvtt_domnode_SelectRuleNodes( const webvtt_dom_node_t *p_root, const vlc_css_rule_t *p_rule,
901
                                            vlc_tick_t i_nzplaybacktime, vlc_array_t *p_results )
902
27.7k
{
903
27.7k
    if(!p_root || p_root->type != NODE_TAG)
904
0
        return;
905
27.7k
    const webvtt_dom_node_t *p_cues = ((const webvtt_dom_tag_t *)p_root)->p_child;
906
53.3k
    for( const vlc_css_selector_t *p_sel = p_rule->p_selectors; p_sel; p_sel = p_sel->p_next )
907
25.5k
    {
908
25.5k
        vlc_array_t tempresults;
909
25.5k
        vlc_array_init( &tempresults );
910
112k
        for( const webvtt_dom_node_t *p_node = p_cues; p_node; p_node = p_node->p_next )
911
86.9k
        {
912
86.9k
            webvtt_domnode_SelectNodesInTree( p_node, p_sel, WEBVTT_MAX_DEPTH,
913
86.9k
                                              i_nzplaybacktime, &tempresults );
914
86.9k
        }
915
112k
        for( size_t i=0; i<vlc_array_count(&tempresults); i++ )
916
87.2k
            (void) vlc_array_append( p_results, vlc_array_item_at_index( &tempresults, i ) );
917
25.5k
        vlc_array_clear( &tempresults );
918
25.5k
    }
919
27.7k
}
920
#endif
921
922
static inline bool IsEndTag( const char *psz )
923
8.37M
{
924
8.37M
    return psz[1] == '/';
925
8.37M
}
926
927
/* returns first opening and last chars of next tag, only when valid */
928
static const char * FindNextTag( const char *psz, const char **ppsz_taglast )
929
3.28M
{
930
3.28M
    psz = strchr( psz, '<' );
931
3.28M
    if( psz )
932
3.02M
    {
933
3.02M
        *ppsz_taglast = strchr( psz + 1, '>' );
934
3.02M
        if( *ppsz_taglast )
935
3.00M
        {
936
3.00M
            const size_t tagsize = *ppsz_taglast - psz + 1;
937
3.00M
            if( tagsize <= 3 )
938
2.36M
            {
939
2.36M
                if( tagsize < 2 || IsEndTag(psz) )
940
306
                    *ppsz_taglast = psz = NULL;
941
2.36M
            }
942
3.00M
        } else psz = NULL;
943
3.02M
    }
944
3.28M
    return psz;
945
3.28M
}
946
947
/* Points to first char of tag name and sets *ppsz_attrs to attributes */
948
static const char *SplitTag( const char *psz_tag, size_t *pi_tag, const char **ppsz_attrs )
949
3.00M
{
950
3.00M
    psz_tag += IsEndTag( psz_tag ) ? 2 : 1;
951
3.00M
    const char *p = psz_tag;
952
3.00M
    *pi_tag = 0;
953
3.00M
    if( isalpha( *p ) )
954
2.27M
    {
955
2.27M
        while( isalnum( *p ) )
956
2.36M
        {
957
2.36M
            p++;
958
2.36M
            (*pi_tag)++;
959
2.36M
        }
960
2.27M
        while( isspace( *p ) )
961
48.6k
            p++;
962
2.27M
    }
963
3.00M
    *ppsz_attrs = p;
964
3.00M
    return psz_tag;
965
3.00M
}
966
967
/*****************************************************************************
968
 *
969
 *****************************************************************************/
970
static webvtt_dom_cue_t * webvtt_dom_cue_New( vlc_tick_t i_nzstart, vlc_tick_t i_nzend )
971
539k
{
972
539k
    webvtt_dom_cue_t *p_cue = calloc( 1, sizeof(*p_cue) );
973
539k
    if( p_cue )
974
539k
    {
975
539k
        p_cue->type = NODE_CUE;
976
539k
        p_cue->psz_id = NULL;
977
539k
        p_cue->i_nzstart = i_nzstart;
978
539k
        p_cue->i_nzstop = i_nzend;
979
539k
        p_cue->p_child = NULL;
980
539k
        p_cue->i_lines = 0;
981
539k
        p_cue->p_cssstyle = NULL;
982
539k
        webvtt_cue_settings_Init( &p_cue->settings );
983
539k
    }
984
539k
    return p_cue;
985
539k
}
986
987
static void webvtt_dom_cue_ClearText( webvtt_dom_cue_t *p_cue )
988
533k
{
989
533k
    webvtt_domnode_ChainDelete( p_cue->p_child );
990
533k
    p_cue->p_child = NULL;
991
533k
    p_cue->i_lines = 0;
992
533k
}
993
994
static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue )
995
533k
{
996
533k
    text_style_Delete( p_cue->p_cssstyle );
997
533k
    webvtt_dom_cue_ClearText( p_cue );
998
533k
    webvtt_cue_settings_Clean( &p_cue->settings );
999
533k
    free( p_cue->psz_id );
1000
533k
    free( p_cue );
1001
533k
}
1002
1003
/* reduces by one line */
1004
static unsigned webvtt_dom_cue_Reduced( webvtt_dom_cue_t *p_cue )
1005
117
{
1006
117
    if( p_cue->i_lines < 1 )
1007
117
        return 0;
1008
1009
0
    for( webvtt_dom_node_t *p_node = p_cue->p_child;
1010
0
                           p_node; p_node = p_node->p_next )
1011
0
    {
1012
0
        if( p_node->type != NODE_TEXT )
1013
0
            continue;
1014
0
        webvtt_dom_text_t *p_textnode = (webvtt_dom_text_t *) p_node;
1015
0
        const char *nl = strchr( p_textnode->psz_text, '\n' );
1016
0
        if( nl )
1017
0
        {
1018
0
            size_t i_len = strlen( p_textnode->psz_text );
1019
0
            size_t i_remain = i_len - (nl - p_textnode->psz_text);
1020
0
            char *psz_new = strndup( nl + 1, i_remain );
1021
0
            free( p_textnode->psz_text );
1022
0
            p_textnode->psz_text = psz_new;
1023
0
            return --p_cue->i_lines;
1024
0
        }
1025
0
        else
1026
0
        {
1027
0
            free( p_textnode->psz_text );
1028
0
            p_textnode->psz_text = NULL;
1029
            /* FIXME: probably can do a local nodes cleanup */
1030
0
        }
1031
0
    }
1032
1033
0
    return p_cue->i_lines;
1034
0
}
1035
1036
/*****************************************************************************
1037
 *
1038
 *****************************************************************************/
1039
1040
static void webvtt_region_ParseTuple( webvtt_region_t *p_region,
1041
                                      const char *psz_key, const char *psz_value )
1042
17.0k
{
1043
17.0k
    if( !strcmp( psz_key, "id" ) )
1044
3.43k
    {
1045
3.43k
        free( p_region->psz_id );
1046
3.43k
        p_region->psz_id = strdup( psz_value );
1047
3.43k
    }
1048
13.5k
    else if( !strcmp( psz_key, "width" ) )
1049
2.12k
    {
1050
2.12k
        parse_percent( psz_value, &p_region->f_width );
1051
2.12k
    }
1052
11.4k
    else if( !strcmp( psz_key, "regionanchor" ) )
1053
1.98k
    {
1054
1.98k
        parse_percent_tuple( psz_value, &p_region->anchor_x,
1055
1.98k
                                        &p_region->anchor_y );
1056
1.98k
    }
1057
9.47k
    else if( !strcmp( psz_key, "viewportanchor" ) )
1058
1.86k
    {
1059
1.86k
        parse_percent_tuple( psz_value, &p_region->viewport_anchor_x,
1060
1.86k
                                        &p_region->viewport_anchor_y );
1061
1.86k
    }
1062
7.60k
    else if( !strcmp( psz_key, "lines" ) )
1063
1.75k
    {
1064
1.75k
        int i = atoi( psz_value );
1065
1.75k
        if( i > 0 )
1066
1.37k
            p_region->i_lines_max_scroll = __MIN(i, WEBVTT_REGION_LINES_COUNT);
1067
1.75k
    }
1068
5.85k
    else if( !strcmp( psz_key, "scroll" ) )
1069
1.24k
    {
1070
1.24k
        p_region->b_scroll_up = !strcmp( psz_value, "up" );
1071
1.24k
    }
1072
17.0k
}
1073
1074
static void webvtt_region_Parse( webvtt_region_t *p_region, char *psz_line )
1075
26.9k
{
1076
26.9k
    char *p_save;
1077
26.9k
    char *psz_tuple;
1078
26.9k
    char *p_str = psz_line;
1079
26.9k
    do
1080
55.3k
    {
1081
55.3k
        psz_tuple = strtok_r( p_str, " ", &p_save );
1082
55.3k
        p_str = NULL;
1083
55.3k
        if( psz_tuple )
1084
28.3k
        {
1085
28.3k
            const char *psz_split = strchr( psz_tuple, ':' );
1086
28.3k
            if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple )
1087
17.0k
            {
1088
17.0k
                char *psz_key = strndup( psz_tuple, psz_split - psz_tuple );
1089
17.0k
                if( psz_key )
1090
17.0k
                {
1091
17.0k
                    webvtt_region_ParseTuple( p_region, psz_key, psz_split + 1 );
1092
17.0k
                    free( psz_key );
1093
17.0k
                }
1094
17.0k
            }
1095
28.3k
        }
1096
55.3k
    } while( psz_tuple );
1097
26.9k
}
1098
1099
static unsigned webvtt_region_CountLines( const webvtt_region_t *p_region )
1100
7.99k
{
1101
7.99k
    unsigned i_lines = 0;
1102
7.99k
    for( const webvtt_dom_node_t *p_node = p_region->p_child;
1103
52.9k
                                  p_node; p_node = p_node->p_next )
1104
44.9k
    {
1105
44.9k
        assert( p_node->type == NODE_CUE );
1106
44.9k
        if( p_node->type != NODE_CUE )
1107
0
            continue;
1108
44.9k
        i_lines += ((const webvtt_dom_cue_t *)p_node)->i_lines;
1109
44.9k
    }
1110
7.99k
    return i_lines;
1111
7.99k
}
1112
1113
static void ClearCuesByTime( webvtt_dom_node_t **pp_next, vlc_tick_t i_nztime )
1114
25.3k
{
1115
64.8k
    while( *pp_next )
1116
39.4k
    {
1117
39.4k
        webvtt_dom_node_t *p_node = *pp_next;
1118
39.4k
        if( p_node )
1119
39.4k
        {
1120
39.4k
            if( p_node->type == NODE_CUE )
1121
30.6k
            {
1122
30.6k
                webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *)p_node;
1123
30.6k
                if( p_cue->i_nzstop <= i_nztime )
1124
30.6k
                {
1125
30.6k
                    *pp_next = p_node->p_next;
1126
30.6k
                    p_node->p_next = NULL;
1127
30.6k
                    webvtt_dom_cue_Delete( p_cue );
1128
30.6k
                    continue;
1129
30.6k
                }
1130
30.6k
            }
1131
8.87k
            else if( p_node->type == NODE_REGION )
1132
8.87k
            {
1133
8.87k
                webvtt_region_t *p_region = (webvtt_region_t *) p_node;
1134
8.87k
                ClearCuesByTime( &p_region->p_child, i_nztime );
1135
8.87k
            }
1136
8.87k
            pp_next = &p_node->p_next;
1137
8.87k
        }
1138
39.4k
    }
1139
25.3k
}
1140
1141
/* Remove top most line/cue for bottom insert */
1142
static bool webvtt_region_Reduce( webvtt_region_t *p_region )
1143
977
{
1144
977
    if( p_region->p_child )
1145
977
    {
1146
977
        assert( p_region->p_child->type == NODE_CUE );
1147
977
        if( p_region->p_child->type != NODE_CUE )
1148
0
            return false;
1149
977
        webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *)p_region->p_child;
1150
977
        if( p_cue->i_lines == 1 ||
1151
117
            webvtt_dom_cue_Reduced( p_cue ) < 1 )
1152
977
        {
1153
977
            p_region->p_child = p_cue->p_next;
1154
977
            p_cue->p_next = NULL;
1155
977
            webvtt_dom_cue_Delete( p_cue );
1156
977
            return true;
1157
977
        }
1158
977
    }
1159
0
    return false;
1160
977
}
1161
1162
static void webvtt_region_AddCue( webvtt_region_t *p_region,
1163
                                  webvtt_dom_cue_t *p_cue )
1164
7.02k
{
1165
7.02k
    webvtt_dom_node_t **pp_add = &p_region->p_child;
1166
36.7k
    while( *pp_add )
1167
29.7k
        pp_add = &((*pp_add)->p_next);
1168
7.02k
    *pp_add = (webvtt_dom_node_t *)p_cue;
1169
7.02k
    p_cue->p_parent = (webvtt_dom_node_t *)p_region;
1170
1171
7.02k
    for( ;; )
1172
7.99k
    {
1173
7.99k
        unsigned i_lines = webvtt_region_CountLines( p_region );
1174
7.99k
        if( i_lines > 0 &&
1175
7.74k
            ( i_lines > WEBVTT_REGION_LINES_COUNT ||
1176
7.38k
             (p_region->b_scroll_up && i_lines > p_region->i_lines_max_scroll)) )
1177
977
        {
1178
977
            if (!webvtt_region_Reduce( p_region )) /* scrolls up */
1179
0
                break;
1180
977
        }
1181
7.02k
        else break;
1182
7.99k
    }
1183
7.02k
}
1184
1185
static void webvtt_region_Delete( webvtt_region_t *p_region )
1186
1.22k
{
1187
1.22k
    text_style_Delete( p_region->p_cssstyle );
1188
1.22k
    webvtt_domnode_ChainDelete( p_region->p_child );
1189
1.22k
    p_region->p_child = NULL;
1190
1.22k
    free( p_region->psz_id );
1191
1.22k
    free( p_region );
1192
1.22k
}
1193
1194
static webvtt_region_t * webvtt_region_New( void )
1195
4.07k
{
1196
4.07k
    webvtt_region_t *p_region = malloc(sizeof(*p_region));
1197
4.07k
    if( p_region )
1198
4.07k
    {
1199
4.07k
        p_region->type = NODE_REGION;
1200
4.07k
        p_region->psz_id = NULL;
1201
4.07k
        p_region->p_next = NULL;
1202
4.07k
        p_region->f_width = 1.0; /* 100% */
1203
4.07k
        p_region->anchor_x = 0;
1204
4.07k
        p_region->anchor_y = 1.0; /* 100% */
1205
4.07k
        p_region->i_lines_max_scroll = 3;
1206
4.07k
        p_region->viewport_anchor_x = 0;
1207
4.07k
        p_region->viewport_anchor_y = 1.0; /* 100% */
1208
4.07k
        p_region->b_scroll_up = false;
1209
4.07k
        p_region->p_cssstyle = NULL;
1210
4.07k
        p_region->p_child = NULL;
1211
4.07k
    }
1212
4.07k
    return p_region;
1213
4.07k
}
1214
1215
static webvtt_region_t * webvtt_region_GetByID( decoder_sys_t *p_sys,
1216
                                                const char *psz_id )
1217
539k
{
1218
539k
    if( !psz_id )
1219
518k
        return NULL;
1220
21.1k
    for( webvtt_dom_node_t *p_node = p_sys->p_root->p_child;
1221
58.1k
                            p_node; p_node = p_node->p_next )
1222
47.9k
    {
1223
47.9k
        if( p_node->type == NODE_REGION )
1224
15.1k
        {
1225
15.1k
            webvtt_region_t *p_region = (webvtt_region_t *) p_node;
1226
15.1k
            if( p_region->psz_id && !strcmp( psz_id, p_region->psz_id ) )
1227
10.8k
                return p_region;
1228
15.1k
        }
1229
47.9k
    }
1230
10.2k
    return NULL;
1231
21.1k
}
1232
1233
/*****************************************************************************
1234
 *
1235
 *****************************************************************************/
1236
static char * DuplicateUnescaped( const char *psz )
1237
279k
{
1238
279k
    char *s = strdup( psz );
1239
279k
    if( s )
1240
279k
        vlc_xml_decode( s );
1241
279k
    return s;
1242
279k
}
1243
1244
static char * NDuplicateUnescaped( const char *psz, size_t len )
1245
3.96M
{
1246
3.96M
    char *s = strndup( psz, len );
1247
3.96M
    if( s )
1248
3.96M
        vlc_xml_decode( s );
1249
3.96M
    return s;
1250
3.96M
}
1251
1252
static unsigned CountNewLines( const char *psz )
1253
876k
{
1254
876k
    unsigned i = 0;
1255
1.86M
    while( psz && *psz )
1256
990k
        psz = strchr( psz + 1, '\n' );
1257
876k
    return i;
1258
876k
}
1259
1260
static webvtt_dom_node_t * CreateDomNodes( const char *psz_text, unsigned *pi_lines )
1261
536k
{
1262
536k
    webvtt_dom_node_t *p_head = NULL;
1263
536k
    webvtt_dom_node_t **pp_append = &p_head;
1264
536k
    webvtt_dom_node_t *p_parent = p_head;
1265
536k
    *pi_lines = 0;
1266
1267
3.54M
    while( *psz_text )
1268
3.28M
    {
1269
3.28M
        const char *psz_taglast;
1270
3.28M
        const char *psz_tag = FindNextTag( psz_text, &psz_taglast );
1271
3.28M
        if( psz_tag )
1272
3.00M
        {
1273
3.00M
            if( psz_tag - psz_text > 0 )
1274
596k
            {
1275
596k
                webvtt_dom_text_t *p_node = webvtt_dom_text_New( p_parent );
1276
596k
                if( p_node )
1277
596k
                {
1278
596k
                    p_node->psz_text = NDuplicateUnescaped( psz_text, psz_tag - psz_text );
1279
596k
                    *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text );
1280
596k
                    *pp_append = (webvtt_dom_node_t *) p_node;
1281
596k
                    pp_append = &p_node->p_next;
1282
596k
                }
1283
596k
            }
1284
1285
3.00M
            if( ! IsEndTag( psz_tag ) )
1286
2.73M
            {
1287
2.73M
                webvtt_dom_tag_t *p_node = webvtt_dom_tag_New( p_parent );
1288
2.73M
                if( p_node )
1289
2.73M
                {
1290
2.73M
                    const char *psz_attrs = NULL;
1291
2.73M
                    size_t i_name;
1292
2.73M
                    const char *psz_name = SplitTag( psz_tag, &i_name, &psz_attrs );
1293
2.73M
                    p_node->psz_tag = NDuplicateUnescaped( psz_name, i_name );
1294
2.73M
                    if( psz_attrs != psz_taglast )
1295
366k
                        p_node->psz_attrs = NDuplicateUnescaped( psz_attrs, psz_taglast - psz_attrs );
1296
                    /* <hh:mm::ss:fff> time tags */
1297
2.73M
                    if( p_node->psz_attrs && isdigit(p_node->psz_attrs[0]) )
1298
52.7k
                        (void) webvtt_scan_time( p_node->psz_attrs, &p_node->i_nzstart );
1299
2.73M
                    *pp_append = (webvtt_dom_node_t *) p_node;
1300
2.73M
                    p_parent = (webvtt_dom_node_t *) p_node;
1301
2.73M
                    pp_append = &p_node->p_child;
1302
2.73M
                }
1303
2.73M
            }
1304
277k
            else
1305
277k
            {
1306
277k
                if( p_parent )
1307
274k
                {
1308
274k
                    const char *psz_attrs = NULL;
1309
274k
                    size_t i_name;
1310
274k
                    const char *psz_name = SplitTag( psz_tag, &i_name, &psz_attrs );
1311
274k
                    char *psz_tagname = NDuplicateUnescaped( psz_name, i_name );
1312
1313
                    /* Close at matched parent node level due to unclosed tags
1314
                     * like <b><v stuff>foo</b> */
1315
274k
                    p_parent = webvtt_domnode_getParentByTag( p_parent, psz_tagname );
1316
274k
                    if( p_parent ) /* continue as parent next */
1317
17.2k
                    {
1318
17.2k
                        pp_append = &p_parent->p_next;
1319
17.2k
                        p_parent = p_parent->p_parent;
1320
17.2k
                    }
1321
257k
                    else /* back as top node */
1322
257k
                        pp_append = &p_head->p_next;
1323
537k
                    while( *pp_append )
1324
263k
                        pp_append = &((*pp_append)->p_next);
1325
1326
274k
                    free( psz_tagname );
1327
274k
                }
1328
2.62k
                else break; /* End tag for non open tag */
1329
277k
            }
1330
3.00M
            psz_text = psz_taglast + 1;
1331
3.00M
        }
1332
279k
        else /* Special case: end */
1333
279k
        {
1334
279k
            webvtt_dom_text_t *p_node = webvtt_dom_text_New( p_parent );
1335
279k
            if( p_node )
1336
279k
            {
1337
279k
                p_node->psz_text = DuplicateUnescaped( psz_text );
1338
279k
                *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text );
1339
279k
                *pp_append = (webvtt_dom_node_t *) p_node;
1340
279k
            }
1341
279k
            break;
1342
279k
        }
1343
3.28M
    }
1344
1345
536k
    return p_head;
1346
536k
}
1347
1348
static void ProcessCue( decoder_t *p_dec, const char *psz, webvtt_dom_cue_t *p_cue )
1349
536k
{
1350
536k
    VLC_UNUSED(p_dec);
1351
1352
536k
    if( p_cue->p_child )
1353
0
        return;
1354
536k
    p_cue->p_child = CreateDomNodes( psz, &p_cue->i_lines );
1355
1.60M
    for( webvtt_dom_node_t *p_child = p_cue->p_child; p_child; p_child = p_child->p_next )
1356
1.06M
        p_child->p_parent = (webvtt_dom_node_t *)p_cue;
1357
#ifdef SUBSVTT_DEBUG
1358
    webvtt_domnode_Debug( (webvtt_dom_node_t *) p_cue, 0 );
1359
#endif
1360
536k
}
1361
1362
static text_style_t * ComputeStyle( decoder_t *p_dec, const webvtt_dom_node_t *p_leaf )
1363
144k
{
1364
144k
    VLC_UNUSED(p_dec);
1365
144k
    text_style_t *p_style = NULL;
1366
144k
    text_style_t *p_dfltstyle = NULL;
1367
144k
    vlc_tick_t i_tagtime = -1;
1368
1369
1.60M
    for( const webvtt_dom_node_t *p_node = p_leaf ; p_node; p_node = p_node->p_parent )
1370
1.45M
    {
1371
1.45M
        bool b_nooverride = false;
1372
1.45M
        if( p_node->type == NODE_CUE )
1373
144k
        {
1374
144k
            const webvtt_dom_cue_t *p_cue = (const webvtt_dom_cue_t *)p_node;
1375
144k
            if( p_cue )
1376
144k
            {
1377
144k
                if( i_tagtime > -1 ) /* don't override timed stylings */
1378
77.2k
                    b_nooverride = true;
1379
144k
            }
1380
144k
        }
1381
1.31M
        else if( p_node->type == NODE_TAG )
1382
1.15M
        {
1383
1.15M
            const webvtt_dom_tag_t *p_tagnode = (const webvtt_dom_tag_t *)p_node;
1384
1385
1.15M
            if( p_tagnode->i_nzstart > -1 )
1386
589k
            {
1387
                /* Ignore other timed stylings */
1388
589k
                if( i_tagtime == -1 )
1389
77.2k
                    i_tagtime = p_tagnode->i_nzstart;
1390
512k
                else
1391
512k
                    continue;
1392
589k
            }
1393
1394
640k
            if ( p_tagnode->psz_tag )
1395
640k
            {
1396
640k
                if ( !strcmp( p_tagnode->psz_tag, "b" ) )
1397
52.7k
                {
1398
52.7k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1399
52.7k
                    {
1400
52.7k
                        p_style->i_style_flags |= STYLE_BOLD;
1401
52.7k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1402
52.7k
                    }
1403
52.7k
                }
1404
587k
                else if ( !strcmp( p_tagnode->psz_tag, "i" ) )
1405
1.88k
                {
1406
1.88k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1407
1.88k
                    {
1408
1.88k
                        p_style->i_style_flags |= STYLE_ITALIC;
1409
1.88k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1410
1.88k
                    }
1411
1.88k
                }
1412
586k
                else if ( !strcmp( p_tagnode->psz_tag, "u" ) )
1413
9.25k
                {
1414
9.25k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1415
9.25k
                    {
1416
9.25k
                        p_style->i_style_flags |= STYLE_UNDERLINE;
1417
9.25k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1418
9.25k
                    }
1419
9.25k
                }
1420
576k
                else if ( !strcmp( p_tagnode->psz_tag, "v" ) && p_tagnode->psz_attrs )
1421
31.3k
                {
1422
31.3k
#ifdef HAVE_CSS
1423
31.3k
                    decoder_sys_t *p_sys = p_dec->p_sys;
1424
31.3k
                    if( p_sys->p_css_rules == NULL ) /* Only auto style when no CSS sheet */
1425
17.6k
#endif
1426
17.6k
                    {
1427
17.6k
                        if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1428
17.6k
                        {
1429
17.6k
                            unsigned a = 0;
1430
381k
                            for( char *p = p_tagnode->psz_attrs; *p; p++ )
1431
363k
                                a = (a << 3) ^ *p;
1432
17.6k
                            p_style->i_font_color = (0x7F7F7F | a) & 0xFFFFFF;
1433
17.6k
                            p_style->i_features |= STYLE_HAS_FONT_COLOR;
1434
17.6k
                        }
1435
17.6k
                    }
1436
31.3k
                }
1437
545k
                else if( !strcmp( p_tagnode->psz_tag, "c" ) && p_tagnode->psz_attrs )
1438
34.0k
                {
1439
34.0k
                    static const struct
1440
34.0k
                    {
1441
34.0k
                        const char *psz;
1442
34.0k
                        uint32_t i_color;
1443
34.0k
                    } CEAcolors[] = {
1444
34.0k
                        { "white",  0xFFFFFF },
1445
34.0k
                        { "lime",   0x00FF00 },
1446
34.0k
                        { "cyan",   0x00FFFF },
1447
34.0k
                        { "red",    0xFF0000 },
1448
34.0k
                        { "yellow", 0xFFFF00 },
1449
34.0k
                        { "magenta",0xFF00FF },
1450
34.0k
                        { "blue",   0x0000FF },
1451
34.0k
                        { "black",  0x000000 },
1452
34.0k
                    };
1453
34.0k
                    char *saveptr = NULL;
1454
34.0k
                    char *psz_tok = strtok_r( p_tagnode->psz_attrs, ".", &saveptr );
1455
75.0k
                    for( ; psz_tok; psz_tok = strtok_r( NULL, ".", &saveptr ) )
1456
40.9k
                    {
1457
40.9k
                        bool bg = !strncmp( psz_tok, "bg_", 3 );
1458
40.9k
                        const char *psz_class = (bg) ? psz_tok + 3 : psz_tok;
1459
241k
                        for( size_t i=0; i<ARRAY_SIZE(CEAcolors); i++ )
1460
226k
                        {
1461
226k
                            if( strcmp( psz_class, CEAcolors[i].psz ) )
1462
200k
                                continue;
1463
25.8k
                            if( p_dfltstyle ||
1464
23.9k
                               (p_dfltstyle = text_style_Create( STYLE_NO_DEFAULTS )) )
1465
25.8k
                            {
1466
25.8k
                                if( bg )
1467
2.23k
                                {
1468
2.23k
                                    p_dfltstyle->i_background_color = CEAcolors[i].i_color;
1469
2.23k
                                    p_dfltstyle->i_background_alpha = STYLE_ALPHA_OPAQUE;
1470
2.23k
                                    p_dfltstyle->i_features |= STYLE_HAS_BACKGROUND_COLOR |
1471
2.23k
                                                               STYLE_HAS_BACKGROUND_ALPHA;
1472
2.23k
                                    p_dfltstyle->i_style_flags |= STYLE_BACKGROUND;
1473
2.23k
                                    p_dfltstyle->i_features |= STYLE_HAS_FLAGS;
1474
2.23k
                                }
1475
23.6k
                                else
1476
23.6k
                                {
1477
23.6k
                                    p_dfltstyle->i_font_color = CEAcolors[i].i_color;
1478
23.6k
                                    p_dfltstyle->i_features |= STYLE_HAS_FONT_COLOR;
1479
23.6k
                                }
1480
25.8k
                            }
1481
25.8k
                            break;
1482
226k
                        }
1483
40.9k
                    }
1484
34.0k
                }
1485
640k
            }
1486
640k
        }
1487
1488
946k
        const text_style_t *p_nodestyle = webvtt_domnode_getCSSStyle( p_node );
1489
946k
        if( p_nodestyle )
1490
117k
        {
1491
117k
            if( p_style )
1492
77.8k
                text_style_Merge( p_style, p_nodestyle, false );
1493
39.2k
            else if( !b_nooverride )
1494
33.7k
                p_style = text_style_Duplicate( p_nodestyle );
1495
117k
        }
1496
1497
        /* Default classes */
1498
946k
        if( p_dfltstyle )
1499
23.9k
        {
1500
23.9k
            if( p_style )
1501
18.2k
            {
1502
18.2k
                text_style_Merge( p_style, p_dfltstyle, false );
1503
18.2k
                text_style_Delete( p_dfltstyle );
1504
18.2k
            }
1505
5.74k
            else p_style = p_dfltstyle;
1506
23.9k
            p_dfltstyle = NULL;
1507
23.9k
        }
1508
946k
    }
1509
1510
144k
    return p_style;
1511
144k
}
1512
1513
static int GetCueTextAlignment( const webvtt_dom_cue_t *p_cue )
1514
38.3k
{
1515
38.3k
    switch( p_cue->settings.align )
1516
38.3k
    {
1517
2.79k
        case WEBVTT_ALIGN_LEFT:
1518
2.79k
            return SUBPICTURE_ALIGN_LEFT;
1519
652
        case WEBVTT_ALIGN_RIGHT:
1520
652
            return SUBPICTURE_ALIGN_RIGHT;
1521
360
        case WEBVTT_ALIGN_START: /* vertical provides rl or rl base direction */
1522
360
            return (p_cue->settings.vertical == WEBVTT_ALIGN_RIGHT) ?
1523
360
                     SUBPICTURE_ALIGN_RIGHT : SUBPICTURE_ALIGN_LEFT;
1524
374
        case WEBVTT_ALIGN_END:
1525
374
            return (p_cue->settings.vertical == WEBVTT_ALIGN_RIGHT) ?
1526
336
                     SUBPICTURE_ALIGN_LEFT : SUBPICTURE_ALIGN_RIGHT;
1527
34.1k
        default:
1528
34.1k
            return 0;
1529
38.3k
    }
1530
38.3k
}
1531
1532
struct render_variables_s
1533
{
1534
    const webvtt_region_t *p_region;
1535
    float i_left_offset;
1536
    float i_left;
1537
    float i_top_offset;
1538
    float i_top;
1539
};
1540
1541
static text_segment_t *ConvertRubyNodeToSegment( const webvtt_dom_node_t *p_node )
1542
2.34k
{
1543
2.34k
    text_segment_ruby_t *p_ruby = NULL;
1544
2.34k
    text_segment_ruby_t **pp_rt_append = &p_ruby;
1545
1546
2.34k
    const char *psz_base = NULL;
1547
1548
6.68k
    for( ; p_node ; p_node = p_node->p_next )
1549
4.34k
    {
1550
4.34k
        if( p_node->type == NODE_TEXT )
1551
889
        {
1552
889
            const webvtt_dom_text_t *p_textnode = (const webvtt_dom_text_t *) p_node;
1553
889
            psz_base = p_textnode->psz_text;
1554
889
        }
1555
3.45k
        else if( p_node->type == NODE_TAG )
1556
3.45k
        {
1557
3.45k
            const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *)p_node;
1558
3.45k
            if( !strcmp(p_tag->psz_tag, "rt") && p_tag->p_child &&
1559
788
                p_tag->p_child->type == NODE_TEXT && psz_base )
1560
85
            {
1561
85
                const webvtt_dom_text_t *p_rttext = (const webvtt_dom_text_t *)p_tag->p_child;
1562
85
                if ( p_rttext->psz_text )
1563
85
                {
1564
85
                    *pp_rt_append = text_segment_ruby_New( psz_base, p_rttext->psz_text );
1565
85
                    if( *pp_rt_append )
1566
85
                        pp_rt_append = &(*pp_rt_append)->p_next;
1567
85
                }
1568
85
            }
1569
3.45k
            psz_base = NULL;
1570
3.45k
        }
1571
4.34k
    }
1572
1573
2.34k
    return ( p_ruby ) ? text_segment_FromRuby( p_ruby ) : NULL;
1574
2.34k
}
1575
1576
static text_segment_t *ConvertNodesToSegments( decoder_t *p_dec,
1577
                                               struct render_variables_s *p_vars,
1578
                                               const webvtt_dom_cue_t *p_cue,
1579
                                               const webvtt_dom_node_t *p_node,
1580
                                               size_t depth )
1581
228k
{
1582
228k
    if (depth > MAX_TIMED_NODE_SEGMENTS_RECURSION)
1583
662
        return NULL;
1584
1585
228k
    text_segment_t *p_head = NULL;
1586
228k
    text_segment_t **pp_append = &p_head;
1587
559k
    for( ; p_node ; p_node = p_node->p_next )
1588
331k
    {
1589
456k
        while( *pp_append )
1590
125k
            pp_append = &((*pp_append)->p_next);
1591
1592
331k
        if( p_node->type == NODE_TEXT )
1593
144k
        {
1594
144k
            const webvtt_dom_text_t *p_textnode = (const webvtt_dom_text_t *) p_node;
1595
144k
            if( p_textnode->psz_text == NULL )
1596
0
                continue;
1597
1598
144k
            *pp_append = text_segment_New( p_textnode->psz_text );
1599
144k
            if( *pp_append )
1600
144k
            {
1601
144k
                (*pp_append)->style = ComputeStyle( p_dec, p_node );
1602
144k
            }
1603
144k
        }
1604
186k
        else if( p_node->type == NODE_TAG )
1605
186k
        {
1606
186k
            const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *)p_node;
1607
186k
            if( strcmp(p_tag->psz_tag, "ruby") )
1608
184k
                *pp_append = ConvertNodesToSegments( p_dec, p_vars, p_cue,
1609
184k
                                                     p_tag->p_child, depth+1 );
1610
2.34k
            else
1611
2.34k
                *pp_append = ConvertRubyNodeToSegment( p_tag->p_child );
1612
186k
        }
1613
331k
    }
1614
228k
    return p_head;
1615
228k
}
1616
1617
static text_segment_t *ConvertCueToSegments( decoder_t *p_dec,
1618
                                             struct render_variables_s *p_vars,
1619
                                             const webvtt_dom_cue_t *p_cue )
1620
44.4k
{
1621
44.4k
    return ConvertNodesToSegments( p_dec, p_vars, p_cue, p_cue->p_child, 0 );
1622
44.4k
}
1623
1624
static void ChainCueSegments( const webvtt_dom_cue_t *p_cue, text_segment_t *p_new,
1625
                              text_segment_t **pp_append )
1626
6.86k
{
1627
6.86k
    if( p_new )
1628
6.63k
    {
1629
6.63k
        bool b_newline = *pp_append;
1630
1631
87.8k
        while( *pp_append )
1632
81.2k
            pp_append = &((*pp_append)->p_next);
1633
1634
6.63k
        if( b_newline ) /* auto newlines */
1635
4.07k
        {
1636
4.07k
            *pp_append = text_segment_New( "\n" );
1637
4.07k
            if( *pp_append )
1638
4.07k
                pp_append = &((*pp_append)->p_next);
1639
4.07k
        }
1640
1641
6.63k
        if( p_cue->settings.vertical == WEBVTT_ALIGN_LEFT ) /* LTR */
1642
109
        {
1643
109
            *pp_append = text_segment_New( "\u2067" );
1644
109
            if( *pp_append )
1645
109
                pp_append = &((*pp_append)->p_next);
1646
109
        }
1647
1648
6.63k
        *pp_append = p_new;
1649
23.6k
        while( *pp_append )
1650
17.0k
            pp_append = &((*pp_append)->p_next);
1651
1652
6.63k
        if( p_cue->settings.vertical == WEBVTT_ALIGN_LEFT )
1653
109
        {
1654
109
            *pp_append = text_segment_New( "\u2069" );
1655
109
            if( *pp_append )
1656
109
                pp_append = &((*pp_append)->p_next);
1657
109
        }
1658
6.63k
    }
1659
6.86k
}
1660
1661
static text_segment_t * ConvertCuesToSegments( decoder_t *p_dec, vlc_tick_t i_nzstart, vlc_tick_t i_nzstop,
1662
                                               struct render_variables_s *p_vars,
1663
                                               const webvtt_dom_cue_t *p_cue )
1664
10.9k
{
1665
10.9k
    text_segment_t *p_segments = NULL;
1666
10.9k
    text_segment_t **pp_append = &p_segments;
1667
10.9k
    VLC_UNUSED(i_nzstop);
1668
1669
17.7k
    for( ; p_cue; p_cue = (const webvtt_dom_cue_t *) p_cue->p_next )
1670
6.86k
    {
1671
6.86k
        if( p_cue->type != NODE_CUE )
1672
0
            continue;
1673
1674
6.86k
        if( p_cue->i_nzstart > i_nzstart || p_cue->i_nzstop <= i_nzstart )
1675
0
            continue;
1676
1677
6.86k
        text_segment_t *p_new = ConvertCueToSegments( p_dec, p_vars, p_cue );
1678
6.86k
        ChainCueSegments( p_cue, p_new, pp_append );
1679
6.86k
    }
1680
10.9k
    return p_segments;
1681
10.9k
}
1682
1683
static void GetTimedTags( const webvtt_dom_node_t *p_node,
1684
                          vlc_tick_t i_nzstart, vlc_tick_t i_nzstop, vlc_array_t *p_times,
1685
                          size_t depth )
1686
173k
{
1687
173k
    if (depth > MAX_TIMED_TAGS_RECURSION)
1688
447
        return;
1689
1690
414k
    for( ; p_node; p_node = p_node->p_next )
1691
241k
    {
1692
241k
        switch( p_node->type )
1693
241k
        {
1694
111k
            case NODE_TAG:
1695
111k
            {
1696
111k
                const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *) p_node;
1697
111k
                if( p_tag->i_nzstart > -1 && p_tag->i_nzstart >= i_nzstart && p_tag->i_nzstart < i_nzstop )
1698
8.18k
                    (void) vlc_array_append( p_times, (void *) p_tag );
1699
111k
                GetTimedTags( p_tag->p_child, i_nzstart, i_nzstop, p_times, depth+1 );
1700
111k
            } break;
1701
8.87k
            case NODE_REGION:
1702
45.2k
            case NODE_CUE:
1703
45.2k
                GetTimedTags( webvtt_domnode_getFirstChild( p_node ),
1704
45.2k
                              i_nzstart, i_nzstop, p_times, depth+1 );
1705
45.2k
                break;
1706
85.0k
            default:
1707
85.0k
                break;
1708
241k
        }
1709
241k
    }
1710
172k
}
1711
1712
static void CreateSpuOrNewUpdaterRegion( decoder_t *p_dec,
1713
                                         subpicture_t **pp_spu,
1714
                                         substext_updater_region_t **pp_updtregion )
1715
38.3k
{
1716
38.3k
    if( *pp_spu == NULL )
1717
18.3k
    {
1718
18.3k
        *pp_spu = decoder_NewSubpictureText( p_dec );
1719
18.3k
        if( *pp_spu )
1720
18.3k
        {
1721
18.3k
            subtext_updater_sys_t *p_spusys = (*pp_spu)->updater.sys;
1722
18.3k
            *pp_updtregion = &p_spusys->region;
1723
18.3k
        }
1724
18.3k
    }
1725
19.9k
    else
1726
19.9k
    {
1727
19.9k
        substext_updater_region_t *p_new =
1728
19.9k
                                SubpictureUpdaterSysRegionNew( );
1729
19.9k
        if( p_new )
1730
19.9k
        {
1731
19.9k
            SubpictureUpdaterSysRegionAdd( *pp_updtregion, p_new );
1732
19.9k
            *pp_updtregion = p_new;
1733
19.9k
        }
1734
19.9k
    }
1735
38.3k
}
1736
1737
static void ClearCSSStyles( webvtt_dom_node_t *p_node )
1738
4.09M
{
1739
4.09M
    if( webvtt_domnode_supportsCSSStyle( p_node ) )
1740
4.01M
        webvtt_domnode_setCSSStyle( p_node, NULL );
1741
4.09M
    webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_node );
1742
8.19M
    for ( ; p_child ; p_child = p_child->p_next )
1743
4.09M
        ClearCSSStyles( p_child );
1744
4.09M
}
1745
1746
#ifdef HAVE_CSS
1747
static void ApplyCSSRules( decoder_t *p_dec, const vlc_css_rule_t *p_rule,
1748
                           vlc_tick_t i_nzplaybacktime )
1749
19.0k
{
1750
19.0k
    decoder_sys_t *p_sys = p_dec->p_sys;
1751
1752
46.7k
    for ( ;  p_rule ; p_rule = p_rule->p_next )
1753
27.7k
    {
1754
27.7k
        vlc_array_t results;
1755
27.7k
        vlc_array_init( &results );
1756
1757
27.7k
        webvtt_domnode_SelectRuleNodes( (webvtt_dom_node_t *) p_sys->p_root,
1758
27.7k
                                        p_rule, i_nzplaybacktime, &results );
1759
1760
27.7k
        for( const vlc_css_declaration_t *p_decl = p_rule->p_declarations;
1761
46.8k
                                          p_decl; p_decl = p_decl->p_next )
1762
19.0k
        {
1763
105k
            for( size_t i=0; i<vlc_array_count(&results); i++ )
1764
86.6k
            {
1765
86.6k
                webvtt_dom_node_t *p_node = vlc_array_item_at_index( &results, i );
1766
86.6k
                if( !webvtt_domnode_supportsCSSStyle( p_node ) )
1767
24.5k
                    continue;
1768
1769
62.1k
                text_style_t *p_style = webvtt_domnode_getCSSStyle( p_node );
1770
62.1k
                if( !p_style )
1771
32.8k
                {
1772
32.8k
                    p_style = text_style_Create( STYLE_NO_DEFAULTS );
1773
32.8k
                    webvtt_domnode_setCSSStyle( p_node, p_style );
1774
32.8k
                }
1775
1776
62.1k
                if( !p_style )
1777
0
                    continue;
1778
1779
62.1k
                webvtt_FillStyleFromCssDeclaration( p_decl, p_style );
1780
62.1k
            }
1781
19.0k
        }
1782
27.7k
        vlc_array_clear( &results );
1783
27.7k
    }
1784
19.0k
}
1785
#endif
1786
1787
static void RenderRegions( decoder_t *p_dec, vlc_tick_t i_nzstart, vlc_tick_t i_nzstop )
1788
19.0k
{
1789
19.0k
    subpicture_t *p_spu = NULL;
1790
19.0k
    substext_updater_region_t *p_updtregion = NULL;
1791
19.0k
    decoder_sys_t *p_sys = p_dec->p_sys;
1792
1793
19.0k
#ifdef HAVE_CSS
1794
19.0k
    ApplyCSSRules( p_dec, p_sys->p_css_rules, i_nzstart );
1795
19.0k
#endif
1796
1797
19.0k
    const webvtt_dom_cue_t *p_rlcue = NULL;
1798
19.0k
    for( const webvtt_dom_node_t *p_node = p_sys->p_root->p_child;
1799
67.5k
                                  p_node; p_node = p_node->p_next )
1800
48.5k
    {
1801
48.5k
        if( p_node->type == NODE_REGION )
1802
10.9k
        {
1803
10.9k
            const webvtt_region_t *p_vttregion = (const webvtt_region_t *) p_node;
1804
            /* Variables */
1805
10.9k
            struct render_variables_s v;
1806
10.9k
            v.p_region = p_vttregion;
1807
10.9k
            v.i_left_offset = p_vttregion->anchor_x * p_vttregion->f_width;
1808
10.9k
            v.i_left = p_vttregion->viewport_anchor_x - v.i_left_offset;
1809
10.9k
            v.i_top_offset = p_vttregion->anchor_y * p_vttregion->i_lines_max_scroll *
1810
10.9k
                             WEBVTT_DEFAULT_LINE_HEIGHT_VH / 100.0f;
1811
10.9k
            v.i_top = p_vttregion->viewport_anchor_y - v.i_top_offset;
1812
            /* !Variables */
1813
1814
10.9k
            text_segment_t *p_segments =
1815
10.9k
                    ConvertCuesToSegments( p_dec, i_nzstart, i_nzstop, &v,
1816
10.9k
                                          (const webvtt_dom_cue_t *)p_vttregion->p_child );
1817
10.9k
            if( !p_segments )
1818
8.33k
                continue;
1819
1820
2.56k
            CreateSpuOrNewUpdaterRegion( p_dec, &p_spu, &p_updtregion );
1821
2.56k
            if( !p_spu || !p_updtregion )
1822
0
            {
1823
0
                text_segment_ChainDelete( p_segments );
1824
0
                continue;
1825
0
            }
1826
1827
2.56k
            p_updtregion->b_absolute = false; /* can't be absolute as snap to lines can overlap ! */
1828
2.56k
            p_updtregion->b_in_window = false;
1829
2.56k
            p_updtregion->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
1830
2.56k
            p_updtregion->inner_align = GetCueTextAlignment( (const webvtt_dom_cue_t *)p_vttregion->p_child );
1831
2.56k
            p_updtregion->origin.x = v.i_left;
1832
2.56k
            p_updtregion->origin.y = v.i_top;
1833
2.56k
            p_updtregion->extent.x = p_vttregion->f_width;
1834
1835
2.56k
            p_updtregion->flags = UPDT_REGION_ORIGIN_X_IS_RATIO|UPDT_REGION_ORIGIN_Y_IS_RATIO
1836
2.56k
                                | UPDT_REGION_EXTENT_X_IS_RATIO;
1837
2.56k
            p_updtregion->p_segments = p_segments;
1838
2.56k
        }
1839
37.6k
        else if ( p_node->type == NODE_CUE )
1840
37.6k
        {
1841
37.6k
            if( p_rlcue == NULL )
1842
18.1k
                p_rlcue = ( const webvtt_dom_cue_t * ) p_node;
1843
37.6k
        }
1844
48.5k
    }
1845
1846
    /* regionless cues */
1847
19.0k
    if ( p_rlcue )
1848
18.1k
    {
1849
        /* Variables */
1850
18.1k
        struct render_variables_s v;
1851
18.1k
        v.p_region = NULL;
1852
18.1k
        v.i_left_offset = 0.0;
1853
18.1k
        v.i_left = 0.0;
1854
18.1k
        v.i_top_offset = 0.0;
1855
18.1k
        v.i_top = 0.0;
1856
        /* !Variables */
1857
1858
55.7k
        for( const webvtt_dom_cue_t *p_cue = p_rlcue; p_cue;
1859
37.6k
             p_cue = (const webvtt_dom_cue_t *) p_cue->p_next )
1860
37.6k
        {
1861
37.6k
            if( p_cue->type != NODE_CUE )
1862
0
                continue;
1863
1864
37.6k
            if( p_cue->i_nzstart > i_nzstart || p_cue->i_nzstop <= i_nzstart )
1865
0
                continue;
1866
1867
37.6k
            text_segment_t *p_segments = ConvertCueToSegments( p_dec, &v, p_cue );
1868
37.6k
            if( !p_segments )
1869
1.79k
                continue;
1870
1871
35.8k
            CreateSpuOrNewUpdaterRegion( p_dec, &p_spu, &p_updtregion );
1872
35.8k
            if( !p_updtregion )
1873
0
            {
1874
0
                text_segment_ChainDelete( p_segments );
1875
0
                continue;
1876
0
            }
1877
1878
            /* can't be absolute as snap to lines can overlap ! */
1879
35.8k
            p_updtregion->b_absolute = false; p_updtregion->b_in_window = false;
1880
35.8k
            if( p_cue->settings.line.b_auto )
1881
32.5k
            {
1882
32.5k
                p_updtregion->align = SUBPICTURE_ALIGN_BOTTOM;
1883
32.5k
            }
1884
3.27k
            else
1885
3.27k
            {
1886
3.27k
                webvtt_rect_t rect = { 0,0,0,0 };
1887
3.27k
                webvtt_get_cueboxrect( &p_cue->settings, &rect );
1888
3.27k
                p_updtregion->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
1889
3.27k
                p_updtregion->origin.x = rect.x;
1890
3.27k
                p_updtregion->origin.y = rect.y;
1891
3.27k
                p_updtregion->extent.x = rect.w;
1892
3.27k
                p_updtregion->extent.y = rect.h;
1893
3.27k
                p_updtregion->flags |= (UPDT_REGION_ORIGIN_X_IS_RATIO|UPDT_REGION_ORIGIN_Y_IS_RATIO|
1894
3.27k
                                        UPDT_REGION_EXTENT_X_IS_RATIO|UPDT_REGION_EXTENT_Y_IS_RATIO);
1895
3.27k
            }
1896
1897
35.8k
            p_updtregion->inner_align = GetCueTextAlignment( p_cue );
1898
35.8k
            p_updtregion->p_segments = p_segments;
1899
35.8k
        }
1900
18.1k
    }
1901
1902
19.0k
    if( p_spu )
1903
18.3k
    {
1904
18.3k
        p_spu->i_start = VLC_TICK_0 + i_nzstart;
1905
18.3k
        p_spu->i_stop = VLC_TICK_0 + i_nzstop;
1906
18.3k
        p_spu->b_ephemer  = true; /* !important */
1907
1908
18.3k
        subtext_updater_sys_t *p_spu_sys = p_spu->updater.sys;
1909
18.3k
        p_spu_sys->p_default_style->f_font_relsize = WEBVTT_DEFAULT_LINE_HEIGHT_VH /
1910
18.3k
                                                     WEBVTT_LINE_TO_HEIGHT_RATIO;
1911
18.3k
        decoder_QueueSub( p_dec, p_spu );
1912
18.3k
    }
1913
19.0k
}
1914
1915
static int timedtagsArrayCmp( const void *a, const void *b )
1916
17.6k
{
1917
17.6k
    const webvtt_dom_tag_t *ta = *((const webvtt_dom_tag_t **) a);
1918
17.6k
    const webvtt_dom_tag_t *tb = *((const webvtt_dom_tag_t **) b);
1919
17.6k
    const int64_t result = ta->i_nzstart - tb->i_nzstart;
1920
17.6k
    return result == 0 ? 0 : result > 0 ? 1 : -1;
1921
17.6k
}
1922
1923
static void Render( decoder_t *p_dec, vlc_tick_t i_nzstart, vlc_tick_t i_nzstop )
1924
16.5k
{
1925
16.5k
    decoder_sys_t *p_sys = p_dec->p_sys;
1926
1927
16.5k
    vlc_array_t timedtags;
1928
16.5k
    vlc_array_init( &timedtags );
1929
1930
16.5k
    GetTimedTags( p_sys->p_root->p_child, i_nzstart, i_nzstop, &timedtags, 0 );
1931
16.5k
    if( timedtags.i_count )
1932
2.14k
        qsort( timedtags.pp_elems, timedtags.i_count, sizeof(*timedtags.pp_elems), timedtagsArrayCmp );
1933
1934
16.5k
    vlc_tick_t i_subnzstart = i_nzstart;
1935
24.6k
    for( size_t i=0; i<timedtags.i_count; i++ )
1936
8.18k
    {
1937
8.18k
         const webvtt_dom_tag_t *p_tag =
1938
8.18k
                 (const webvtt_dom_tag_t *) vlc_array_item_at_index( &timedtags, i );
1939
8.18k
         if( p_tag->i_nzstart != i_subnzstart ) /* might be duplicates */
1940
4.39k
         {
1941
4.39k
             if( i > 0 )
1942
2.46k
                 ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
1943
4.39k
             RenderRegions( p_dec, i_subnzstart, p_tag->i_nzstart );
1944
4.39k
             i_subnzstart = p_tag->i_nzstart;
1945
4.39k
         }
1946
8.18k
    }
1947
16.5k
    if( i_subnzstart != i_nzstop )
1948
14.6k
    {
1949
14.6k
        if( i_subnzstart != i_nzstart )
1950
2.00k
            ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
1951
14.6k
        RenderRegions( p_dec, i_subnzstart, i_nzstop );
1952
14.6k
    }
1953
1954
16.5k
    vlc_array_clear( &timedtags );
1955
16.5k
}
1956
1957
static int ProcessISOBMFF( decoder_t *p_dec,
1958
                           const uint8_t *p_buffer, size_t i_buffer,
1959
                           vlc_tick_t i_nzstart, vlc_tick_t i_nzstop )
1960
16.5k
{
1961
16.5k
    decoder_sys_t *p_sys = p_dec->p_sys;
1962
16.5k
    mp4_box_iterator_t it;
1963
16.5k
    mp4_box_iterator_Init( &it, p_buffer, i_buffer );
1964
555k
    while( mp4_box_iterator_Next( &it ) )
1965
539k
    {
1966
539k
        if( it.i_type == ATOM_vttc || it.i_type == ATOM_vttx )
1967
539k
        {
1968
539k
            webvtt_dom_cue_t *p_cue = webvtt_dom_cue_New( i_nzstart, i_nzstop );
1969
539k
            if( !p_cue )
1970
0
                continue;
1971
1972
539k
            mp4_box_iterator_t vtcc;
1973
539k
            mp4_box_iterator_Init( &vtcc, it.p_payload, it.i_payload );
1974
1.61M
            while( mp4_box_iterator_Next( &vtcc ) )
1975
1.07M
            {
1976
1.07M
                char *psz = NULL;
1977
1.07M
                switch( vtcc.i_type )
1978
1.07M
                {
1979
385k
                    case ATOM_iden:
1980
385k
                        free( p_cue->psz_id );
1981
385k
                        p_cue->psz_id = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
1982
385k
                        break;
1983
148k
                    case ATOM_sttg:
1984
148k
                    {
1985
148k
                        psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
1986
148k
                        if( psz )
1987
148k
                            webvtt_cue_settings_Parse( &p_cue->settings, psz );
1988
148k
                    } break;
1989
536k
                    case ATOM_payl:
1990
536k
                    {
1991
536k
                        psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
1992
536k
                        if( psz )
1993
536k
                            ProcessCue( p_dec, psz, p_cue );
1994
536k
                    } break;
1995
1.07M
                }
1996
1.07M
                free( psz );
1997
1.07M
            }
1998
1999
539k
            webvtt_region_t *p_region = webvtt_region_GetByID( p_sys,
2000
539k
                                                               p_cue->settings.psz_region );
2001
539k
            webvtt_dom_cue_t *p_existingcue = webvtt_domnode_getCue( p_region ? p_region->p_child
2002
539k
                                                                              : p_sys->p_root->p_child,
2003
539k
                                                                     p_cue->i_nzstart,
2004
539k
                                                                     p_cue->psz_id );
2005
539k
            if( p_existingcue )
2006
501k
            {
2007
501k
                webvtt_domnode_mergeCues( p_existingcue, p_cue );
2008
501k
            }
2009
37.3k
            else
2010
37.3k
            {
2011
37.3k
                if( p_region )
2012
7.02k
                {
2013
7.02k
                    webvtt_region_AddCue( p_region, p_cue );
2014
7.02k
                    assert( p_region->p_child );
2015
7.02k
                }
2016
30.3k
                else
2017
30.3k
                {
2018
30.3k
                    webvtt_domnode_AppendLast( &p_sys->p_root->p_child, p_cue );
2019
30.3k
                    p_cue->p_parent = (webvtt_dom_node_t *) p_sys->p_root;
2020
30.3k
                }
2021
37.3k
            }
2022
539k
        }
2023
539k
    }
2024
16.5k
    return 0;
2025
16.5k
}
2026
2027
struct parser_ctx
2028
{
2029
    webvtt_region_t *p_region;
2030
#ifdef HAVE_CSS
2031
    struct vlc_memstream css;
2032
    bool b_css_memstream_opened;
2033
#endif
2034
    decoder_t *p_dec;
2035
};
2036
2037
static void ParserHeaderHandler( void *priv, enum webvtt_header_line_e s,
2038
                                 bool b_new, const char *psz_line )
2039
133k
{
2040
133k
    struct parser_ctx *ctx = (struct parser_ctx *)priv;
2041
133k
    decoder_t *p_dec = ctx->p_dec;
2042
133k
    decoder_sys_t *p_sys = p_dec->p_sys;
2043
2044
133k
    if( b_new || !psz_line /* commit */ )
2045
22.9k
    {
2046
22.9k
        if( ctx->p_region )
2047
4.07k
        {
2048
4.07k
            if( ctx->p_region->psz_id )
2049
2.84k
            {
2050
2.84k
                webvtt_domnode_AppendLast( &p_sys->p_root->p_child, ctx->p_region );
2051
2.84k
                ctx->p_region->p_parent = (webvtt_dom_node_t *) p_sys->p_root;
2052
2.84k
                msg_Dbg( p_dec, "added new region %s", ctx->p_region->psz_id );
2053
2.84k
            }
2054
            /* incomplete region decl (no id at least) */
2055
1.22k
            else webvtt_region_Delete( ctx->p_region );
2056
4.07k
            ctx->p_region = NULL;
2057
4.07k
        }
2058
18.8k
#ifdef HAVE_CSS
2059
18.8k
        else if( ctx->b_css_memstream_opened )
2060
10.7k
        {
2061
10.7k
            if( vlc_memstream_close( &ctx->css ) == 0 )
2062
10.7k
            {
2063
10.7k
                vlc_css_parser_t p;
2064
10.7k
                vlc_css_parser_Init(&p);
2065
10.7k
                vlc_css_parser_ParseBytes( &p,
2066
10.7k
                                          (const uint8_t *) ctx->css.ptr,
2067
10.7k
                                           ctx->css.length );
2068
#  ifdef CSS_PARSER_DEBUG
2069
                vlc_css_parser_Debug( &p );
2070
#  endif
2071
10.7k
                vlc_css_rule_t **pp_append = &p_sys->p_css_rules;
2072
180k
                while( *pp_append )
2073
169k
                    pp_append = &((*pp_append)->p_next);
2074
10.7k
                *pp_append = p.rules.p_first;
2075
10.7k
                p.rules.p_first = NULL;
2076
2077
10.7k
                vlc_css_parser_Clean(&p);
2078
10.7k
                free( ctx->css.ptr );
2079
10.7k
            }
2080
10.7k
        }
2081
22.9k
#endif
2082
2083
22.9k
        if( !psz_line )
2084
8.17k
            return;
2085
2086
14.7k
        if( b_new )
2087
14.7k
        {
2088
14.7k
            if( s == WEBVTT_HEADER_REGION )
2089
4.07k
                ctx->p_region = webvtt_region_New();
2090
10.7k
#ifdef HAVE_CSS
2091
10.7k
            else if( s == WEBVTT_HEADER_STYLE )
2092
10.7k
                ctx->b_css_memstream_opened = !vlc_memstream_open( &ctx->css );
2093
14.7k
#endif
2094
14.7k
            return;
2095
14.7k
        }
2096
14.7k
    }
2097
2098
110k
    if( s == WEBVTT_HEADER_REGION && ctx->p_region )
2099
26.9k
        webvtt_region_Parse( ctx->p_region, (char*) psz_line );
2100
83.8k
#ifdef HAVE_CSS
2101
83.8k
    else if( s == WEBVTT_HEADER_STYLE && ctx->b_css_memstream_opened )
2102
83.8k
    {
2103
83.8k
        vlc_memstream_puts( &ctx->css, psz_line );
2104
83.8k
        vlc_memstream_putc( &ctx->css, '\n' );
2105
83.8k
    }
2106
110k
#endif
2107
110k
}
2108
2109
static void LoadExtradata( decoder_t *p_dec )
2110
8.17k
{
2111
8.17k
    stream_t *p_stream = vlc_stream_MemoryNew( p_dec,
2112
8.17k
                                               p_dec->fmt_in->p_extra,
2113
8.17k
                                               p_dec->fmt_in->i_extra,
2114
8.17k
                                               true );
2115
8.17k
    if( !p_stream )
2116
0
        return;
2117
2118
8.17k
   struct parser_ctx ctx;
2119
8.17k
#ifdef HAVE_CSS
2120
8.17k
   ctx.b_css_memstream_opened = false;
2121
8.17k
#endif
2122
8.17k
   ctx.p_region = NULL;
2123
8.17k
   ctx.p_dec = p_dec;
2124
8.17k
   webvtt_text_parser_t *p_parser =
2125
8.17k
           webvtt_text_parser_New( &ctx, NULL, NULL, ParserHeaderHandler );
2126
8.17k
   if( p_parser )
2127
8.17k
   {
2128
8.17k
        char *psz_line;
2129
151k
        while( (psz_line = vlc_stream_ReadLine( p_stream )) )
2130
142k
            webvtt_text_parser_Feed( p_parser, psz_line );
2131
8.17k
        webvtt_text_parser_Delete( p_parser );
2132
        /* commit using null */
2133
8.17k
        ParserHeaderHandler( &ctx, 0, false, NULL );
2134
8.17k
   }
2135
2136
8.17k
    vlc_stream_Delete( p_stream );
2137
8.17k
}
2138
2139
/****************************************************************************
2140
 * Flush:
2141
 ****************************************************************************/
2142
static void Flush( decoder_t *p_dec )
2143
8.65k
{
2144
8.65k
    decoder_sys_t *p_sys = p_dec->p_sys;
2145
8.65k
    ClearCuesByTime( &p_sys->p_root->p_child, VLC_TICK_MAX );
2146
8.65k
}
2147
2148
/****************************************************************************
2149
 * DecodeBlock: decoder data entry point
2150
 ****************************************************************************/
2151
static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
2152
41.1k
{
2153
41.1k
    if( p_block == NULL ) /* No Drain */
2154
24.6k
        return VLCDEC_SUCCESS;
2155
2156
16.5k
    decoder_sys_t *p_sys = p_dec->p_sys;
2157
2158
16.5k
    vlc_tick_t i_nzstart = p_block->i_pts - VLC_TICK_0;
2159
16.5k
    vlc_tick_t i_nzstop = i_nzstart + p_block->i_length;
2160
2161
16.5k
    if( p_block->i_flags & BLOCK_FLAG_DISCONTINUITY )
2162
8.65k
        Flush( p_dec );
2163
7.84k
    else
2164
7.84k
        ClearCuesByTime( &p_sys->p_root->p_child, i_nzstart );
2165
2166
16.5k
    ProcessISOBMFF( p_dec, p_block->p_buffer, p_block->i_buffer,
2167
16.5k
                    i_nzstart, i_nzstop );
2168
2169
16.5k
    Render( p_dec, i_nzstart, i_nzstop );
2170
2171
16.5k
    block_Release( p_block );
2172
16.5k
    return VLCDEC_SUCCESS;
2173
41.1k
}
2174
2175
/*****************************************************************************
2176
 * webvtt_CloseDecoder: clean up the decoder
2177
 *****************************************************************************/
2178
void webvtt_CloseDecoder( vlc_object_t *p_this )
2179
8.18k
{
2180
8.18k
    decoder_t *p_dec = (decoder_t *)p_this;
2181
8.18k
    decoder_sys_t *p_sys = p_dec->p_sys;
2182
2183
8.18k
    webvtt_domnode_ChainDelete( (webvtt_dom_node_t *) p_sys->p_root );
2184
2185
8.18k
#ifdef HAVE_CSS
2186
8.18k
    vlc_css_rules_Delete( p_sys->p_css_rules );
2187
8.18k
#endif
2188
2189
8.18k
    free( p_sys );
2190
8.18k
}
2191
2192
/*****************************************************************************
2193
 * webvtt_OpenDecoder: probe the decoder and return score
2194
 *****************************************************************************/
2195
int webvtt_OpenDecoder( vlc_object_t *p_this )
2196
8.39k
{
2197
8.39k
    decoder_t *p_dec = (decoder_t*)p_this;
2198
8.39k
    decoder_sys_t *p_sys;
2199
2200
8.39k
    if( p_dec->fmt_in->i_codec != VLC_CODEC_WEBVTT )
2201
210
        return VLC_EGENERIC;
2202
2203
    /* Allocate the memory needed to store the decoder's structure */
2204
8.18k
    p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
2205
8.18k
    if( unlikely( p_sys == NULL ) )
2206
0
        return VLC_ENOMEM;
2207
2208
8.18k
    p_sys->p_root = webvtt_dom_tag_New( NULL );
2209
8.18k
    if( !p_sys->p_root )
2210
0
    {
2211
0
        free( p_sys );
2212
0
        return VLC_ENOMEM;
2213
0
    }
2214
8.18k
    p_sys->p_root->psz_tag = strdup( "video" );
2215
2216
8.18k
    p_dec->pf_decode = DecodeBlock;
2217
8.18k
    p_dec->pf_flush  = Flush;
2218
2219
8.18k
    if( p_dec->fmt_in->i_extra )
2220
8.17k
        LoadExtradata( p_dec );
2221
2222
8.18k
    return VLC_SUCCESS;
2223
8.18k
}