Coverage Report

Created: 2026-03-07 07:40

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