Coverage Report

Created: 2026-06-09 09:09

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
202k
#define MAX_TIMED_TAGS_RECURSION           50
54
// maximum recursions in ConvertNodesToSegments()
55
239k
#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
24.3k
#define WEBVTT_REGION_LINES_COUNT          18
66
22.5k
#define WEBVTT_DEFAULT_LINE_HEIGHT_VH    5.33f
67
16.0k
#define WEBVTT_LINE_TO_HEIGHT_RATIO      1.06f
68
59.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
16.9k
{
175
16.9k
    char *psz_end;
176
16.9k
    float d = vlc_strtof_c( psz, &psz_end );
177
16.9k
    if( d >= 0.0 && d <= 100.0 && *psz_end == '%' )
178
3.90k
        *value = d / 100.0;
179
16.9k
    return psz_end != psz;
180
16.9k
}
181
182
static bool parse_percent_tuple( const char *psz, float *x, float *y )
183
2.61k
{
184
2.61k
    char *psz_end;
185
2.61k
    float a = vlc_strtof_c( psz, &psz_end );
186
2.61k
    if( psz_end != psz &&
187
2.39k
        a >= 0.0 && a <= 100.0 && psz_end && *psz_end == '%' )
188
1.46k
    {
189
1.46k
        psz = strchr( psz_end, ',' );
190
1.46k
        if( psz )
191
1.23k
        {
192
1.23k
            float b = vlc_strtof_c( ++psz, &psz_end );
193
1.23k
            if( psz_end != psz &&
194
1.02k
                b >= 0.0 && b <= 100.0 && psz_end && *psz_end == '%' )
195
403
            {
196
403
                *x = a / 100.0;
197
403
                *y = b / 100.0;
198
403
                return true;
199
403
            }
200
1.23k
        }
201
1.46k
    }
202
2.21k
    return false;
203
2.61k
}
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
5.08k
{
213
5.08k
    float extent;
214
5.08k
    float indent_anchor_position;
215
5.08k
    enum webvtt_align_e alignment_on_indent_anchor;
216
217
    /* Position of top or left depending on writing direction */
218
5.08k
    float line_offset;
219
5.08k
    if( !p_settings->line.b_auto ) /* numerical */
220
5.08k
    {
221
5.08k
        if( p_settings->b_snap_to_lines ) /* line # */
222
2.55k
            line_offset = p_settings->line.value /
223
2.55k
                          (WEBVTT_REGION_LINES_COUNT * WEBVTT_LINE_TO_HEIGHT_RATIO);
224
2.52k
        else
225
2.52k
            line_offset = p_settings->line.value;
226
5.08k
    }
227
0
    else line_offset = 1.0;
228
229
5.08k
    if( p_settings->position < 0 )
230
4.36k
    {
231
4.36k
        if( p_settings->align == WEBVTT_ALIGN_LEFT )
232
251
            indent_anchor_position = 0;
233
4.11k
        else if( p_settings->align == WEBVTT_ALIGN_RIGHT )
234
237
            indent_anchor_position = 1.0;
235
3.87k
        else
236
3.87k
            indent_anchor_position = 0.5; /* center */
237
4.36k
    }
238
719
    else indent_anchor_position = p_settings->position;
239
240
5.08k
    if( p_settings->positionalign == WEBVTT_ALIGN_AUTO )
241
4.51k
    {
242
        /* text align */
243
4.51k
        if( p_settings->align == WEBVTT_ALIGN_LEFT ||
244
4.15k
            p_settings->align == WEBVTT_ALIGN_RIGHT )
245
593
            alignment_on_indent_anchor = p_settings->align;
246
3.92k
        else
247
3.92k
            alignment_on_indent_anchor = WEBVTT_ALIGN_CENTER;
248
4.51k
    }
249
570
    else alignment_on_indent_anchor = p_settings->positionalign;
250
251
5.08k
    if( !p_settings->size.b_auto )
252
1.02k
        extent = p_settings->size.value;
253
4.05k
    else
254
4.05k
        extent = 0.0;
255
256
    /* apply */
257
258
    /* we need 100% or size for inner_align to work on writing direction */
259
5.08k
    if( p_settings->vertical == WEBVTT_ALIGN_AUTO ) /* Horizontal text */
260
4.50k
    {
261
4.50k
        p_rect->y = line_offset >= 0 ? line_offset : 1.0 + line_offset;
262
4.50k
        p_rect->w = (extent) ? extent : 1.0;
263
264
4.50k
        if( indent_anchor_position > 0 )
265
4.05k
        {
266
4.05k
            if( alignment_on_indent_anchor == WEBVTT_ALIGN_LEFT ||
267
3.94k
                alignment_on_indent_anchor == WEBVTT_ALIGN_START )
268
105
            {
269
105
                p_rect->x  = indent_anchor_position;
270
105
                p_rect->w -= p_rect->x;
271
105
            }
272
3.94k
            else if( alignment_on_indent_anchor == WEBVTT_ALIGN_CENTER )
273
3.69k
            {
274
3.69k
                p_rect->x  = indent_anchor_position - 0.5;
275
3.69k
                p_rect->w  = p_rect->x >= 0 ? 1.0 - p_rect->x: 1.0 + p_rect->x;
276
3.69k
            }
277
4.05k
        }
278
4.50k
    }
279
578
    else /* Vertical text, but no support in the text renderer */
280
578
    {
281
578
        if( p_settings->vertical == WEBVTT_ALIGN_RIGHT )
282
346
            p_rect->x = line_offset >= 0 ? 1.0 - line_offset : -line_offset;
283
232
        else
284
232
            p_rect->x = line_offset >= 0 ? line_offset : 1.0 + line_offset;
285
578
        p_rect->w = (extent >= 0) ? extent : 1.0;
286
287
578
        if( p_settings->vertical == WEBVTT_ALIGN_LEFT )
288
232
        {
289
232
            p_rect->y  = indent_anchor_position;
290
232
            p_rect->h  = 1.0 - p_rect->y;
291
232
        }
292
346
        else
293
346
        {
294
346
            p_rect->y  = 1.0 - indent_anchor_position;
295
346
            p_rect->h  = 1.0 - p_rect->y;
296
346
        }
297
578
    }
298
5.08k
}
299
300
static void webvtt_cue_settings_ParseTuple( webvtt_cue_settings_t *p_settings,
301
                                            const char *psz_key, const char *psz_value )
302
107k
{
303
107k
    if( !strcmp( psz_key, "vertical" ) )
304
1.46k
    {
305
1.46k
        if( !strcmp( psz_value, "rl" ) )
306
651
            p_settings->vertical = WEBVTT_ALIGN_RIGHT;
307
815
        else if( !strcmp( psz_value, "lr" ) )
308
582
            p_settings->vertical = WEBVTT_ALIGN_LEFT;
309
233
        else
310
233
            p_settings->vertical = WEBVTT_ALIGN_AUTO;
311
1.46k
    }
312
106k
    else if( !strcmp( psz_key, "line" ) )
313
11.4k
    {
314
11.4k
        p_settings->line.b_auto = false;
315
11.4k
        if( strchr( psz_value, '%' ) )
316
5.86k
        {
317
5.86k
            parse_percent( psz_value, &p_settings->line.value );
318
5.86k
            p_settings->b_snap_to_lines = false;
319
5.86k
        }
320
5.54k
        else
321
5.54k
            p_settings->line.value = vlc_strtof_c( psz_value, NULL );
322
        /* else auto */
323
324
11.4k
        const char *psz_align = strchr( psz_value, ',' );
325
11.4k
        if( psz_align++ )
326
2.57k
        {
327
2.57k
            if( !strcmp( psz_align, "center" ) )
328
240
                p_settings->linealign = WEBVTT_ALIGN_CENTER;
329
2.33k
            else if( !strcmp( psz_align, "end" ) )
330
1.41k
                p_settings->linealign = WEBVTT_ALIGN_END;
331
920
            else
332
920
                p_settings->linealign = WEBVTT_ALIGN_START;
333
2.57k
        }
334
11.4k
    }
335
94.6k
    else if( !strcmp( psz_key, "position" ) )
336
4.77k
    {
337
4.77k
        parse_percent( psz_value, &p_settings->position );
338
4.77k
        const char *psz_align = strchr( psz_value, ',' );
339
4.77k
        if( psz_align++ )
340
1.91k
        {
341
1.91k
            if( !strcmp( psz_align, "line-left" ) )
342
167
                p_settings->positionalign = WEBVTT_ALIGN_LEFT;
343
1.75k
            else if( !strcmp( psz_align, "line-right" ) )
344
320
                p_settings->positionalign = WEBVTT_ALIGN_RIGHT;
345
1.43k
            else if( !strcmp( psz_align, "center" ) )
346
601
                p_settings->positionalign = WEBVTT_ALIGN_CENTER;
347
830
            else
348
830
                p_settings->positionalign = WEBVTT_ALIGN_AUTO;
349
1.91k
        }
350
4.77k
    }
351
89.8k
    else if( !strcmp( psz_key, "size" ) )
352
4.95k
    {
353
4.95k
        parse_percent( psz_value, &p_settings->size.value );
354
4.95k
        p_settings->size.b_auto = false;
355
4.95k
    }
356
84.8k
    else if( !strcmp( psz_key, "region" ) )
357
24.4k
    {
358
24.4k
        free( p_settings->psz_region );
359
24.4k
        p_settings->psz_region = strdup( psz_value );
360
24.4k
    }
361
60.4k
    else if( !strcmp( psz_key, "align" ) )
362
19.3k
    {
363
19.3k
        if( !strcmp( psz_value, "start" ) )
364
1.21k
            p_settings->align = WEBVTT_ALIGN_START;
365
18.1k
        else  if( !strcmp( psz_value, "end" ) )
366
1.64k
            p_settings->align = WEBVTT_ALIGN_END;
367
16.4k
        else  if( !strcmp( psz_value, "left" ) )
368
14.0k
            p_settings->align = WEBVTT_ALIGN_LEFT;
369
2.44k
        else  if( !strcmp( psz_value, "right" ) )
370
1.14k
            p_settings->align = WEBVTT_ALIGN_RIGHT;
371
1.29k
        else
372
1.29k
            p_settings->align = WEBVTT_ALIGN_CENTER;
373
19.3k
    }
374
107k
}
375
376
static void webvtt_cue_settings_Parse( webvtt_cue_settings_t *p_settings,
377
                                       char *p_str )
378
210k
{
379
210k
    char *p_save;
380
210k
    char *psz_tuple;
381
210k
    do
382
527k
    {
383
527k
        psz_tuple = strtok_r( p_str, " ", &p_save );
384
527k
        p_str = NULL;
385
527k
        if( psz_tuple )
386
316k
        {
387
316k
            const char *psz_split = strchr( psz_tuple, ':' );
388
316k
            if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple )
389
107k
            {
390
107k
                char *psz_key = strndup( psz_tuple, psz_split - psz_tuple );
391
107k
                if( psz_key )
392
107k
                {
393
107k
                    webvtt_cue_settings_ParseTuple( p_settings, psz_key, psz_split + 1 );
394
107k
                    free( psz_key );
395
107k
                }
396
107k
            }
397
316k
        }
398
527k
    } while( psz_tuple );
399
210k
}
400
401
static void webvtt_cue_settings_Clean( webvtt_cue_settings_t *p_settings )
402
1.12M
{
403
1.12M
    free( p_settings->psz_region );
404
1.12M
}
405
406
static void webvtt_cue_settings_Init( webvtt_cue_settings_t *p_settings )
407
1.12M
{
408
1.12M
    p_settings->psz_region = NULL;
409
1.12M
    p_settings->vertical = WEBVTT_ALIGN_AUTO;
410
1.12M
    p_settings->b_snap_to_lines = true;
411
1.12M
    p_settings->line.b_auto = true;
412
1.12M
    p_settings->line.value = 1.0;
413
1.12M
    p_settings->linealign = WEBVTT_ALIGN_START;
414
1.12M
    p_settings->position = -1;
415
1.12M
    p_settings->positionalign = WEBVTT_ALIGN_AUTO;
416
1.12M
    p_settings->size.value = 1.0; /* 100% */
417
1.12M
    p_settings->size.b_auto = true;
418
1.12M
    p_settings->align = WEBVTT_ALIGN_CENTER;
419
1.12M
}
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
32.2k
{
462
152k
    while( *pp_append )
463
120k
        pp_append = &((*pp_append)->p_next);
464
32.2k
    *pp_append = p_node;
465
32.2k
}
466
467
#define webvtt_domnode_AppendLast( a, b ) \
468
32.2k
    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.58M
{
473
3.58M
    if( p_node->type == NODE_TAG )
474
2.32M
    {
475
2.32M
        webvtt_dom_tag_t *p_tag_node = (webvtt_dom_tag_t *) p_node;
476
2.32M
        text_style_Delete( p_tag_node->p_cssstyle );
477
2.32M
        free( p_tag_node->psz_attrs );
478
2.32M
        free( p_tag_node->psz_tag );
479
2.32M
    }
480
1.25M
    else if( p_node->type == NODE_TEXT )
481
1.25M
    {
482
1.25M
        webvtt_dom_text_t *p_text_node = (webvtt_dom_text_t *)p_node;
483
1.25M
        free( p_text_node->psz_text );
484
1.25M
    }
485
6.15k
    else if( p_node->type == NODE_CUE )
486
3.74k
    {
487
3.74k
        webvtt_dom_cue_t *p_cue_node = (webvtt_dom_cue_t *)p_node;
488
3.74k
        text_style_Delete( p_cue_node->p_cssstyle );
489
3.74k
        webvtt_cue_settings_Clean( &p_cue_node->settings );
490
3.74k
        free( p_cue_node->psz_id );
491
3.74k
    }
492
2.41k
    else if( p_node->type == NODE_REGION )
493
2.41k
    {
494
2.41k
        webvtt_region_t *p_region_node = (webvtt_region_t *)p_node;
495
2.41k
        text_style_Delete( p_region_node->p_cssstyle );
496
2.41k
        free( p_region_node->psz_id );
497
2.41k
    }
498
3.58M
    free( p_node );
499
3.58M
}
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.12M
{
505
1.12M
    vlc_array_t stack;
506
1.12M
    vlc_array_init( &stack );
507
508
4.70M
    while( p_node )
509
3.58M
    {
510
3.58M
        webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_node );
511
3.58M
        webvtt_dom_node_t *p_next = p_node->p_next;
512
        /* delete current node, then go to child, and then siblings */
513
3.58M
        webvtt_domnode_DeleteNode( p_node );
514
3.58M
        p_node = p_child;
515
3.58M
        if( p_next )
516
186k
        {
517
186k
            if( p_child ) /* go to child, process sibling later */
518
45.3k
                vlc_array_append( &stack, p_next );
519
141k
            else
520
141k
                p_node = p_next;
521
186k
        }
522
523
3.58M
        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
45.3k
            {
529
45.3k
                p_node = vlc_array_item_at_index( &stack, idx - 1 );
530
45.3k
                vlc_array_remove( &stack, idx - 1 );
531
45.3k
            }
532
1.16M
        }
533
3.58M
    }
534
535
1.12M
    vlc_array_clear( &stack );
536
1.12M
}
537
538
static webvtt_dom_text_t * webvtt_dom_text_New( webvtt_dom_node_t *p_parent )
539
1.25M
{
540
1.25M
    webvtt_dom_text_t *p_node = calloc( 1, sizeof(*p_node) );
541
1.25M
    if( p_node )
542
1.25M
    {
543
1.25M
        p_node->type = NODE_TEXT;
544
1.25M
        p_node->p_parent = p_parent;
545
1.25M
    }
546
1.25M
    return p_node;
547
1.25M
}
548
549
static webvtt_dom_tag_t * webvtt_dom_tag_New( webvtt_dom_node_t *p_parent )
550
2.32M
{
551
2.32M
    webvtt_dom_tag_t *p_node = calloc( 1, sizeof(*p_node) );
552
2.32M
    if( p_node )
553
2.32M
    {
554
2.32M
        p_node->i_nzstart = -1;
555
2.32M
        p_node->type = NODE_TAG;
556
2.32M
        p_node->p_parent = p_parent;
557
2.32M
    }
558
2.32M
    return p_node;
559
2.32M
}
560
561
static webvtt_dom_node_t * webvtt_domnode_getParentByTag( webvtt_dom_node_t *p_parent,
562
                                                         const char *psz_tag )
563
58.6k
{
564
139k
    for( ; p_parent ; p_parent = p_parent->p_parent )
565
100k
    {
566
100k
        if( p_parent->type == NODE_TAG )
567
100k
        {
568
100k
            webvtt_dom_tag_t *p_node = (webvtt_dom_tag_t *) p_parent;
569
100k
            if( p_node->psz_tag && psz_tag && !strcmp( p_node->psz_tag, psz_tag ) )
570
20.0k
                break;
571
100k
        }
572
100k
    }
573
58.6k
    return p_parent;
574
58.6k
}
575
576
static webvtt_dom_node_t * webvtt_domnode_getFirstChild( webvtt_dom_node_t *p_node )
577
7.78M
{
578
7.78M
    webvtt_dom_node_t *p_child = NULL;
579
7.78M
    switch( p_node->type )
580
7.78M
    {
581
86.4k
        case NODE_CUE:
582
86.4k
            p_child  = ((webvtt_dom_cue_t *)p_node)->p_child;
583
86.4k
            break;
584
36.0k
        case NODE_REGION:
585
36.0k
            p_child  = ((webvtt_region_t *)p_node)->p_child;
586
36.0k
            break;
587
6.22M
        case NODE_TAG:
588
6.22M
            p_child  = ((webvtt_dom_tag_t *)p_node)->p_child;
589
6.22M
            break;
590
1.43M
        default:
591
1.43M
            break;
592
7.78M
    }
593
7.78M
    return p_child;
594
7.78M
}
595
4.19M
#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.08M
{
599
1.08M
    p_dst->i_nzstop = p_src->i_nzstop;
600
    /* needs more work */
601
1.08M
    webvtt_dom_cue_Delete( p_src );
602
1.08M
}
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.12M
{
608
1.89M
    for( ; p_node ; p_node = p_node->p_next )
609
1.85M
    {
610
1.85M
        if( p_node->type != NODE_CUE )
611
317k
            continue;
612
1.54M
        webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *) p_node;
613
1.54M
        if( p_cue->i_nzstart != i_nzstart )
614
0
            continue;
615
1.54M
        if( !psz_id != !p_cue->psz_id ||
616
1.26M
            (psz_id && strcmp(psz_id, p_cue->psz_id)) )
617
459k
            continue;
618
1.08M
        return p_cue;
619
1.54M
    }
620
39.2k
    return NULL;
621
1.12M
}
622
623
#ifdef HAVE_CSS
624
static vlc_tick_t webvtt_domnode_GetPlaybackTime( const webvtt_dom_node_t *p_node, bool b_end )
625
181k
{
626
544k
    for( ; p_node; p_node = p_node->p_parent )
627
534k
    {
628
534k
        if( p_node->type == NODE_TAG )
629
377k
        {
630
377k
            vlc_tick_t i_nzstart = ((const webvtt_dom_tag_t *) p_node)->i_nzstart;
631
377k
            if( i_nzstart > -1 && !b_end )
632
91.7k
                return i_nzstart;
633
377k
        }
634
157k
        else if( p_node->type == NODE_CUE )
635
79.8k
        {
636
79.8k
            break;
637
79.8k
        }
638
534k
    }
639
89.2k
    if( p_node )
640
79.8k
        return b_end ? ((const webvtt_dom_cue_t *) p_node)->i_nzstop:
641
79.8k
                       ((const webvtt_dom_cue_t *) p_node)->i_nzstart;
642
9.44k
    return VLC_TICK_INVALID;
643
89.2k
}
644
645
static bool webvtt_domnode_Match_Class( const webvtt_dom_node_t *p_node, const char *psz )
646
22.2k
{
647
22.2k
    const size_t i_len = strlen( psz );
648
22.2k
    if( i_len && p_node->type == NODE_TAG )
649
13.6k
    {
650
13.6k
        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
651
16.0k
        for( const char *p = p_tagnode->psz_attrs; p && *p; p++ )
652
10.8k
        {
653
10.8k
            p = strstr( p, psz );
654
10.8k
            if( !p )
655
7.66k
                return false;
656
3.14k
            if( p > p_tagnode->psz_attrs && p[-1] == '.' && !isalnum(p[i_len]) )
657
728
                return true;
658
3.14k
        }
659
13.6k
    }
660
13.8k
    return false;
661
22.2k
}
662
663
static bool webvtt_domnode_Match_Id( const webvtt_dom_node_t *p_node, const char *psz_id )
664
10.3k
{
665
10.3k
    if( !psz_id )
666
0
        return false;
667
10.3k
    if( *psz_id == '#' )
668
10.3k
        psz_id++;
669
10.3k
    if( p_node->type == NODE_REGION )
670
567
        return ((webvtt_region_t *)p_node)->psz_id &&
671
567
                !strcmp( ((webvtt_region_t *)p_node)->psz_id, psz_id );
672
9.78k
    else if( p_node->type == NODE_CUE )
673
1.44k
        return ((webvtt_dom_cue_t *)p_node)->psz_id &&
674
1.32k
                !strcmp( ((webvtt_dom_cue_t *)p_node)->psz_id, psz_id );
675
8.33k
    return false;
676
10.3k
}
677
678
static bool webvtt_domnode_Match_Tag( const webvtt_dom_node_t *p_node, const char *psz_tag )
679
72.3k
{
680
72.3k
    if( p_node->type == NODE_TAG && psz_tag )
681
38.2k
    {
682
        /* special case, not allowed to match anywhere but root */
683
38.2k
        if( !strcmp(psz_tag, "video") && p_node->p_parent )
684
203
            return false;
685
38.0k
        return ((webvtt_dom_tag_t *)p_node)->psz_tag &&
686
38.0k
                !strcmp( ((webvtt_dom_tag_t *)p_node)->psz_tag, psz_tag );
687
38.2k
    }
688
34.0k
    else return false;
689
72.3k
}
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
206k
{
694
206k
    if( !strcmp(psz, "past") || !strcmp(psz, "future") )
695
181k
    {
696
181k
        vlc_tick_t i_nzstart = webvtt_domnode_GetPlaybackTime( p_node, false );
697
181k
        return ( *psz == 'p' ) ? i_nzstart < i_nzplaybacktime : i_nzstart > i_nzplaybacktime;
698
181k
    }
699
25.4k
    return false;
700
206k
}
701
702
static bool webvtt_domnode_Match_PseudoElement( const webvtt_dom_node_t *p_node, const char *psz )
703
59.0k
{
704
59.0k
    if( !strcmp(psz, "cue") )
705
48.1k
        return p_node->type == NODE_CUE;
706
10.9k
    else if( !strcmp(psz, "cue-region") )
707
3.36k
        return p_node->type == NODE_REGION;
708
7.57k
    return false;
709
59.0k
}
710
711
static bool MatchAttribute( const char *psz_attr, const char *psz_lookup, enum vlc_css_match_e match )
712
4.63k
{
713
4.63k
    switch( match )
714
4.63k
    {
715
4.63k
        case MATCH_EQUALS:
716
4.63k
            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
4.63k
    }
749
0
    return false;
750
4.63k
}
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
11.8k
{
755
11.8k
    if( p_node->type == NODE_TAG && p_matchsel )
756
7.95k
    {
757
7.95k
        const webvtt_dom_tag_t *p_tagnode = (webvtt_dom_tag_t *) p_node;
758
759
7.95k
        if( p_tagnode->psz_attrs != NULL &&
760
7.21k
          ( ( !strcmp( p_tagnode->psz_tag, "v" ) && !strcmp( psz, "voice" ) ) || /* v = only voice */
761
2.59k
            ( !strcmp( p_tagnode->psz_tag, "lang" ) && !strcmp( psz, "lang" ) ) ) )
762
4.63k
        {
763
4.63k
            const char *psz_start = NULL;
764
            /* skip classes decl */
765
30.6k
            for( const char *p = p_tagnode->psz_attrs; *p; p++ )
766
26.5k
            {
767
26.5k
                if( isspace(*p) )
768
1.28k
                {
769
1.28k
                    psz_start = p + 1;
770
1.28k
                }
771
25.2k
                else if( psz_start != NULL )
772
592
                {
773
592
                    break;
774
592
                }
775
26.5k
            }
776
777
4.63k
            if( psz_start == NULL || *psz_start == 0 )
778
4.04k
                psz_start = p_tagnode->psz_attrs;
779
780
4.63k
            return MatchAttribute( psz_start, p_matchsel->psz_name, p_matchsel->match );
781
4.63k
        }
782
7.95k
    }
783
7.17k
    return false;
784
11.8k
}
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
382k
{
789
382k
    switch( p_sel->type )
790
382k
    {
791
72.3k
        case SELECTOR_SIMPLE:
792
72.3k
            return webvtt_domnode_Match_Tag( p_node, p_sel->psz_name );
793
206k
        case SELECTOR_PSEUDOCLASS:
794
206k
            return webvtt_domnode_Match_PseudoClass( p_node, p_sel->psz_name,
795
206k
                                                     i_nzplaybacktime );
796
59.0k
        case SELECTOR_PSEUDOELEMENT:
797
59.0k
            return webvtt_domnode_Match_PseudoElement( p_node, p_sel->psz_name );
798
10.3k
        case SPECIFIER_ID:
799
10.3k
            return webvtt_domnode_Match_Id( p_node, p_sel->psz_name );
800
22.2k
        case SPECIFIER_CLASS:
801
22.2k
            return webvtt_domnode_Match_Class( p_node, p_sel->psz_name );
802
11.8k
        case SPECIFIER_ATTRIB:
803
11.8k
            return webvtt_domnode_Match_Attribute( p_node, p_sel->psz_name, p_sel->p_matchsel );
804
382k
    }
805
0
    return false;
806
382k
}
807
#endif
808
809
static text_style_t ** get_ppCSSStyle( webvtt_dom_node_t *p_node )
810
8.62M
{
811
8.62M
    switch( p_node->type )
812
8.62M
    {
813
164k
        case NODE_CUE:
814
164k
            return &((webvtt_dom_cue_t *)p_node)->p_cssstyle;
815
36.2k
        case NODE_REGION:
816
36.2k
            return &((webvtt_region_t *)p_node)->p_cssstyle;
817
8.21M
        case NODE_TAG:
818
8.21M
            return &((webvtt_dom_tag_t *)p_node)->p_cssstyle;
819
202k
        default:
820
202k
            return NULL;
821
8.62M
    }
822
8.62M
}
823
824
static text_style_t * webvtt_domnode_getCSSStyle( webvtt_dom_node_t *p_node )
825
1.09M
{
826
1.09M
    text_style_t **pp_style = get_ppCSSStyle( p_node );
827
1.09M
    if( pp_style )
828
957k
        return *pp_style;
829
142k
    return NULL;
830
1.09M
}
831
1.09M
#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
3.80M
{
835
3.80M
    return get_ppCSSStyle( p_node ) != NULL;
836
3.80M
}
837
838
static void webvtt_domnode_setCSSStyle( webvtt_dom_node_t *p_node, text_style_t *p_style )
839
3.72M
{
840
3.72M
    text_style_t **pp_style = get_ppCSSStyle( p_node );
841
3.72M
    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
3.72M
    if( *pp_style )
849
7.90k
        text_style_Delete( *pp_style );
850
3.72M
    *pp_style = p_style;
851
3.72M
}
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
401k
{
862
401k
    const webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_tree );
863
401k
    if( i_max_depth > 0 )
864
378k
    {
865
708k
        for( ; p_child; p_child = p_child->p_next )
866
329k
            webvtt_domnode_SelectNodesInTree( p_child, p_sel, i_max_depth - 1,
867
329k
                                              i_nzplaybacktime, p_results );
868
378k
    }
869
401k
}
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
25.1k
{
875
25.1k
    if( p_spec == NULL )
876
0
        return;
877
878
25.1k
    switch( p_spec->combinator )
879
25.1k
    {
880
6.72k
        case RELATION_DESCENDENT:
881
6.72k
            webvtt_domnode_SelectChildNodesInTree( p_node, p_spec, WEBVTT_MAX_DEPTH,
882
6.72k
                                                   i_nzplaybacktime, p_results );
883
6.72k
            break;
884
2.07k
        case RELATION_DIRECTADJACENT:
885
6.92k
            for( const webvtt_dom_node_t *p_adj = p_node->p_next; p_adj; p_adj = p_adj->p_next )
886
4.84k
                webvtt_domnode_SelectChildNodesInTree( p_adj, p_spec, 1,
887
4.84k
                                                       i_nzplaybacktime, p_results );
888
2.07k
            break;
889
8.06k
        case RELATION_INDIRECTADJACENT:
890
8.06k
            for( const webvtt_dom_node_t *p_adj = webvtt_domnode_getFirstChild( p_node->p_parent );
891
13.8k
                                          p_adj && p_adj != p_node; p_adj = p_adj->p_next )
892
5.76k
                webvtt_domnode_SelectChildNodesInTree( p_adj, p_spec, 1,
893
5.76k
                                                       i_nzplaybacktime, p_results );
894
8.06k
            break;
895
2.29k
        case RELATION_CHILD:
896
2.29k
            webvtt_domnode_SelectChildNodesInTree( p_node, p_spec, 1,
897
2.29k
                                                   i_nzplaybacktime, p_results );
898
2.29k
            break;
899
6.00k
        case RELATION_SELF:
900
6.00k
            webvtt_domnode_SelectNodesInTree( p_node, p_spec, WEBVTT_MAX_DEPTH,
901
6.00k
                                              i_nzplaybacktime, p_results );
902
25.1k
    }
903
25.1k
}
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
382k
{
909
382k
    if( p_node == NULL )
910
0
        return;
911
912
382k
    if( webvtt_domnode_MatchType( p_node, p_sel, i_nzplaybacktime ) )
913
96.1k
    {
914
96.1k
        if( p_sel->specifiers.p_first == NULL )
915
71.0k
        {
916
            /* End of matching, this node is part of results */
917
71.0k
            (void) vlc_array_append( p_results, (void *) p_node );
918
71.0k
        }
919
25.1k
        else webvtt_domnode_SelectNodesBySpeficier( p_node, p_sel->specifiers.p_first,
920
25.1k
                                                    i_nzplaybacktime, p_results );
921
96.1k
    }
922
923
    /* lookup other subnodes */
924
382k
    webvtt_domnode_SelectChildNodesInTree( p_node, p_sel, i_max_depth - 1,
925
382k
                                           i_nzplaybacktime, p_results );
926
382k
}
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
16.4k
{
931
16.4k
    if(!p_root || p_root->type != NODE_TAG)
932
0
        return;
933
16.4k
    const webvtt_dom_node_t *p_cues = ((const webvtt_dom_tag_t *)p_root)->p_child;
934
30.8k
    for( const vlc_css_selector_t *p_sel = p_rule->p_selectors; p_sel; p_sel = p_sel->p_next )
935
14.4k
    {
936
14.4k
        vlc_array_t tempresults;
937
14.4k
        vlc_array_init( &tempresults );
938
61.2k
        for( const webvtt_dom_node_t *p_node = p_cues; p_node; p_node = p_node->p_next )
939
46.8k
        {
940
46.8k
            webvtt_domnode_SelectNodesInTree( p_node, p_sel, WEBVTT_MAX_DEPTH,
941
46.8k
                                              i_nzplaybacktime, &tempresults );
942
46.8k
        }
943
85.4k
        for( size_t i=0; i<vlc_array_count(&tempresults); i++ )
944
71.0k
            (void) vlc_array_append( p_results, vlc_array_item_at_index( &tempresults, i ) );
945
14.4k
        vlc_array_clear( &tempresults );
946
14.4k
    }
947
16.4k
}
948
#endif
949
950
static inline bool IsEndTag( const char *psz )
951
6.92M
{
952
6.92M
    return psz[1] == '/';
953
6.92M
}
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.46M
{
958
3.46M
    psz = strchr( psz, '<' );
959
3.46M
    if( psz )
960
2.39M
    {
961
2.39M
        *ppsz_taglast = strchr( psz + 1, '>' );
962
2.39M
        if( *ppsz_taglast )
963
2.38M
        {
964
2.38M
            const size_t tagsize = *ppsz_taglast - psz + 1;
965
2.38M
            if( tagsize <= 3 )
966
2.15M
            {
967
2.15M
                if( tagsize < 2 || IsEndTag(psz) )
968
466
                    *ppsz_taglast = psz = NULL;
969
2.15M
            }
970
2.38M
        } else psz = NULL;
971
2.39M
    }
972
3.46M
    return psz;
973
3.46M
}
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.38M
{
978
2.38M
    psz_tag += IsEndTag( psz_tag ) ? 2 : 1;
979
2.38M
    const char *p = psz_tag;
980
2.38M
    *pi_tag = 0;
981
2.38M
    if( isalpha( *p ) )
982
1.65M
    {
983
1.65M
        while( isalnum( *p ) )
984
1.71M
        {
985
1.71M
            p++;
986
1.71M
            (*pi_tag)++;
987
1.71M
        }
988
1.65M
        while( isspace( *p ) )
989
71.3k
            p++;
990
1.65M
    }
991
2.38M
    *ppsz_attrs = p;
992
2.38M
    return psz_tag;
993
2.38M
}
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.12M
{
1000
1.12M
    webvtt_dom_cue_t *p_cue = calloc( 1, sizeof(*p_cue) );
1001
1.12M
    if( p_cue )
1002
1.12M
    {
1003
1.12M
        p_cue->type = NODE_CUE;
1004
1.12M
        p_cue->psz_id = NULL;
1005
1.12M
        p_cue->i_nzstart = i_nzstart;
1006
1.12M
        p_cue->i_nzstop = i_nzend;
1007
1.12M
        p_cue->p_child = NULL;
1008
1.12M
        p_cue->i_lines = 0;
1009
1.12M
        p_cue->p_cssstyle = NULL;
1010
1.12M
        webvtt_cue_settings_Init( &p_cue->settings );
1011
1.12M
    }
1012
1.12M
    return p_cue;
1013
1.12M
}
1014
1015
static void webvtt_dom_cue_ClearText( webvtt_dom_cue_t *p_cue )
1016
1.11M
{
1017
1.11M
    webvtt_domnode_ChainDelete( p_cue->p_child );
1018
1.11M
    p_cue->p_child = NULL;
1019
1.11M
    p_cue->i_lines = 0;
1020
1.11M
}
1021
1022
static void webvtt_dom_cue_Delete( webvtt_dom_cue_t *p_cue )
1023
1.11M
{
1024
1.11M
    text_style_Delete( p_cue->p_cssstyle );
1025
1.11M
    webvtt_dom_cue_ClearText( p_cue );
1026
1.11M
    webvtt_cue_settings_Clean( &p_cue->settings );
1027
1.11M
    free( p_cue->psz_id );
1028
1.11M
    free( p_cue );
1029
1.11M
}
1030
1031
/* reduces by one line */
1032
static unsigned webvtt_dom_cue_Reduced( webvtt_dom_cue_t *p_cue )
1033
320
{
1034
320
    if( p_cue->i_lines < 1 )
1035
320
        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
12.1k
{
1071
12.1k
    if( !strcmp( psz_key, "id" ) )
1072
2.98k
    {
1073
2.98k
        free( p_region->psz_id );
1074
2.98k
        p_region->psz_id = strdup( psz_value );
1075
2.98k
    }
1076
9.11k
    else if( !strcmp( psz_key, "width" ) )
1077
1.39k
    {
1078
1.39k
        parse_percent( psz_value, &p_region->f_width );
1079
1.39k
    }
1080
7.72k
    else if( !strcmp( psz_key, "regionanchor" ) )
1081
1.30k
    {
1082
1.30k
        parse_percent_tuple( psz_value, &p_region->anchor_x,
1083
1.30k
                                        &p_region->anchor_y );
1084
1.30k
    }
1085
6.41k
    else if( !strcmp( psz_key, "viewportanchor" ) )
1086
1.30k
    {
1087
1.30k
        parse_percent_tuple( psz_value, &p_region->viewport_anchor_x,
1088
1.30k
                                        &p_region->viewport_anchor_y );
1089
1.30k
    }
1090
5.11k
    else if( !strcmp( psz_key, "lines" ) )
1091
1.11k
    {
1092
1.11k
        int i = atoi( psz_value );
1093
1.11k
        if( i > 0 )
1094
755
            p_region->i_lines_max_scroll = __MIN(i, WEBVTT_REGION_LINES_COUNT);
1095
1.11k
    }
1096
3.99k
    else if( !strcmp( psz_key, "scroll" ) )
1097
674
    {
1098
674
        p_region->b_scroll_up = !strcmp( psz_value, "up" );
1099
674
    }
1100
12.1k
}
1101
1102
static void webvtt_region_Parse( webvtt_region_t *p_region, char *psz_line )
1103
18.5k
{
1104
18.5k
    char *p_save;
1105
18.5k
    char *psz_tuple;
1106
18.5k
    char *p_str = psz_line;
1107
18.5k
    do
1108
38.3k
    {
1109
38.3k
        psz_tuple = strtok_r( p_str, " ", &p_save );
1110
38.3k
        p_str = NULL;
1111
38.3k
        if( psz_tuple )
1112
19.7k
        {
1113
19.7k
            const char *psz_split = strchr( psz_tuple, ':' );
1114
19.7k
            if( psz_split && psz_split[1] != 0 && psz_split != psz_tuple )
1115
12.1k
            {
1116
12.1k
                char *psz_key = strndup( psz_tuple, psz_split - psz_tuple );
1117
12.1k
                if( psz_key )
1118
12.1k
                {
1119
12.1k
                    webvtt_region_ParseTuple( p_region, psz_key, psz_split + 1 );
1120
12.1k
                    free( psz_key );
1121
12.1k
                }
1122
12.1k
            }
1123
19.7k
        }
1124
38.3k
    } while( psz_tuple );
1125
18.5k
}
1126
1127
static unsigned webvtt_region_CountLines( const webvtt_region_t *p_region )
1128
11.4k
{
1129
11.4k
    unsigned i_lines = 0;
1130
11.4k
    for( const webvtt_dom_node_t *p_node = p_region->p_child;
1131
92.4k
                                  p_node; p_node = p_node->p_next )
1132
80.9k
    {
1133
80.9k
        assert( p_node->type == NODE_CUE );
1134
80.9k
        if( p_node->type != NODE_CUE )
1135
0
            continue;
1136
80.9k
        i_lines += ((const webvtt_dom_cue_t *)p_node)->i_lines;
1137
80.9k
    }
1138
11.4k
    return i_lines;
1139
11.4k
}
1140
1141
static void ClearCuesByTime( webvtt_dom_node_t **pp_next, vlc_tick_t i_nztime )
1142
32.6k
{
1143
73.1k
    while( *pp_next )
1144
40.5k
    {
1145
40.5k
        webvtt_dom_node_t *p_node = *pp_next;
1146
40.5k
        if( p_node )
1147
40.5k
        {
1148
40.5k
            if( p_node->type == NODE_CUE )
1149
33.3k
            {
1150
33.3k
                webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *)p_node;
1151
33.3k
                if( p_cue->i_nzstop <= i_nztime )
1152
33.3k
                {
1153
33.3k
                    *pp_next = p_node->p_next;
1154
33.3k
                    p_node->p_next = NULL;
1155
33.3k
                    webvtt_dom_cue_Delete( p_cue );
1156
33.3k
                    continue;
1157
33.3k
                }
1158
33.3k
            }
1159
7.16k
            else if( p_node->type == NODE_REGION )
1160
7.16k
            {
1161
7.16k
                webvtt_region_t *p_region = (webvtt_region_t *) p_node;
1162
7.16k
                ClearCuesByTime( &p_region->p_child, i_nztime );
1163
7.16k
            }
1164
7.16k
            pp_next = &p_node->p_next;
1165
7.16k
        }
1166
40.5k
    }
1167
32.6k
}
1168
1169
/* Remove top most line/cue for bottom insert */
1170
static bool webvtt_region_Reduce( webvtt_region_t *p_region )
1171
2.09k
{
1172
2.09k
    if( p_region->p_child )
1173
2.09k
    {
1174
2.09k
        assert( p_region->p_child->type == NODE_CUE );
1175
2.09k
        if( p_region->p_child->type != NODE_CUE )
1176
0
            return false;
1177
2.09k
        webvtt_dom_cue_t *p_cue = (webvtt_dom_cue_t *)p_region->p_child;
1178
2.09k
        if( p_cue->i_lines == 1 ||
1179
320
            webvtt_dom_cue_Reduced( p_cue ) < 1 )
1180
2.09k
        {
1181
2.09k
            p_region->p_child = p_cue->p_next;
1182
2.09k
            p_cue->p_next = NULL;
1183
2.09k
            webvtt_dom_cue_Delete( p_cue );
1184
2.09k
            return true;
1185
2.09k
        }
1186
2.09k
    }
1187
0
    return false;
1188
2.09k
}
1189
1190
static void webvtt_region_AddCue( webvtt_region_t *p_region,
1191
                                  webvtt_dom_cue_t *p_cue )
1192
9.39k
{
1193
9.39k
    webvtt_dom_node_t **pp_add = &p_region->p_child;
1194
62.0k
    while( *pp_add )
1195
52.6k
        pp_add = &((*pp_add)->p_next);
1196
9.39k
    *pp_add = (webvtt_dom_node_t *)p_cue;
1197
9.39k
    p_cue->p_parent = (webvtt_dom_node_t *)p_region;
1198
1199
9.39k
    for( ;; )
1200
11.4k
    {
1201
11.4k
        unsigned i_lines = webvtt_region_CountLines( p_region );
1202
11.4k
        if( i_lines > 0 &&
1203
10.9k
            ( i_lines > WEBVTT_REGION_LINES_COUNT ||
1204
10.1k
             (p_region->b_scroll_up && i_lines > p_region->i_lines_max_scroll)) )
1205
2.09k
        {
1206
2.09k
            if (!webvtt_region_Reduce( p_region )) /* scrolls up */
1207
0
                break;
1208
2.09k
        }
1209
9.39k
        else break;
1210
11.4k
    }
1211
9.39k
}
1212
1213
static void webvtt_region_Delete( webvtt_region_t *p_region )
1214
577
{
1215
577
    text_style_Delete( p_region->p_cssstyle );
1216
577
    webvtt_domnode_ChainDelete( p_region->p_child );
1217
577
    p_region->p_child = NULL;
1218
577
    free( p_region->psz_id );
1219
577
    free( p_region );
1220
577
}
1221
1222
static webvtt_region_t * webvtt_region_New( void )
1223
2.98k
{
1224
2.98k
    webvtt_region_t *p_region = malloc(sizeof(*p_region));
1225
2.98k
    if( p_region )
1226
2.98k
    {
1227
2.98k
        p_region->type = NODE_REGION;
1228
2.98k
        p_region->psz_id = NULL;
1229
2.98k
        p_region->p_next = NULL;
1230
2.98k
        p_region->f_width = 1.0; /* 100% */
1231
2.98k
        p_region->anchor_x = 0;
1232
2.98k
        p_region->anchor_y = 1.0; /* 100% */
1233
2.98k
        p_region->i_lines_max_scroll = 3;
1234
2.98k
        p_region->viewport_anchor_x = 0;
1235
2.98k
        p_region->viewport_anchor_y = 1.0; /* 100% */
1236
2.98k
        p_region->b_scroll_up = false;
1237
2.98k
        p_region->p_cssstyle = NULL;
1238
2.98k
        p_region->p_child = NULL;
1239
2.98k
    }
1240
2.98k
    return p_region;
1241
2.98k
}
1242
1243
static webvtt_region_t * webvtt_region_GetByID( decoder_sys_t *p_sys,
1244
                                                const char *psz_id )
1245
1.12M
{
1246
1.12M
    if( !psz_id )
1247
1.09M
        return NULL;
1248
24.0k
    for( webvtt_dom_node_t *p_node = p_sys->p_root->p_child;
1249
61.3k
                            p_node; p_node = p_node->p_next )
1250
51.9k
    {
1251
51.9k
        if( p_node->type == NODE_REGION )
1252
17.3k
        {
1253
17.3k
            webvtt_region_t *p_region = (webvtt_region_t *) p_node;
1254
17.3k
            if( p_region->psz_id && !strcmp( psz_id, p_region->psz_id ) )
1255
14.6k
                return p_region;
1256
17.3k
        }
1257
51.9k
    }
1258
9.40k
    return NULL;
1259
24.0k
}
1260
1261
/*****************************************************************************
1262
 *
1263
 *****************************************************************************/
1264
static char * DuplicateUnescaped( const char *psz )
1265
1.07M
{
1266
1.07M
    char *s = strdup( psz );
1267
1.07M
    if( s )
1268
1.07M
        vlc_xml_decode( s );
1269
1.07M
    return s;
1270
1.07M
}
1271
1272
static char * NDuplicateUnescaped( const char *psz, size_t len )
1273
2.71M
{
1274
2.71M
    char *s = strndup( psz, len );
1275
2.71M
    if( s )
1276
2.71M
        vlc_xml_decode( s );
1277
2.71M
    return s;
1278
2.71M
}
1279
1280
static unsigned CountNewLines( const char *psz )
1281
1.25M
{
1282
1.25M
    unsigned i = 0;
1283
2.67M
    while( psz && *psz )
1284
1.41M
        psz = strchr( psz + 1, '\n' );
1285
1.25M
    return i;
1286
1.25M
}
1287
1288
static webvtt_dom_node_t * CreateDomNodes( const char *psz_text, unsigned *pi_lines )
1289
1.11M
{
1290
1.11M
    webvtt_dom_node_t *p_head = NULL;
1291
1.11M
    webvtt_dom_node_t **pp_append = &p_head;
1292
1.11M
    webvtt_dom_node_t *p_parent = p_head;
1293
1.11M
    *pi_lines = 0;
1294
1295
3.49M
    while( *psz_text )
1296
3.46M
    {
1297
3.46M
        const char *psz_taglast;
1298
3.46M
        const char *psz_tag = FindNextTag( psz_text, &psz_taglast );
1299
3.46M
        if( psz_tag )
1300
2.38M
        {
1301
2.38M
            if( psz_tag - psz_text > 0 )
1302
173k
            {
1303
173k
                webvtt_dom_text_t *p_node = webvtt_dom_text_New( p_parent );
1304
173k
                if( p_node )
1305
173k
                {
1306
173k
                    p_node->psz_text = NDuplicateUnescaped( psz_text, psz_tag - psz_text );
1307
173k
                    *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text );
1308
173k
                    *pp_append = (webvtt_dom_node_t *) p_node;
1309
173k
                    pp_append = &p_node->p_next;
1310
173k
                }
1311
173k
            }
1312
1313
2.38M
            if( ! IsEndTag( psz_tag ) )
1314
2.32M
            {
1315
2.32M
                webvtt_dom_tag_t *p_node = webvtt_dom_tag_New( p_parent );
1316
2.32M
                if( p_node )
1317
2.32M
                {
1318
2.32M
                    const char *psz_attrs = NULL;
1319
2.32M
                    size_t i_name;
1320
2.32M
                    const char *psz_name = SplitTag( psz_tag, &i_name, &psz_attrs );
1321
2.32M
                    p_node->psz_tag = NDuplicateUnescaped( psz_name, i_name );
1322
2.32M
                    if( psz_attrs != psz_taglast )
1323
159k
                        p_node->psz_attrs = NDuplicateUnescaped( psz_attrs, psz_taglast - psz_attrs );
1324
                    /* <hh:mm::ss:fff> time tags */
1325
2.32M
                    if( p_node->psz_attrs && isdigit(p_node->psz_attrs[0]) )
1326
54.6k
                        (void) webvtt_scan_time( p_node->psz_attrs, &p_node->i_nzstart );
1327
2.32M
                    *pp_append = (webvtt_dom_node_t *) p_node;
1328
2.32M
                    p_parent = (webvtt_dom_node_t *) p_node;
1329
2.32M
                    pp_append = &p_node->p_child;
1330
2.32M
                }
1331
2.32M
            }
1332
60.3k
            else
1333
60.3k
            {
1334
60.3k
                if( p_parent )
1335
58.6k
                {
1336
58.6k
                    const char *psz_attrs = NULL;
1337
58.6k
                    size_t i_name;
1338
58.6k
                    const char *psz_name = SplitTag( psz_tag, &i_name, &psz_attrs );
1339
58.6k
                    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
58.6k
                    p_parent = webvtt_domnode_getParentByTag( p_parent, psz_tagname );
1344
58.6k
                    if( p_parent ) /* continue as parent next */
1345
20.0k
                    {
1346
20.0k
                        pp_append = &p_parent->p_next;
1347
20.0k
                        p_parent = p_parent->p_parent;
1348
20.0k
                    }
1349
38.5k
                    else /* back as top node */
1350
38.5k
                        pp_append = &p_head->p_next;
1351
104k
                    while( *pp_append )
1352
45.8k
                        pp_append = &((*pp_append)->p_next);
1353
1354
58.6k
                    free( psz_tagname );
1355
58.6k
                }
1356
1.67k
                else break; /* End tag for non open tag */
1357
60.3k
            }
1358
2.38M
            psz_text = psz_taglast + 1;
1359
2.38M
        }
1360
1.07M
        else /* Special case: end */
1361
1.07M
        {
1362
1.07M
            webvtt_dom_text_t *p_node = webvtt_dom_text_New( p_parent );
1363
1.07M
            if( p_node )
1364
1.07M
            {
1365
1.07M
                p_node->psz_text = DuplicateUnescaped( psz_text );
1366
1.07M
                *pi_lines += ((*pi_lines == 0) ? 1 : 0) + CountNewLines( p_node->psz_text );
1367
1.07M
                *pp_append = (webvtt_dom_node_t *) p_node;
1368
1.07M
            }
1369
1.07M
            break;
1370
1.07M
        }
1371
3.46M
    }
1372
1373
1.11M
    return p_head;
1374
1.11M
}
1375
1376
static void ProcessCue( decoder_t *p_dec, const char *psz, webvtt_dom_cue_t *p_cue )
1377
1.11M
{
1378
1.11M
    VLC_UNUSED(p_dec);
1379
1380
1.11M
    if( p_cue->p_child )
1381
0
        return;
1382
1.11M
    p_cue->p_child = CreateDomNodes( psz, &p_cue->i_lines );
1383
2.32M
    for( webvtt_dom_node_t *p_child = p_cue->p_child; p_child; p_child = p_child->p_next )
1384
1.21M
        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.11M
}
1389
1390
static text_style_t * ComputeStyle( decoder_t *p_dec, const webvtt_dom_node_t *p_leaf )
1391
142k
{
1392
142k
    VLC_UNUSED(p_dec);
1393
142k
    text_style_t *p_style = NULL;
1394
142k
    text_style_t *p_dfltstyle = NULL;
1395
142k
    vlc_tick_t i_tagtime = -1;
1396
1397
1.46M
    for( const webvtt_dom_node_t *p_node = p_leaf ; p_node; p_node = p_node->p_parent )
1398
1.31M
    {
1399
1.31M
        bool b_nooverride = false;
1400
1.31M
        if( p_node->type == NODE_CUE )
1401
142k
        {
1402
142k
            const webvtt_dom_cue_t *p_cue = (const webvtt_dom_cue_t *)p_node;
1403
142k
            if( p_cue )
1404
142k
            {
1405
142k
                if( i_tagtime > -1 ) /* don't override timed stylings */
1406
71.4k
                    b_nooverride = true;
1407
142k
            }
1408
142k
        }
1409
1.17M
        else if( p_node->type == NODE_TAG )
1410
1.01M
        {
1411
1.01M
            const webvtt_dom_tag_t *p_tagnode = (const webvtt_dom_tag_t *)p_node;
1412
1413
1.01M
            if( p_tagnode->i_nzstart > -1 )
1414
330k
            {
1415
                /* Ignore other timed stylings */
1416
330k
                if( i_tagtime == -1 )
1417
71.4k
                    i_tagtime = p_tagnode->i_nzstart;
1418
258k
                else
1419
258k
                    continue;
1420
330k
            }
1421
1422
753k
            if ( p_tagnode->psz_tag )
1423
753k
            {
1424
753k
                if ( !strcmp( p_tagnode->psz_tag, "b" ) )
1425
127k
                {
1426
127k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1427
127k
                    {
1428
127k
                        p_style->i_style_flags |= STYLE_BOLD;
1429
127k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1430
127k
                    }
1431
127k
                }
1432
625k
                else if ( !strcmp( p_tagnode->psz_tag, "i" ) )
1433
2.92k
                {
1434
2.92k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1435
2.92k
                    {
1436
2.92k
                        p_style->i_style_flags |= STYLE_ITALIC;
1437
2.92k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1438
2.92k
                    }
1439
2.92k
                }
1440
622k
                else if ( !strcmp( p_tagnode->psz_tag, "u" ) )
1441
14.9k
                {
1442
14.9k
                    if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1443
14.9k
                    {
1444
14.9k
                        p_style->i_style_flags |= STYLE_UNDERLINE;
1445
14.9k
                        p_style->i_features |= STYLE_HAS_FLAGS;
1446
14.9k
                    }
1447
14.9k
                }
1448
607k
                else if ( !strcmp( p_tagnode->psz_tag, "v" ) && p_tagnode->psz_attrs )
1449
77.4k
                {
1450
77.4k
#ifdef HAVE_CSS
1451
77.4k
                    decoder_sys_t *p_sys = p_dec->p_sys;
1452
77.4k
                    if( p_sys->p_css_rules == NULL ) /* Only auto style when no CSS sheet */
1453
57.2k
#endif
1454
57.2k
                    {
1455
57.2k
                        if( p_style || (p_style = text_style_Create( STYLE_NO_DEFAULTS )) )
1456
57.2k
                        {
1457
57.2k
                            unsigned a = 0;
1458
1.21M
                            for( char *p = p_tagnode->psz_attrs; *p; p++ )
1459
1.16M
                                a = (a << 3) ^ *p;
1460
57.2k
                            p_style->i_font_color = (0x7F7F7F | a) & 0xFFFFFF;
1461
57.2k
                            p_style->i_features |= STYLE_HAS_FONT_COLOR;
1462
57.2k
                        }
1463
57.2k
                    }
1464
77.4k
                }
1465
529k
                else if( !strcmp( p_tagnode->psz_tag, "c" ) && p_tagnode->psz_attrs )
1466
31.6k
                {
1467
31.6k
                    static const struct
1468
31.6k
                    {
1469
31.6k
                        const char *psz;
1470
31.6k
                        uint32_t i_color;
1471
31.6k
                    } CEAcolors[] = {
1472
31.6k
                        { "white",  0xFFFFFF },
1473
31.6k
                        { "lime",   0x00FF00 },
1474
31.6k
                        { "cyan",   0x00FFFF },
1475
31.6k
                        { "red",    0xFF0000 },
1476
31.6k
                        { "yellow", 0xFFFF00 },
1477
31.6k
                        { "magenta",0xFF00FF },
1478
31.6k
                        { "blue",   0x0000FF },
1479
31.6k
                        { "black",  0x000000 },
1480
31.6k
                    };
1481
31.6k
                    char *saveptr = NULL;
1482
31.6k
                    char *psz_tok = strtok_r( p_tagnode->psz_attrs, ".", &saveptr );
1483
71.9k
                    for( ; psz_tok; psz_tok = strtok_r( NULL, ".", &saveptr ) )
1484
40.2k
                    {
1485
40.2k
                        bool bg = !strncmp( psz_tok, "bg_", 3 );
1486
40.2k
                        const char *psz_class = (bg) ? psz_tok + 3 : psz_tok;
1487
279k
                        for( size_t i=0; i<ARRAY_SIZE(CEAcolors); i++ )
1488
257k
                        {
1489
257k
                            if( strcmp( psz_class, CEAcolors[i].psz ) )
1490
239k
                                continue;
1491
18.3k
                            if( p_dfltstyle ||
1492
16.2k
                               (p_dfltstyle = text_style_Create( STYLE_NO_DEFAULTS )) )
1493
18.3k
                            {
1494
18.3k
                                if( bg )
1495
1.54k
                                {
1496
1.54k
                                    p_dfltstyle->i_background_color = CEAcolors[i].i_color;
1497
1.54k
                                    p_dfltstyle->i_background_alpha = STYLE_ALPHA_OPAQUE;
1498
1.54k
                                    p_dfltstyle->i_features |= STYLE_HAS_BACKGROUND_COLOR |
1499
1.54k
                                                               STYLE_HAS_BACKGROUND_ALPHA;
1500
1.54k
                                    p_dfltstyle->i_style_flags |= STYLE_BACKGROUND;
1501
1.54k
                                    p_dfltstyle->i_features |= STYLE_HAS_FLAGS;
1502
1.54k
                                }
1503
16.8k
                                else
1504
16.8k
                                {
1505
16.8k
                                    p_dfltstyle->i_font_color = CEAcolors[i].i_color;
1506
16.8k
                                    p_dfltstyle->i_features |= STYLE_HAS_FONT_COLOR;
1507
16.8k
                                }
1508
18.3k
                            }
1509
18.3k
                            break;
1510
257k
                        }
1511
40.2k
                    }
1512
31.6k
                }
1513
753k
            }
1514
753k
        }
1515
1516
1.05M
        const text_style_t *p_nodestyle = webvtt_domnode_getCSSStyle( p_node );
1517
1.05M
        if( p_nodestyle )
1518
113k
        {
1519
113k
            if( p_style )
1520
87.5k
                text_style_Merge( p_style, p_nodestyle, false );
1521
25.5k
            else if( !b_nooverride )
1522
23.1k
                p_style = text_style_Duplicate( p_nodestyle );
1523
113k
        }
1524
1525
        /* Default classes */
1526
1.05M
        if( p_dfltstyle )
1527
16.2k
        {
1528
16.2k
            if( p_style )
1529
13.4k
            {
1530
13.4k
                text_style_Merge( p_style, p_dfltstyle, false );
1531
13.4k
                text_style_Delete( p_dfltstyle );
1532
13.4k
            }
1533
2.81k
            else p_style = p_dfltstyle;
1534
16.2k
            p_dfltstyle = NULL;
1535
16.2k
        }
1536
1.05M
    }
1537
1538
142k
    return p_style;
1539
142k
}
1540
1541
static int GetCueTextAlignment( const webvtt_dom_cue_t *p_cue )
1542
36.1k
{
1543
36.1k
    switch( p_cue->settings.align )
1544
36.1k
    {
1545
4.23k
        case WEBVTT_ALIGN_LEFT:
1546
4.23k
            return SUBPICTURE_ALIGN_LEFT;
1547
547
        case WEBVTT_ALIGN_RIGHT:
1548
547
            return SUBPICTURE_ALIGN_RIGHT;
1549
778
        case WEBVTT_ALIGN_START: /* vertical provides rl or rl base direction */
1550
778
            return (p_cue->settings.vertical == WEBVTT_ALIGN_RIGHT) ?
1551
778
                     SUBPICTURE_ALIGN_RIGHT : SUBPICTURE_ALIGN_LEFT;
1552
599
        case WEBVTT_ALIGN_END:
1553
599
            return (p_cue->settings.vertical == WEBVTT_ALIGN_RIGHT) ?
1554
442
                     SUBPICTURE_ALIGN_LEFT : SUBPICTURE_ALIGN_RIGHT;
1555
29.9k
        default:
1556
29.9k
            return 0;
1557
36.1k
    }
1558
36.1k
}
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.08k
{
1571
2.08k
    text_segment_ruby_t *p_ruby = NULL;
1572
2.08k
    text_segment_ruby_t **pp_rt_append = &p_ruby;
1573
1574
2.08k
    const char *psz_base = NULL;
1575
1576
5.78k
    for( ; p_node ; p_node = p_node->p_next )
1577
3.69k
    {
1578
3.69k
        if( p_node->type == NODE_TEXT )
1579
1.10k
        {
1580
1.10k
            const webvtt_dom_text_t *p_textnode = (const webvtt_dom_text_t *) p_node;
1581
1.10k
            psz_base = p_textnode->psz_text;
1582
1.10k
        }
1583
2.58k
        else if( p_node->type == NODE_TAG )
1584
2.58k
        {
1585
2.58k
            const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *)p_node;
1586
2.58k
            if( !strcmp(p_tag->psz_tag, "rt") && p_tag->p_child &&
1587
726
                p_tag->p_child->type == NODE_TEXT && psz_base )
1588
300
            {
1589
300
                const webvtt_dom_text_t *p_rttext = (const webvtt_dom_text_t *)p_tag->p_child;
1590
300
                if ( p_rttext->psz_text )
1591
300
                {
1592
300
                    *pp_rt_append = text_segment_ruby_New( psz_base, p_rttext->psz_text );
1593
300
                    if( *pp_rt_append )
1594
300
                        pp_rt_append = &(*pp_rt_append)->p_next;
1595
300
                }
1596
300
            }
1597
2.58k
            psz_base = NULL;
1598
2.58k
        }
1599
3.69k
    }
1600
1601
2.08k
    return ( p_ruby ) ? text_segment_FromRuby( p_ruby ) : NULL;
1602
2.08k
}
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
239k
{
1610
239k
    if (depth > MAX_TIMED_NODE_SEGMENTS_RECURSION)
1611
277
        return NULL;
1612
1613
239k
    text_segment_t *p_head = NULL;
1614
239k
    text_segment_t **pp_append = &p_head;
1615
579k
    for( ; p_node ; p_node = p_node->p_next )
1616
339k
    {
1617
458k
        while( *pp_append )
1618
118k
            pp_append = &((*pp_append)->p_next);
1619
1620
339k
        if( p_node->type == NODE_TEXT )
1621
142k
        {
1622
142k
            const webvtt_dom_text_t *p_textnode = (const webvtt_dom_text_t *) p_node;
1623
142k
            if( p_textnode->psz_text == NULL )
1624
0
                continue;
1625
1626
142k
            *pp_append = text_segment_New( p_textnode->psz_text );
1627
142k
            if( *pp_append )
1628
142k
            {
1629
142k
                (*pp_append)->style = ComputeStyle( p_dec, p_node );
1630
142k
            }
1631
142k
        }
1632
197k
        else if( p_node->type == NODE_TAG )
1633
197k
        {
1634
197k
            const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *)p_node;
1635
197k
            if( strcmp(p_tag->psz_tag, "ruby") )
1636
195k
                *pp_append = ConvertNodesToSegments( p_dec, p_vars, p_cue,
1637
195k
                                                     p_tag->p_child, depth+1 );
1638
2.08k
            else
1639
2.08k
                *pp_append = ConvertRubyNodeToSegment( p_tag->p_child );
1640
197k
        }
1641
339k
    }
1642
239k
    return p_head;
1643
239k
}
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
44.1k
{
1649
44.1k
    return ConvertNodesToSegments( p_dec, p_vars, p_cue, p_cue->p_child, 0 );
1650
44.1k
}
1651
1652
static void ChainCueSegments( const webvtt_dom_cue_t *p_cue, text_segment_t *p_new,
1653
                              text_segment_t **pp_append )
1654
8.35k
{
1655
8.35k
    if( p_new )
1656
7.74k
    {
1657
7.74k
        bool b_newline = *pp_append;
1658
1659
151k
        while( *pp_append )
1660
143k
            pp_append = &((*pp_append)->p_next);
1661
1662
7.74k
        if( b_newline ) /* auto newlines */
1663
5.58k
        {
1664
5.58k
            *pp_append = text_segment_New( "\n" );
1665
5.58k
            if( *pp_append )
1666
5.58k
                pp_append = &((*pp_append)->p_next);
1667
5.58k
        }
1668
1669
7.74k
        if( p_cue->settings.vertical == WEBVTT_ALIGN_LEFT ) /* LTR */
1670
191
        {
1671
191
            *pp_append = text_segment_New( "\u2067" );
1672
191
            if( *pp_append )
1673
191
                pp_append = &((*pp_append)->p_next);
1674
191
        }
1675
1676
7.74k
        *pp_append = p_new;
1677
29.2k
        while( *pp_append )
1678
21.5k
            pp_append = &((*pp_append)->p_next);
1679
1680
7.74k
        if( p_cue->settings.vertical == WEBVTT_ALIGN_LEFT )
1681
191
        {
1682
191
            *pp_append = text_segment_New( "\u2069" );
1683
191
            if( *pp_append )
1684
191
                pp_append = &((*pp_append)->p_next);
1685
191
        }
1686
7.74k
    }
1687
8.35k
}
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
9.02k
{
1693
9.02k
    text_segment_t *p_segments = NULL;
1694
9.02k
    text_segment_t **pp_append = &p_segments;
1695
9.02k
    VLC_UNUSED(i_nzstop);
1696
1697
17.3k
    for( ; p_cue; p_cue = (const webvtt_dom_cue_t *) p_cue->p_next )
1698
8.35k
    {
1699
8.35k
        if( p_cue->type != NODE_CUE )
1700
0
            continue;
1701
1702
8.35k
        if( p_cue->i_nzstart > i_nzstart || p_cue->i_nzstop <= i_nzstart )
1703
0
            continue;
1704
1705
8.35k
        text_segment_t *p_new = ConvertCueToSegments( p_dec, p_vars, p_cue );
1706
8.35k
        ChainCueSegments( p_cue, p_new, pp_append );
1707
8.35k
    }
1708
9.02k
    return p_segments;
1709
9.02k
}
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
202k
{
1715
202k
    if (depth > MAX_TIMED_TAGS_RECURSION)
1716
115
        return;
1717
1718
474k
    for( ; p_node; p_node = p_node->p_next )
1719
271k
    {
1720
271k
        switch( p_node->type )
1721
271k
        {
1722
132k
            case NODE_TAG:
1723
132k
            {
1724
132k
                const webvtt_dom_tag_t *p_tag = (const webvtt_dom_tag_t *) p_node;
1725
132k
                if( p_tag->i_nzstart > -1 && p_tag->i_nzstart >= i_nzstart && p_tag->i_nzstart < i_nzstop )
1726
4.60k
                    (void) vlc_array_append( p_times, (void *) p_tag );
1727
132k
                GetTimedTags( p_tag->p_child, i_nzstart, i_nzstop, p_times, depth+1 );
1728
132k
            } break;
1729
7.16k
            case NODE_REGION:
1730
44.2k
            case NODE_CUE:
1731
44.2k
                GetTimedTags( webvtt_domnode_getFirstChild( p_node ),
1732
44.2k
                              i_nzstart, i_nzstop, p_times, depth+1 );
1733
44.2k
                break;
1734
94.7k
            default:
1735
94.7k
                break;
1736
271k
        }
1737
271k
    }
1738
202k
}
1739
1740
static void CreateSpuOrNewUpdaterRegion( decoder_t *p_dec,
1741
                                         subpicture_t **pp_spu,
1742
                                         substext_updater_region_t **pp_updtregion )
1743
36.1k
{
1744
36.1k
    if( *pp_spu == NULL )
1745
13.5k
    {
1746
13.5k
        *pp_spu = decoder_NewSubpictureText( p_dec );
1747
13.5k
        if( *pp_spu )
1748
13.5k
        {
1749
13.5k
            subtext_updater_sys_t *p_spusys = (*pp_spu)->updater.sys;
1750
13.5k
            *pp_updtregion = &p_spusys->region;
1751
13.5k
        }
1752
13.5k
    }
1753
22.5k
    else
1754
22.5k
    {
1755
22.5k
        substext_updater_region_t *p_new =
1756
22.5k
                                SubpictureUpdaterSysRegionNew( );
1757
22.5k
        if( p_new )
1758
22.5k
        {
1759
22.5k
            SubpictureUpdaterSysRegionAdd( *pp_updtregion, p_new );
1760
22.5k
            *pp_updtregion = p_new;
1761
22.5k
        }
1762
22.5k
    }
1763
36.1k
}
1764
1765
static void ClearCSSStyles( webvtt_dom_node_t *p_node )
1766
3.10k
{
1767
3.10k
    if( webvtt_domnode_supportsCSSStyle( p_node ) )
1768
3.10k
        webvtt_domnode_setCSSStyle( p_node, NULL );
1769
1770
    /* start from leaves */
1771
3.10k
    p_node = webvtt_domnode_getFirstChild( p_node );
1772
3.10k
    if( !p_node )
1773
0
        return;
1774
1775
3.10k
    vlc_array_t stack;
1776
3.10k
    vlc_array_init( &stack );
1777
1778
3.74M
    while( p_node )
1779
3.73M
    {
1780
3.73M
        if( webvtt_domnode_supportsCSSStyle( p_node ) )
1781
3.69M
            webvtt_domnode_setCSSStyle( p_node, NULL );
1782
1783
3.73M
        webvtt_dom_node_t *p_child = webvtt_domnode_getFirstChild( p_node );
1784
3.73M
        if( p_child ) /* explore first */
1785
3.69M
        {
1786
3.69M
            if( p_node->p_next )
1787
4.06k
                vlc_array_append( &stack, p_node->p_next );
1788
3.69M
            p_node = p_child;
1789
3.69M
        }
1790
47.2k
        else
1791
47.2k
        {
1792
47.2k
            p_node = p_node->p_next;
1793
1794
47.2k
            if( !p_node )
1795
5.84k
            {
1796
                /* continue on parent sibling */
1797
5.84k
                size_t idx = vlc_array_count( &stack );
1798
5.84k
                if( idx )
1799
3.71k
                {
1800
3.71k
                    p_node = vlc_array_item_at_index( &stack, idx - 1 );
1801
3.71k
                    vlc_array_remove( &stack, idx - 1 );
1802
3.71k
                    p_node = p_node->p_next;
1803
3.71k
                }
1804
5.84k
            }
1805
47.2k
        }
1806
3.73M
    }
1807
1808
3.10k
    vlc_array_clear( &stack );
1809
3.10k
}
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
28.5k
{
1815
28.5k
    decoder_sys_t *p_sys = p_dec->p_sys;
1816
1817
44.9k
    for ( ;  p_rule ; p_rule = p_rule->p_next )
1818
16.4k
    {
1819
16.4k
        vlc_array_t results;
1820
16.4k
        vlc_array_init( &results );
1821
1822
16.4k
        webvtt_domnode_SelectRuleNodes( (webvtt_dom_node_t *) p_sys->p_root,
1823
16.4k
                                        p_rule, i_nzplaybacktime, &results );
1824
1825
16.4k
        for( const vlc_css_declaration_t *p_decl = p_rule->p_declarations;
1826
26.0k
                                          p_decl; p_decl = p_decl->p_next )
1827
9.67k
        {
1828
69.3k
            for( size_t i=0; i<vlc_array_count(&results); i++ )
1829
59.7k
            {
1830
59.7k
                webvtt_dom_node_t *p_node = vlc_array_item_at_index( &results, i );
1831
59.7k
                if( !webvtt_domnode_supportsCSSStyle( p_node ) )
1832
19.5k
                    continue;
1833
1834
40.1k
                text_style_t *p_style = webvtt_domnode_getCSSStyle( p_node );
1835
40.1k
                if( !p_style )
1836
19.6k
                {
1837
19.6k
                    p_style = text_style_Create( STYLE_NO_DEFAULTS );
1838
19.6k
                    webvtt_domnode_setCSSStyle( p_node, p_style );
1839
19.6k
                }
1840
1841
40.1k
                if( !p_style )
1842
0
                    continue;
1843
1844
40.1k
                webvtt_FillStyleFromCssDeclaration( p_decl, p_style );
1845
40.1k
            }
1846
9.67k
        }
1847
16.4k
        vlc_array_clear( &results );
1848
16.4k
    }
1849
28.5k
}
1850
#endif
1851
1852
static void RenderRegions( decoder_t *p_dec, vlc_tick_t i_nzstart, vlc_tick_t i_nzstop )
1853
28.5k
{
1854
28.5k
    subpicture_t *p_spu = NULL;
1855
28.5k
    substext_updater_region_t *p_updtregion = NULL;
1856
28.5k
    decoder_sys_t *p_sys = p_dec->p_sys;
1857
1858
28.5k
#ifdef HAVE_CSS
1859
28.5k
    ApplyCSSRules( p_dec, p_sys->p_css_rules, i_nzstart );
1860
28.5k
#endif
1861
1862
28.5k
    const webvtt_dom_cue_t *p_rlcue = NULL;
1863
28.5k
    for( const webvtt_dom_node_t *p_node = p_sys->p_root->p_child;
1864
73.3k
                                  p_node; p_node = p_node->p_next )
1865
44.8k
    {
1866
44.8k
        if( p_node->type == NODE_REGION )
1867
9.02k
        {
1868
9.02k
            const webvtt_region_t *p_vttregion = (const webvtt_region_t *) p_node;
1869
            /* Variables */
1870
9.02k
            struct render_variables_s v;
1871
9.02k
            v.p_region = p_vttregion;
1872
9.02k
            v.i_left_offset = p_vttregion->anchor_x * p_vttregion->f_width;
1873
9.02k
            v.i_left = p_vttregion->viewport_anchor_x - v.i_left_offset;
1874
9.02k
            v.i_top_offset = p_vttregion->anchor_y * p_vttregion->i_lines_max_scroll *
1875
9.02k
                             WEBVTT_DEFAULT_LINE_HEIGHT_VH / 100.0f;
1876
9.02k
            v.i_top = p_vttregion->viewport_anchor_y - v.i_top_offset;
1877
            /* !Variables */
1878
1879
9.02k
            text_segment_t *p_segments =
1880
9.02k
                    ConvertCuesToSegments( p_dec, i_nzstart, i_nzstop, &v,
1881
9.02k
                                          (const webvtt_dom_cue_t *)p_vttregion->p_child );
1882
9.02k
            if( !p_segments )
1883
6.85k
                continue;
1884
1885
2.16k
            CreateSpuOrNewUpdaterRegion( p_dec, &p_spu, &p_updtregion );
1886
2.16k
            if( !p_spu || !p_updtregion )
1887
0
            {
1888
0
                text_segment_ChainDelete( p_segments );
1889
0
                continue;
1890
0
            }
1891
1892
2.16k
            p_updtregion->b_absolute = false; /* can't be absolute as snap to lines can overlap ! */
1893
2.16k
            p_updtregion->b_in_window = false;
1894
2.16k
            p_updtregion->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
1895
2.16k
            p_updtregion->inner_align = GetCueTextAlignment( (const webvtt_dom_cue_t *)p_vttregion->p_child );
1896
2.16k
            p_updtregion->origin.x = v.i_left;
1897
2.16k
            p_updtregion->origin.y = v.i_top;
1898
2.16k
            p_updtregion->extent.x = p_vttregion->f_width;
1899
1900
2.16k
            p_updtregion->flags = UPDT_REGION_ORIGIN_X_IS_RATIO|UPDT_REGION_ORIGIN_Y_IS_RATIO
1901
2.16k
                                | UPDT_REGION_EXTENT_X_IS_RATIO;
1902
2.16k
            p_updtregion->p_segments = p_segments;
1903
2.16k
        }
1904
35.8k
        else if ( p_node->type == NODE_CUE )
1905
35.8k
        {
1906
35.8k
            if( p_rlcue == NULL )
1907
13.1k
                p_rlcue = ( const webvtt_dom_cue_t * ) p_node;
1908
35.8k
        }
1909
44.8k
    }
1910
1911
    /* regionless cues */
1912
28.5k
    if ( p_rlcue )
1913
13.1k
    {
1914
        /* Variables */
1915
13.1k
        struct render_variables_s v;
1916
13.1k
        v.p_region = NULL;
1917
13.1k
        v.i_left_offset = 0.0;
1918
13.1k
        v.i_left = 0.0;
1919
13.1k
        v.i_top_offset = 0.0;
1920
13.1k
        v.i_top = 0.0;
1921
        /* !Variables */
1922
1923
48.9k
        for( const webvtt_dom_cue_t *p_cue = p_rlcue; p_cue;
1924
35.8k
             p_cue = (const webvtt_dom_cue_t *) p_cue->p_next )
1925
35.8k
        {
1926
35.8k
            if( p_cue->type != NODE_CUE )
1927
0
                continue;
1928
1929
35.8k
            if( p_cue->i_nzstart > i_nzstart || p_cue->i_nzstop <= i_nzstart )
1930
0
                continue;
1931
1932
35.8k
            text_segment_t *p_segments = ConvertCueToSegments( p_dec, &v, p_cue );
1933
35.8k
            if( !p_segments )
1934
1.85k
                continue;
1935
1936
33.9k
            CreateSpuOrNewUpdaterRegion( p_dec, &p_spu, &p_updtregion );
1937
33.9k
            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
33.9k
            p_updtregion->b_absolute = false; p_updtregion->b_in_window = false;
1945
33.9k
            if( p_cue->settings.line.b_auto )
1946
28.8k
            {
1947
28.8k
                p_updtregion->align = SUBPICTURE_ALIGN_BOTTOM;
1948
28.8k
            }
1949
5.08k
            else
1950
5.08k
            {
1951
5.08k
                webvtt_rect_t rect = { 0,0,0,0 };
1952
5.08k
                webvtt_get_cueboxrect( &p_cue->settings, &rect );
1953
5.08k
                p_updtregion->align = SUBPICTURE_ALIGN_TOP|SUBPICTURE_ALIGN_LEFT;
1954
5.08k
                p_updtregion->origin.x = rect.x;
1955
5.08k
                p_updtregion->origin.y = rect.y;
1956
5.08k
                p_updtregion->extent.x = rect.w;
1957
5.08k
                p_updtregion->extent.y = rect.h;
1958
5.08k
                p_updtregion->flags |= (UPDT_REGION_ORIGIN_X_IS_RATIO|UPDT_REGION_ORIGIN_Y_IS_RATIO|
1959
5.08k
                                        UPDT_REGION_EXTENT_X_IS_RATIO|UPDT_REGION_EXTENT_Y_IS_RATIO);
1960
5.08k
            }
1961
1962
33.9k
            p_updtregion->inner_align = GetCueTextAlignment( p_cue );
1963
33.9k
            p_updtregion->p_segments = p_segments;
1964
33.9k
        }
1965
13.1k
    }
1966
1967
28.5k
    if( p_spu )
1968
13.5k
    {
1969
13.5k
        p_spu->i_start = VLC_TICK_0 + i_nzstart;
1970
13.5k
        p_spu->i_stop = VLC_TICK_0 + i_nzstop;
1971
13.5k
        p_spu->b_ephemer  = true; /* !important */
1972
1973
13.5k
        subtext_updater_sys_t *p_spu_sys = p_spu->updater.sys;
1974
13.5k
        p_spu_sys->p_default_style->f_font_relsize = WEBVTT_DEFAULT_LINE_HEIGHT_VH /
1975
13.5k
                                                     WEBVTT_LINE_TO_HEIGHT_RATIO;
1976
13.5k
        decoder_QueueSub( p_dec, p_spu );
1977
13.5k
    }
1978
28.5k
}
1979
1980
static int timedtagsArrayCmp( const void *a, const void *b )
1981
7.66k
{
1982
7.66k
    const webvtt_dom_tag_t *ta = *((const webvtt_dom_tag_t **) a);
1983
7.66k
    const webvtt_dom_tag_t *tb = *((const webvtt_dom_tag_t **) b);
1984
7.66k
    const int64_t result = ta->i_nzstart - tb->i_nzstart;
1985
7.66k
    return result == 0 ? 0 : result > 0 ? 1 : -1;
1986
7.66k
}
1987
1988
static void Render( decoder_t *p_dec, vlc_tick_t i_nzstart, vlc_tick_t i_nzstop )
1989
25.4k
{
1990
25.4k
    decoder_sys_t *p_sys = p_dec->p_sys;
1991
1992
25.4k
    vlc_array_t timedtags;
1993
25.4k
    vlc_array_init( &timedtags );
1994
1995
25.4k
    GetTimedTags( p_sys->p_root->p_child, i_nzstart, i_nzstop, &timedtags, 0 );
1996
25.4k
    if( timedtags.i_count )
1997
1.51k
        qsort( timedtags.pp_elems, timedtags.i_count, sizeof(*timedtags.pp_elems), timedtagsArrayCmp );
1998
1999
25.4k
    vlc_tick_t i_subnzstart = i_nzstart;
2000
30.0k
    for( size_t i=0; i<timedtags.i_count; i++ )
2001
4.60k
    {
2002
4.60k
         const webvtt_dom_tag_t *p_tag =
2003
4.60k
                 (const webvtt_dom_tag_t *) vlc_array_item_at_index( &timedtags, i );
2004
4.60k
         if( p_tag->i_nzstart != i_subnzstart ) /* might be duplicates */
2005
3.05k
         {
2006
3.05k
             if( i > 0 )
2007
1.68k
                 ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
2008
3.05k
             RenderRegions( p_dec, i_subnzstart, p_tag->i_nzstart );
2009
3.05k
             i_subnzstart = p_tag->i_nzstart;
2010
3.05k
         }
2011
4.60k
    }
2012
25.4k
    if( i_subnzstart != i_nzstop )
2013
25.4k
    {
2014
25.4k
        if( i_subnzstart != i_nzstart )
2015
1.41k
            ClearCSSStyles( (webvtt_dom_node_t *)p_sys->p_root );
2016
25.4k
        RenderRegions( p_dec, i_subnzstart, i_nzstop );
2017
25.4k
    }
2018
2019
25.4k
    vlc_array_clear( &timedtags );
2020
25.4k
}
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
25.4k
{
2026
25.4k
    decoder_sys_t *p_sys = p_dec->p_sys;
2027
25.4k
    mp4_box_iterator_t it;
2028
25.4k
    mp4_box_iterator_Init( &it, p_buffer, i_buffer );
2029
1.14M
    while( mp4_box_iterator_Next( &it ) )
2030
1.12M
    {
2031
1.12M
        if( it.i_type == ATOM_vttc || it.i_type == ATOM_vttx )
2032
1.12M
        {
2033
1.12M
            webvtt_dom_cue_t *p_cue = webvtt_dom_cue_New( i_nzstart, i_nzstop );
2034
1.12M
            if( !p_cue )
2035
0
                continue;
2036
2037
1.12M
            mp4_box_iterator_t vtcc;
2038
1.12M
            mp4_box_iterator_Init( &vtcc, it.p_payload, it.i_payload );
2039
3.33M
            while( mp4_box_iterator_Next( &vtcc ) )
2040
2.21M
            {
2041
2.21M
                char *psz = NULL;
2042
2.21M
                switch( vtcc.i_type )
2043
2.21M
                {
2044
889k
                    case ATOM_iden:
2045
889k
                        free( p_cue->psz_id );
2046
889k
                        p_cue->psz_id = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
2047
889k
                        break;
2048
210k
                    case ATOM_sttg:
2049
210k
                    {
2050
210k
                        psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
2051
210k
                        if( psz )
2052
210k
                            webvtt_cue_settings_Parse( &p_cue->settings, psz );
2053
210k
                    } break;
2054
1.11M
                    case ATOM_payl:
2055
1.11M
                    {
2056
1.11M
                        psz = strndup( (char *) vtcc.p_payload, vtcc.i_payload );
2057
1.11M
                        if( psz )
2058
1.11M
                            ProcessCue( p_dec, psz, p_cue );
2059
1.11M
                    } break;
2060
2.21M
                }
2061
2.21M
                free( psz );
2062
2.21M
            }
2063
2064
1.12M
            webvtt_region_t *p_region = webvtt_region_GetByID( p_sys,
2065
1.12M
                                                               p_cue->settings.psz_region );
2066
1.12M
            webvtt_dom_cue_t *p_existingcue = webvtt_domnode_getCue( p_region ? p_region->p_child
2067
1.12M
                                                                              : p_sys->p_root->p_child,
2068
1.12M
                                                                     p_cue->i_nzstart,
2069
1.12M
                                                                     p_cue->psz_id );
2070
1.12M
            if( p_existingcue )
2071
1.08M
            {
2072
1.08M
                webvtt_domnode_mergeCues( p_existingcue, p_cue );
2073
1.08M
            }
2074
39.2k
            else
2075
39.2k
            {
2076
39.2k
                if( p_region )
2077
9.39k
                {
2078
9.39k
                    webvtt_region_AddCue( p_region, p_cue );
2079
9.39k
                    assert( p_region->p_child );
2080
9.39k
                }
2081
29.8k
                else
2082
29.8k
                {
2083
29.8k
                    webvtt_domnode_AppendLast( &p_sys->p_root->p_child, p_cue );
2084
29.8k
                    p_cue->p_parent = (webvtt_dom_node_t *) p_sys->p_root;
2085
29.8k
                }
2086
39.2k
            }
2087
1.12M
        }
2088
1.12M
    }
2089
25.4k
    return 0;
2090
25.4k
}
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
126k
{
2105
126k
    struct parser_ctx *ctx = (struct parser_ctx *)priv;
2106
126k
    decoder_t *p_dec = ctx->p_dec;
2107
126k
    decoder_sys_t *p_sys = p_dec->p_sys;
2108
2109
126k
    if( b_new || !psz_line /* commit */ )
2110
16.5k
    {
2111
16.5k
        if( ctx->p_region )
2112
2.98k
        {
2113
2.98k
            if( ctx->p_region->psz_id )
2114
2.41k
            {
2115
2.41k
                webvtt_domnode_AppendLast( &p_sys->p_root->p_child, ctx->p_region );
2116
2.41k
                ctx->p_region->p_parent = (webvtt_dom_node_t *) p_sys->p_root;
2117
2.41k
                msg_Dbg( p_dec, "added new region %s", ctx->p_region->psz_id );
2118
2.41k
            }
2119
            /* incomplete region decl (no id at least) */
2120
577
            else webvtt_region_Delete( ctx->p_region );
2121
2.98k
            ctx->p_region = NULL;
2122
2.98k
        }
2123
13.5k
#ifdef HAVE_CSS
2124
13.5k
        else if( ctx->b_css_memstream_opened )
2125
9.12k
        {
2126
9.12k
            if( vlc_memstream_close( &ctx->css ) == 0 )
2127
9.12k
            {
2128
9.12k
                vlc_css_parser_t p;
2129
9.12k
                vlc_css_parser_Init(&p);
2130
9.12k
                vlc_css_parser_ParseBytes( &p,
2131
9.12k
                                          (const uint8_t *) ctx->css.ptr,
2132
9.12k
                                           ctx->css.length );
2133
#  ifdef CSS_PARSER_DEBUG
2134
                vlc_css_parser_Debug( &p );
2135
#  endif
2136
9.12k
                vlc_css_rule_t **pp_append = &p_sys->p_css_rules;
2137
262k
                while( *pp_append )
2138
253k
                    pp_append = &((*pp_append)->p_next);
2139
9.12k
                *pp_append = p.rules.p_first;
2140
9.12k
                p.rules.p_first = NULL;
2141
2142
9.12k
                vlc_css_parser_Clean(&p);
2143
9.12k
                free( ctx->css.ptr );
2144
9.12k
            }
2145
9.12k
        }
2146
16.5k
#endif
2147
2148
16.5k
        if( !psz_line )
2149
4.43k
            return;
2150
2151
12.1k
        if( b_new )
2152
12.1k
        {
2153
12.1k
            if( s == WEBVTT_HEADER_REGION )
2154
2.98k
                ctx->p_region = webvtt_region_New();
2155
9.12k
#ifdef HAVE_CSS
2156
9.12k
            else if( s == WEBVTT_HEADER_STYLE )
2157
9.12k
                ctx->b_css_memstream_opened = !vlc_memstream_open( &ctx->css );
2158
12.1k
#endif
2159
12.1k
            return;
2160
12.1k
        }
2161
12.1k
    }
2162
2163
110k
    if( s == WEBVTT_HEADER_REGION && ctx->p_region )
2164
18.5k
        webvtt_region_Parse( ctx->p_region, (char*) psz_line );
2165
91.6k
#ifdef HAVE_CSS
2166
91.6k
    else if( s == WEBVTT_HEADER_STYLE && ctx->b_css_memstream_opened )
2167
91.6k
    {
2168
91.6k
        vlc_memstream_puts( &ctx->css, psz_line );
2169
91.6k
        vlc_memstream_putc( &ctx->css, '\n' );
2170
91.6k
    }
2171
110k
#endif
2172
110k
}
2173
2174
static void LoadExtradata( decoder_t *p_dec )
2175
4.43k
{
2176
4.43k
    stream_t *p_stream = vlc_stream_MemoryNew( p_dec,
2177
4.43k
                                               p_dec->fmt_in->p_extra,
2178
4.43k
                                               p_dec->fmt_in->i_extra,
2179
4.43k
                                               true );
2180
4.43k
    if( !p_stream )
2181
0
        return;
2182
2183
4.43k
   struct parser_ctx ctx;
2184
4.43k
#ifdef HAVE_CSS
2185
4.43k
   ctx.b_css_memstream_opened = false;
2186
4.43k
#endif
2187
4.43k
   ctx.p_region = NULL;
2188
4.43k
   ctx.p_dec = p_dec;
2189
4.43k
   webvtt_text_parser_t *p_parser =
2190
4.43k
           webvtt_text_parser_New( &ctx, NULL, NULL, ParserHeaderHandler );
2191
4.43k
   if( p_parser )
2192
4.43k
   {
2193
4.43k
        char *psz_line;
2194
136k
        while( (psz_line = vlc_stream_ReadLine( p_stream )) )
2195
131k
            webvtt_text_parser_Feed( p_parser, psz_line );
2196
4.43k
        webvtt_text_parser_Delete( p_parser );
2197
        /* commit using null */
2198
4.43k
        ParserHeaderHandler( &ctx, 0, false, NULL );
2199
4.43k
   }
2200
2201
4.43k
    vlc_stream_Delete( p_stream );
2202
4.43k
}
2203
2204
/****************************************************************************
2205
 * Flush:
2206
 ****************************************************************************/
2207
static void Flush( decoder_t *p_dec )
2208
7.20k
{
2209
7.20k
    decoder_sys_t *p_sys = p_dec->p_sys;
2210
7.20k
    ClearCuesByTime( &p_sys->p_root->p_child, VLC_TICK_MAX );
2211
7.20k
}
2212
2213
/****************************************************************************
2214
 * DecodeBlock: decoder data entry point
2215
 ****************************************************************************/
2216
static int DecodeBlock( decoder_t *p_dec, block_t *p_block )
2217
55.3k
{
2218
55.3k
    if( p_block == NULL ) /* No Drain */
2219
29.9k
        return VLCDEC_SUCCESS;
2220
2221
25.4k
    decoder_sys_t *p_sys = p_dec->p_sys;
2222
2223
25.4k
    vlc_tick_t i_nzstart = p_block->i_pts - VLC_TICK_0;
2224
25.4k
    vlc_tick_t i_nzstop = i_nzstart + p_block->i_length;
2225
2226
25.4k
    if( p_block->i_flags & BLOCK_FLAG_DISCONTINUITY )
2227
7.20k
        Flush( p_dec );
2228
18.2k
    else
2229
18.2k
        ClearCuesByTime( &p_sys->p_root->p_child, i_nzstart );
2230
2231
25.4k
    ProcessISOBMFF( p_dec, p_block->p_buffer, p_block->i_buffer,
2232
25.4k
                    i_nzstart, i_nzstop );
2233
2234
25.4k
    Render( p_dec, i_nzstart, i_nzstop );
2235
2236
25.4k
    block_Release( p_block );
2237
25.4k
    return VLCDEC_SUCCESS;
2238
55.3k
}
2239
2240
/*****************************************************************************
2241
 * webvtt_CloseDecoder: clean up the decoder
2242
 *****************************************************************************/
2243
void webvtt_CloseDecoder( vlc_object_t *p_this )
2244
4.43k
{
2245
4.43k
    decoder_t *p_dec = (decoder_t *)p_this;
2246
4.43k
    decoder_sys_t *p_sys = p_dec->p_sys;
2247
2248
4.43k
    webvtt_domnode_ChainDelete( (webvtt_dom_node_t *) p_sys->p_root );
2249
2250
4.43k
#ifdef HAVE_CSS
2251
4.43k
    vlc_css_rules_Delete( p_sys->p_css_rules );
2252
4.43k
#endif
2253
2254
4.43k
    free( p_sys );
2255
4.43k
}
2256
2257
/*****************************************************************************
2258
 * webvtt_OpenDecoder: probe the decoder and return score
2259
 *****************************************************************************/
2260
int webvtt_OpenDecoder( vlc_object_t *p_this )
2261
5.70k
{
2262
5.70k
    decoder_t *p_dec = (decoder_t*)p_this;
2263
5.70k
    decoder_sys_t *p_sys;
2264
2265
5.70k
    if( p_dec->fmt_in->i_codec != VLC_CODEC_WEBVTT )
2266
1.26k
        return VLC_EGENERIC;
2267
2268
    /* Allocate the memory needed to store the decoder's structure */
2269
4.43k
    p_dec->p_sys = p_sys = calloc( 1, sizeof( *p_sys ) );
2270
4.43k
    if( unlikely( p_sys == NULL ) )
2271
0
        return VLC_ENOMEM;
2272
2273
4.43k
    p_sys->p_root = webvtt_dom_tag_New( NULL );
2274
4.43k
    if( !p_sys->p_root )
2275
0
    {
2276
0
        free( p_sys );
2277
0
        return VLC_ENOMEM;
2278
0
    }
2279
4.43k
    p_sys->p_root->psz_tag = strdup( "video" );
2280
2281
4.43k
    p_dec->pf_decode = DecodeBlock;
2282
4.43k
    p_dec->pf_flush  = Flush;
2283
2284
4.43k
    if( p_dec->fmt_in->i_extra )
2285
4.43k
        LoadExtradata( p_dec );
2286
2287
4.43k
    return VLC_SUCCESS;
2288
4.43k
}