Coverage Report

Created: 2025-11-24 06:20

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