Coverage Report

Created: 2026-09-03 07:12

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libass/libass/ass.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3
 *
4
 * This file is part of libass.
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include "config.h"
20
#include "ass_compat.h"
21
22
#include <stdio.h>
23
#include <stdlib.h>
24
#include <string.h>
25
#include <assert.h>
26
#include <errno.h>
27
#include <sys/types.h>
28
#include <sys/stat.h>
29
#include <inttypes.h>
30
31
#ifdef CONFIG_ICONV
32
#include <iconv.h>
33
#endif
34
35
#include "ass.h"
36
#include "ass_utils.h"
37
#include "ass_library.h"
38
#include "ass_priv.h"
39
#include "ass_shaper.h"
40
#include "ass_string.h"
41
42
51.3k
#define ass_atof(STR) (ass_strtod((STR),NULL))
43
44
static const char *const ass_style_format =
45
        "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
46
        "OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, "
47
        "ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, "
48
        "Alignment, MarginL, MarginR, MarginV, Encoding";
49
static const char *const ass_event_format =
50
        "Layer, Start, End, Style, Name, "
51
        "MarginL, MarginR, MarginV, Effect, Text";
52
static const char *const ssa_style_format =
53
        "Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, "
54
        "TertiaryColour, BackColour, Bold, Italic, BorderStyle, Outline, "
55
        "Shadow, Alignment, MarginL, MarginR, MarginV, AlphaLevel, Encoding";
56
static const char *const ssa_event_format =
57
        "Marked, Start, End, Style, Name, "
58
        "MarginL, MarginR, MarginV, Effect, Text";
59
60
1.42k
#define ASS_STYLES_ALLOC 20
61
62
int ass_library_version(void)
63
0
{
64
0
    return LIBASS_VERSION;
65
0
}
66
67
void ass_free_track(ASS_Track *track)
68
196
{
69
196
    int i;
70
71
196
    if (!track)
72
0
        return;
73
74
196
    if (track->parser_priv) {
75
196
        free(track->parser_priv->read_order_bitmap);
76
196
        free(track->parser_priv->fontname);
77
196
        free(track->parser_priv->fontdata);
78
196
        free(track->parser_priv);
79
196
    }
80
196
    free(track->style_format);
81
196
    free(track->event_format);
82
196
    free(track->Language);
83
196
    if (track->styles) {
84
10.9k
        for (i = 0; i < track->n_styles; ++i)
85
10.7k
            ass_free_style(track, i);
86
196
    }
87
196
    free(track->styles);
88
196
    if (track->events) {
89
7.86k
        for (i = 0; i < track->n_events; ++i)
90
7.73k
            ass_free_event(track, i);
91
132
    }
92
196
    free(track->events);
93
196
    free(track->name);
94
196
    free(track);
95
196
}
96
97
/// \brief Allocate a new style struct
98
/// \param track track
99
/// \return style id or negative value on failure
100
int ass_alloc_style(ASS_Track *track)
101
10.7k
{
102
10.7k
    int sid;
103
104
10.7k
    assert(track->n_styles <= track->max_styles);
105
106
10.7k
    if (track->n_styles == track->max_styles) {
107
711
        if (track->max_styles >= FFMIN(SIZE_MAX, INT_MAX) - ASS_STYLES_ALLOC)
108
0
            return -1;
109
711
        int new_max = track->max_styles + ASS_STYLES_ALLOC;
110
711
        if (!ASS_REALLOC_ARRAY(track->styles, new_max))
111
0
            return -1;
112
711
        track->max_styles = new_max;
113
711
    }
114
115
10.7k
    sid = track->n_styles++;
116
10.7k
    memset(track->styles + sid, 0, sizeof(ASS_Style));
117
10.7k
    return sid;
118
10.7k
}
119
120
/// \brief Allocate a new event struct
121
/// \param track track
122
/// \return event id or negative value on failure
123
int ass_alloc_event(ASS_Track *track)
124
8.68k
{
125
8.68k
    int eid;
126
127
8.68k
    assert(track->n_events <= track->max_events);
128
129
8.68k
    if (track->n_events == track->max_events) {
130
694
        if (track->max_events >= FFMIN(SIZE_MAX, INT_MAX) / 2)
131
0
            return -1;
132
694
        int new_max = track->max_events * 2 + 1;
133
694
        if (!ASS_REALLOC_ARRAY(track->events, new_max))
134
0
            return -1;
135
694
        track->max_events = new_max;
136
694
    }
137
138
8.68k
    eid = track->n_events++;
139
8.68k
    memset(track->events + eid, 0, sizeof(ASS_Event));
140
8.68k
    return eid;
141
8.68k
}
142
143
void ass_free_event(ASS_Track *track, int eid)
144
8.68k
{
145
8.68k
    ASS_Event *event = track->events + eid;
146
147
8.68k
    free(event->Name);
148
8.68k
    free(event->Effect);
149
8.68k
    free(event->Text);
150
8.68k
    free(event->render_priv);
151
8.68k
}
152
153
void ass_free_style(ASS_Track *track, int sid)
154
10.7k
{
155
10.7k
    ASS_Style *style = track->styles + sid;
156
157
10.7k
    free(style->Name);
158
10.7k
    free(style->FontName);
159
10.7k
}
160
161
static int resize_read_order_bitmap(ASS_Track *track, int max_id)
162
0
{
163
    // Don't allow malicious files to OOM us easily. Also avoids int overflows.
164
0
    if (max_id < 0 || max_id >= 10 * 1024 * 1024 * 8)
165
0
        goto fail;
166
0
    assert(track->parser_priv->read_order_bitmap || !track->parser_priv->read_order_elems);
167
0
    if (max_id >= track->parser_priv->read_order_elems * 32) {
168
0
        int oldelems = track->parser_priv->read_order_elems;
169
0
        int elems = ((max_id + 31) / 32 + 1) * 2;
170
0
        assert(elems >= oldelems);
171
0
        track->parser_priv->read_order_elems = elems;
172
0
        void *new_bitmap =
173
0
            realloc(track->parser_priv->read_order_bitmap, elems * 4);
174
0
        if (!new_bitmap)
175
0
            goto fail;
176
0
        track->parser_priv->read_order_bitmap = new_bitmap;
177
0
        memset(track->parser_priv->read_order_bitmap + oldelems, 0,
178
0
               (elems - oldelems) * 4);
179
0
    }
180
0
    return 0;
181
182
0
fail:
183
0
    free(track->parser_priv->read_order_bitmap);
184
0
    track->parser_priv->read_order_bitmap = NULL;
185
0
    track->parser_priv->read_order_elems = 0;
186
0
    return -1;
187
0
}
188
189
static int test_and_set_read_order_bit(ASS_Track *track, int id)
190
0
{
191
0
    if (resize_read_order_bitmap(track, id) < 0)
192
0
        return -1;
193
0
    int index = id >> 5;
194
0
    uint32_t bit = 1u << (id & 0x1F);
195
0
    if (track->parser_priv->read_order_bitmap[index] & bit)
196
0
        return 1;
197
0
    track->parser_priv->read_order_bitmap[index] |= bit;
198
0
    return 0;
199
0
}
200
201
static inline void clear_read_order_bit(ASS_Track *track, int id)
202
0
{
203
    // we previously loaded a packet with an invalid ReadOrder field
204
    // (or int overflow) and decided to just skip checking for duplicates
205
0
    if (id < 0)
206
0
        return;
207
208
0
    int index = id >> 5;
209
0
    if (index < track->parser_priv->read_order_elems) {
210
0
        uint32_t mask = ~(1u << (id & 0x1F));
211
0
        track->parser_priv->read_order_bitmap[index] &= mask;
212
0
    }
213
0
}
214
215
static inline void update_prune_ts(ASS_Track *track, const long long ts)
216
7.73k
{
217
7.73k
    if (ts < track->parser_priv->prune_next_ts)
218
261
        track->parser_priv->prune_next_ts = ts;
219
7.73k
}
220
221
// ==============================================================================================
222
223
/**
224
 * \brief Set up default style
225
 * \param style style to edit to defaults
226
 * The parameters are mostly taken directly from VSFilter source for
227
 * best compatibility.
228
 */
229
static void set_default_style(ASS_Style *style)
230
196
{
231
196
    style->Name             = strdup("Default");
232
196
    style->FontName         = strdup("Arial");
233
196
    style->FontSize         = 18;
234
196
    style->PrimaryColour    = 0xffffff00;
235
196
    style->SecondaryColour  = 0x00ffff00;
236
196
    style->OutlineColour    = 0x00000000;
237
196
    style->BackColour       = 0x00000080;
238
196
    style->Bold             = 200;
239
196
    style->ScaleX           = 1.0;
240
196
    style->ScaleY           = 1.0;
241
196
    style->Spacing          = 0;
242
196
    style->BorderStyle      = 1;
243
196
    style->Outline          = 2;
244
196
    style->Shadow           = 3;
245
196
    style->Alignment        = 2;
246
196
    style->MarginL = style->MarginR = style->MarginV = 20;
247
196
}
248
249
static long long string2timecode(ASS_Library *library, char *p)
250
11.9k
{
251
11.9k
    int32_t h, m, s, ms;
252
11.9k
    long long tm;
253
11.9k
    int res = sscanf(p, "%" SCNd32 ":%" SCNd32 ":%" SCNd32 ".%" SCNd32, &h, &m, &s, &ms);
254
11.9k
    if (res < 4) {
255
7.48k
        ass_msg(library, MSGL_WARN, "Bad timestamp");
256
7.48k
        return 0;
257
7.48k
    }
258
4.51k
    tm = ((h * 60LL + m) * 60 + s) * 1000 + ms * 10LL;
259
4.51k
    return tm;
260
11.9k
}
261
262
static int read_digits(char **str, unsigned base, uint32_t *res)
263
30.3k
{
264
30.3k
    char *p = *str;
265
30.3k
    char *start = p;
266
30.3k
    uint32_t val = 0;
267
268
33.6k
    while (1) {
269
33.6k
        unsigned digit;
270
33.6k
        if (*p >= '0' && *p < FFMIN(base, 10) + '0')
271
3.24k
            digit = *p - '0';
272
30.3k
        else if (*p >= 'a' && *p < base - 10 + 'a')
273
0
            digit = *p - 'a' + 10;
274
30.3k
        else if (*p >= 'A' && *p < base - 10 + 'A')
275
0
            digit = *p - 'A' + 10;
276
30.3k
        else
277
30.3k
            break;
278
3.24k
        val = val * base + digit;
279
3.24k
        ++p;
280
3.24k
    }
281
282
30.3k
    *res = val;
283
30.3k
    *str = p;
284
30.3k
    return p != start;
285
30.3k
}
286
287
/**
288
 * \brief Convert a string to an integer reduced modulo 2**32
289
 * Follows the rules for strtoul but reduces the number modulo 2**32
290
 * instead of saturating it to 2**32 - 1.
291
 */
292
static int mystrtou32_modulo(char **p, unsigned base, uint32_t *res)
293
30.3k
{
294
    // This emulates scanf with %d or %x format as it works on
295
    // Windows, because that's what is used by VSFilter. In practice,
296
    // scanf works the same way on other platforms too, but
297
    // the standard leaves its behavior on overflow undefined.
298
299
    // Unlike scanf and like strtoul, produce 0 for invalid inputs.
300
301
30.3k
    char *start = *p;
302
30.3k
    int sign = 1;
303
304
30.3k
    skip_spaces(p);
305
306
30.3k
    if (**p == '+')
307
75
        ++*p;
308
30.2k
    else if (**p == '-')
309
198
        sign = -1, ++*p;
310
311
30.3k
    if (base == 16 && !ass_strncasecmp(*p, "0x", 2))
312
0
        *p += 2;
313
314
30.3k
    if (read_digits(p, base, res)) {
315
1.22k
        *res *= sign;
316
1.22k
        return 1;
317
29.1k
    } else {
318
29.1k
        *p = start;
319
29.1k
        return 0;
320
29.1k
    }
321
30.3k
}
322
323
static int32_t parse_int_header(char *str)
324
30.3k
{
325
30.3k
    uint32_t val = 0;
326
30.3k
    unsigned base;
327
328
30.3k
    if (!ass_strncasecmp(str, "&h", 2) || !ass_strncasecmp(str, "0x", 2)) {
329
6
        str += 2;
330
6
        base = 16;
331
6
    } else
332
30.3k
        base = 10;
333
334
30.3k
    mystrtou32_modulo(&str, base, &val);
335
30.3k
    return val;
336
30.3k
}
337
338
static uint32_t parse_color_header(char *str)
339
5.63k
{
340
5.63k
    uint32_t color = parse_int_header(str);
341
5.63k
    return ass_bswap32(color);
342
5.63k
}
343
344
// Return a boolean value for a string
345
static char parse_bool(char *str)
346
386
{
347
386
    skip_spaces(&str);
348
386
    return !ass_strncasecmp(str, "yes", 3) || strtol(str, NULL, 10) > 0;
349
386
}
350
351
static int parse_ycbcr_matrix(char *str)
352
1.54k
{
353
1.54k
    skip_spaces(&str);
354
1.54k
    if (*str == '\0')
355
3
        return YCBCR_DEFAULT;
356
357
1.54k
    char *end = str + strlen(str);
358
1.54k
    rskip_spaces(&end, str);
359
360
    // Trim a local copy of the input that we know is safe to
361
    // modify. The buffer is larger than any valid string + NUL,
362
    // so we can simply chop off the rest of the input.
363
1.54k
    char buffer[16];
364
1.54k
    size_t n = FFMIN(end - str, sizeof buffer - 1);
365
1.54k
    memcpy(buffer, str, n);
366
1.54k
    buffer[n] = '\0';
367
368
1.54k
    if (!ass_strcasecmp(buffer, "none"))
369
128
        return YCBCR_NONE;
370
1.41k
    if (!ass_strcasecmp(buffer, "tv.601"))
371
128
        return YCBCR_BT601_TV;
372
1.28k
    if (!ass_strcasecmp(buffer, "pc.601"))
373
277
        return YCBCR_BT601_PC;
374
1.00k
    if (!ass_strcasecmp(buffer, "tv.709"))
375
128
        return YCBCR_BT709_TV;
376
881
    if (!ass_strcasecmp(buffer, "pc.709"))
377
129
        return YCBCR_BT709_PC;
378
752
    if (!ass_strcasecmp(buffer, "tv.240m"))
379
128
        return YCBCR_SMPTE240M_TV;
380
624
    if (!ass_strcasecmp(buffer, "pc.240m"))
381
128
        return YCBCR_SMPTE240M_PC;
382
496
    if (!ass_strcasecmp(buffer, "tv.fcc"))
383
129
        return YCBCR_FCC_TV;
384
367
    if (!ass_strcasecmp(buffer, "pc.fcc"))
385
130
        return YCBCR_FCC_PC;
386
237
    return YCBCR_UNKNOWN;
387
367
}
388
389
#define NEXT(str,token,rtrim) \
390
179k
    token = next_token(&str, rtrim); \
391
179k
    if (!token) break;
392
94.0k
#define NEXTNAME(str,token) NEXT(str, token, true)
393
85.5k
#define NEXTVAL(str,token) NEXT(str, token, false)
394
395
396
#define ALIAS(alias,name) \
397
101k
        if (ass_strcasecmp(tname, #alias) == 0) {tname = #name;}
398
399
/* One section started with PARSE_START and PARSE_END parses a single token
400
 * (contained in the variable named token) for the header indicated by the
401
 * variable tname. It does so by chaining a number of else-if statements, each
402
 * of which checks if the tname variable indicates that this header should be
403
 * parsed. The first parameter of the macro gives the name of the header.
404
 *
405
 * The string that is passed is in str. str is advanced to the next token if
406
 * a header could be parsed. The parsed results are stored in the variable
407
 * target, which has the type ASS_Style* or ASS_Event*.
408
 */
409
74.8k
#define PARSE_START if (0) {
410
7.27k
#define PARSE_END   }
411
412
#define ANYVAL(name,func) \
413
332k
    } else if (ass_strcasecmp(tname, #name) == 0) { \
414
51.3k
        target->name = func(token);
415
416
#define STRVAL(name) \
417
104k
    } else if (ass_strcasecmp(tname, #name) == 0) { \
418
12.5k
        char *new_str = strdup(token); \
419
12.5k
        if (new_str) { \
420
12.5k
            free(target->name); \
421
12.5k
            target->name = new_str; \
422
12.5k
        }
423
424
#define STARREDSTRVAL(name) \
425
23.8k
    } else if (ass_strcasecmp(tname, #name) == 0) { \
426
1.80k
        while (*token == '*') ++token; \
427
1.80k
        char *new_str = strdup(token); \
428
1.80k
        if (new_str) { \
429
1.80k
            free(target->name); \
430
1.80k
            target->name = new_str; \
431
1.80k
        }
432
433
5.65k
#define COLORVAL(name) ANYVAL(name,parse_color_header)
434
24.8k
#define INTVAL(name) ANYVAL(name,parse_int_header)
435
51.3k
#define FPVAL(name) ANYVAL(name,ass_atof)
436
#define TIMEVAL(name) \
437
27.9k
    } else if (ass_strcasecmp(tname, #name) == 0) { \
438
11.9k
        target->name = string2timecode(track->library, token);
439
440
#define STYLEVAL(name) \
441
49.8k
    } else if (ass_strcasecmp(tname, #name) == 0) { \
442
5.79k
        target->name = ass_lookup_style(track, token);
443
444
// skip spaces in str beforehand, or trim leading spaces afterwards
445
static inline void advance_token_pos(const char **const str,
446
                                     const char **const start,
447
                                     const char **const end)
448
168k
{
449
168k
    *start = *str;
450
168k
    *end   = *start;
451
929k
    while (**end != '\0' && **end != ',') ++*end;
452
168k
    *str = *end + (**end == ',');
453
168k
}
454
455
static char *next_token(char **str, bool rtrim)
456
179k
{
457
179k
    char *p;
458
179k
    char *start;
459
179k
    skip_spaces(str);
460
179k
    if (**str == '\0') {
461
11.4k
        return 0;
462
11.4k
    }
463
464
168k
    advance_token_pos((const char**)str,
465
168k
                      (const char**)&start,
466
168k
                      (const char**)&p);
467
468
168k
    if (rtrim)
469
93.2k
        rskip_spaces(&p, start);
470
168k
    *p = '\0';
471
168k
    return start;
472
179k
}
473
474
/**
475
 * \brief Parse the tail of Dialogue line
476
 * \param track track
477
 * \param event parsed data goes here
478
 * \param str string to parse, zero-terminated
479
 * \param n_ignored number of format options to skip at the beginning
480
*/
481
static int process_event_tail(ASS_Track *track, ASS_Event *event,
482
                              char *str, int n_ignored)
483
8.68k
{
484
8.68k
    char *token;
485
8.68k
    char *tname;
486
8.68k
    char *p = str;
487
8.68k
    int i;
488
8.68k
    ASS_Event *target = event;
489
490
8.68k
    char *format = strdup(track->event_format);
491
8.68k
    if (!format)
492
0
        return -1;
493
8.68k
    char *q = format;           // format scanning pointer
494
495
8.68k
    for (i = 0; i < n_ignored; ++i) {
496
0
        NEXTVAL(q, tname);
497
0
    }
498
499
59.6k
    while (1) {
500
59.6k
        NEXTNAME(q, tname);
501
59.6k
        if (ass_strcasecmp(tname, "Text") == 0) {
502
7.73k
            event->Text = strdup(p);
503
7.73k
            if (event->Text && *event->Text != 0) {
504
2.94k
                char *end = event->Text + strlen(event->Text);
505
3.31k
                while (end > event->Text &&
506
3.04k
                       (end[-1] == '\r' || end[-1] == '\t' || end[-1] == ' '))
507
362
                    *--end = 0;
508
2.94k
            }
509
7.73k
            event->Duration -= event->Start;
510
7.73k
            free(format);
511
7.73k
            return event->Text ? 0 : -1;           // "Text" is always the last
512
7.73k
        }
513
51.8k
        NEXTVAL(p, token);
514
515
50.9k
        ALIAS(End, Duration)    // temporarily store end timecode in event->Duration
516
50.9k
        ALIAS(Actor, Name)      // both variants are used in files
517
50.9k
        PARSE_START
518
1.12k
            INTVAL(Layer)
519
5.79k
            STYLEVAL(Style)
520
5.79k
            STRVAL(Name)
521
5.63k
            STRVAL(Effect)
522
5.57k
            INTVAL(MarginL)
523
5.57k
            INTVAL(MarginR)
524
5.37k
            INTVAL(MarginV)
525
6.01k
            TIMEVAL(Start)
526
16.9k
            TIMEVAL(Duration)
527
16.9k
        PARSE_END
528
50.9k
    }
529
949
    free(format);
530
949
    return 1;
531
8.68k
}
532
533
static void set_style_alpha(ASS_Style *style, int32_t front_alpha, int32_t back_alpha)
534
7.12k
{
535
7.12k
        front_alpha  = FFMAX(FFMIN(front_alpha, 0xFF), 0);
536
7.12k
        back_alpha = FFMAX(FFMIN(back_alpha, 0xFF), 0);
537
7.12k
        style->PrimaryColour   = (style->PrimaryColour   & 0xFFFFFF00) | front_alpha;
538
7.12k
        style->SecondaryColour = (style->SecondaryColour & 0xFFFFFF00) | front_alpha;
539
7.12k
        style->OutlineColour   = (style->OutlineColour   & 0xFFFFFF00) | front_alpha;
540
7.12k
        style->BackColour      = (style->BackColour      & 0xFFFFFF00) | back_alpha;
541
7.12k
}
542
543
/**
544
 * \brief Parse command line style overrides (--ass-force-style option)
545
 * \param track track to apply overrides to
546
 * The format for overrides is [StyleName.]Field=Value
547
 */
548
void ass_process_force_style(ASS_Track *track)
549
165
{
550
165
    char **fs, *eq, *dt, *style, *tname, *token;
551
165
    ASS_Style *target;
552
165
    int sid;
553
165
    char **list = track->library->style_overrides;
554
555
165
    if (!list)
556
165
        return;
557
558
0
    for (fs = list; *fs; ++fs) {
559
0
        eq = strrchr(*fs, '=');
560
0
        if (!eq)
561
0
            continue;
562
0
        *eq = '\0';
563
0
        token = eq + 1;
564
565
0
        if (!ass_strcasecmp(*fs, "PlayResX"))
566
0
            track->PlayResX = parse_int_header(token);
567
0
        else if (!ass_strcasecmp(*fs, "PlayResY"))
568
0
            track->PlayResY = parse_int_header(token);
569
0
        else if (!ass_strcasecmp(*fs, "LayoutResX"))
570
0
            track->LayoutResX = parse_int_header(token);
571
0
        else if (!ass_strcasecmp(*fs, "LayoutResY"))
572
0
            track->LayoutResY = parse_int_header(token);
573
0
        else if (!ass_strcasecmp(*fs, "Timer"))
574
0
            track->Timer = ass_atof(token);
575
0
        else if (!ass_strcasecmp(*fs, "WrapStyle"))
576
0
            track->WrapStyle = parse_int_header(token);
577
0
        else if (!ass_strcasecmp(*fs, "ScaledBorderAndShadow"))
578
0
            track->ScaledBorderAndShadow = parse_bool(token);
579
0
        else if (!ass_strcasecmp(*fs, "Kerning"))
580
0
            track->Kerning = parse_bool(token);
581
0
        else if (!ass_strcasecmp(*fs, "YCbCr Matrix"))
582
0
            track->YCbCrMatrix = parse_ycbcr_matrix(token);
583
584
0
        dt = strrchr(*fs, '.');
585
0
        if (dt) {
586
0
            *dt = '\0';
587
0
            style = *fs;
588
0
            tname = dt + 1;
589
0
        } else {
590
0
            style = NULL;
591
0
            tname = *fs;
592
0
        }
593
0
        for (sid = 0; sid < track->n_styles; ++sid) {
594
0
            if (style == NULL
595
0
                || ass_strcasecmp(track->styles[sid].Name, style) == 0) {
596
0
                target = track->styles + sid;
597
0
                PARSE_START
598
0
                    STRVAL(FontName)
599
0
                    COLORVAL(PrimaryColour)
600
0
                    COLORVAL(SecondaryColour)
601
0
                    COLORVAL(OutlineColour)
602
0
                    COLORVAL(BackColour)
603
0
                    } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) {
604
0
                        int32_t alpha = parse_int_header(token);
605
0
                        set_style_alpha(target, alpha, alpha);
606
0
                    FPVAL(FontSize)
607
0
                    INTVAL(Bold)
608
0
                    INTVAL(Italic)
609
0
                    INTVAL(Underline)
610
0
                    INTVAL(StrikeOut)
611
0
                    FPVAL(Spacing)
612
0
                    FPVAL(Angle)
613
0
                    INTVAL(BorderStyle)
614
0
                    INTVAL(Alignment)
615
0
                    INTVAL(Justify)
616
0
                    INTVAL(MarginL)
617
0
                    INTVAL(MarginR)
618
0
                    INTVAL(MarginV)
619
0
                    INTVAL(Encoding)
620
0
                    FPVAL(ScaleX)
621
0
                    FPVAL(ScaleY)
622
0
                    FPVAL(Outline)
623
0
                    FPVAL(Shadow)
624
0
                    FPVAL(Blur)
625
0
                PARSE_END
626
0
            }
627
0
        }
628
0
        *eq = '=';
629
0
        if (dt)
630
0
            *dt = '.';
631
0
    }
632
0
}
633
634
/**
635
 * \brief Parse the Style line
636
 * \param track track
637
 * \param str string to parse, zero-terminated
638
 * Allocates a new style struct.
639
*/
640
static int process_style(ASS_Track *track, char *str)
641
10.5k
{
642
10.5k
    char *token;
643
10.5k
    char *tname;
644
10.5k
    char *p = str;
645
10.5k
    char *format;
646
10.5k
    char *q;                    // format scanning pointer
647
10.5k
    int sid;
648
10.5k
    ASS_Style *style;
649
10.5k
    ASS_Style *target;
650
651
10.5k
    if (!track->style_format) {
652
        // no style format header
653
        // probably an ancient script version
654
52
        if (track->track_type == TRACK_TYPE_SSA)
655
24
            track->style_format = strdup(ssa_style_format);
656
28
        else
657
28
            track->style_format = strdup(ass_style_format);
658
52
        if (!track->style_format)
659
0
            return -1;
660
52
    }
661
662
10.5k
    q = format = strdup(track->style_format);
663
10.5k
    if (!q)
664
0
        return -1;
665
666
10.5k
    ass_msg(track->library, MSGL_V, "[%p] Style: %s", track, str);
667
668
10.5k
    sid = ass_alloc_style(track);
669
10.5k
    if (sid < 0) {
670
0
        free(format);
671
0
        return -1;
672
0
    }
673
674
10.5k
    style = track->styles + sid;
675
10.5k
    target = style;
676
677
    // fill style with some default values
678
10.5k
    style->ScaleX = 100.;
679
10.5k
    style->ScaleY = 100.;
680
681
10.5k
    int32_t ssa_alpha = 0;
682
683
34.4k
    while (1) {
684
34.4k
        NEXTNAME(q, tname);
685
33.6k
        NEXTVAL(p, token);
686
687
23.8k
        PARSE_START
688
1.80k
            STARREDSTRVAL(Name)
689
1.80k
                if (!target->Name)
690
0
                    goto fail;
691
1.80k
            STRVAL(FontName)
692
1.75k
            COLORVAL(PrimaryColour)
693
1.74k
            COLORVAL(SecondaryColour)
694
1.74k
            COLORVAL(OutlineColour) // TertiaryColor
695
1.73k
            COLORVAL(BackColour)
696
                // SSA uses BackColour for both outline and shadow
697
                // this will destroy SSA's TertiaryColour, but i'm not going to use it anyway
698
1.73k
                if (track->track_type == TRACK_TYPE_SSA)
699
678
                    target->OutlineColour = target->BackColour;
700
14.6k
            } else if (ass_strcasecmp(tname, "AlphaLevel") == 0) {
701
74
                ssa_alpha = parse_int_header(token);
702
1.74k
            FPVAL(FontSize)
703
1.74k
            INTVAL(Bold)
704
1.72k
            INTVAL(Italic)
705
1.71k
            INTVAL(Underline)
706
416
            INTVAL(StrikeOut)
707
414
            FPVAL(Spacing)
708
393
            FPVAL(Angle)
709
1.43k
            INTVAL(BorderStyle)
710
1.43k
            INTVAL(Alignment)
711
1.24k
                if (track->track_type == TRACK_TYPE_ASS)
712
712
                    target->Alignment = numpad2align(target->Alignment);
713
                // VSFilter compatibility
714
534
                else if (target->Alignment == 8)
715
141
                    target->Alignment = 3;
716
393
                else if (target->Alignment == 4)
717
128
                    target->Alignment = 11;
718
1.24k
            INTVAL(MarginL)
719
185
            INTVAL(MarginR)
720
52
            INTVAL(MarginV)
721
42
            INTVAL(Encoding)
722
412
            FPVAL(ScaleX)
723
412
            FPVAL(ScaleY)
724
1.42k
            FPVAL(Outline)
725
4.12k
            FPVAL(Shadow)
726
4.12k
        PARSE_END
727
23.8k
    }
728
729
10.5k
    free(format);
730
10.5k
    format = NULL;
731
732
    // VSF compat: always set BackColour Alpha to 0x80 in SSA
733
10.5k
    if (track->track_type == TRACK_TYPE_SSA)
734
7.12k
        set_style_alpha(style, ssa_alpha, 0x80);
735
10.5k
    style->ScaleX = FFMAX(style->ScaleX, 0.) / 100.;
736
10.5k
    style->ScaleY = FFMAX(style->ScaleY, 0.) / 100.;
737
10.5k
    style->Spacing = FFMAX(style->Spacing, 0.);
738
10.5k
    style->Outline = FFMAX(style->Outline, 0.);
739
10.5k
    style->Shadow = FFMAX(style->Shadow, 0.);
740
10.5k
    style->Bold = !!style->Bold;
741
10.5k
    style->Italic = !!style->Italic;
742
10.5k
    style->Underline = !!style->Underline;
743
10.5k
    style->StrikeOut = !!style->StrikeOut;
744
10.5k
    if (!style->Name || !*style->Name) {
745
10.4k
        free(style->Name);
746
10.4k
        style->Name = strdup("Default");
747
10.4k
    }
748
10.5k
    if (!style->FontName)
749
8.78k
        style->FontName = strdup("Arial");
750
10.5k
    if (!style->Name || !style->FontName)
751
0
        goto fail;
752
10.5k
    if (strcmp(target->Name, "Default") == 0)
753
10.4k
        track->default_style = sid;
754
10.5k
    return 0;
755
756
0
fail:
757
0
    free(format);
758
0
    ass_free_style(track, sid);
759
0
    track->n_styles--;
760
0
    return -1;
761
10.5k
}
762
763
static bool format_line_compare(const char *fmt1, const char *fmt2)
764
116
{
765
116
#define TOKEN_ALIAS1(token, name, alias) \
766
120
    if (token ## _end - token ## _start == sizeof( #alias ) - 1 &&     \
767
120
            !strncmp(token ## _start, #alias, sizeof( #alias ) - 1)) { \
768
0
        token ## _start = #name;                                       \
769
0
        token ## _end = token ## _start + sizeof( #name ) - 1;         \
770
0
    }
771
116
#define TOKEN_ALIAS(name, alias) TOKEN_ALIAS1(tk1, name, alias) TOKEN_ALIAS1(tk2, name, alias)
772
773
120
    while (true) {
774
120
        const char *tk1_start, *tk2_start;
775
120
        const char *tk1_end, *tk2_end;
776
777
120
        skip_spaces((char**)&fmt1);
778
120
        skip_spaces((char**)&fmt2);
779
120
        if (!*fmt1 || !*fmt2)
780
60
            break;
781
782
60
        advance_token_pos(&fmt1, &tk1_start, &tk1_end);
783
60
        advance_token_pos(&fmt2, &tk2_start, &tk2_end);
784
60
        rskip_spaces((char**)&tk1_end, (char*)tk1_start);
785
60
        rskip_spaces((char**)&tk2_end, (char*)tk2_start);
786
787
60
        TOKEN_ALIAS(Name, Actor)
788
60
        if ((tk1_end-tk1_start) != (tk2_end-tk2_start))
789
56
            return false;
790
4
        if (ass_strncasecmp(tk1_start, tk2_start, tk1_end-tk1_start))
791
0
            return false;
792
4
    }
793
60
    return *fmt1 == *fmt2;
794
795
116
#undef TOKEN_ALIAS
796
116
#undef TOKEN_ALIAS1
797
116
}
798
799
800
/**
801
 * \brief Set SBAS=1 if not set explicitly in case of custom format line
802
 * \param track track
803
 * \param fmt   format line of file
804
 * \param std   standard format line
805
 *
806
 * As of writing libass is the only renderer accepting custom format lines.
807
 * For years libass defaulted SBAS to yes instead of no.
808
 * To avoid breaking released scripts with custom format lines,
809
 * keep SBAS=1 default for custom format files.
810
 */
811
static void custom_format_line_compatibility(ASS_Track *const track,
812
                                             const char *const fmt,
813
                                             const char *const std)
814
116
{
815
116
    if (!(track->parser_priv->header_flags & SINFO_SCALEDBORDER)
816
116
        && !format_line_compare(fmt, std)) {
817
116
        ass_msg(track->library, MSGL_INFO,
818
116
               "Track has custom format line(s). "
819
116
                "'ScaledBorderAndShadow' will default to 'yes'.");
820
116
        track->ScaledBorderAndShadow = 1;
821
116
    }
822
116
}
823
824
static int process_styles_line(ASS_Track *track, char *str)
825
12.1k
{
826
12.1k
    int ret = 0;
827
12.1k
    if (!strncmp(str, "Format:", 7)) {
828
73
        char *p = str + 7;
829
73
        skip_spaces(&p);
830
73
        free(track->style_format);
831
73
        track->style_format = strdup(p);
832
73
        if (!track->style_format)
833
0
            return -1;
834
73
        ass_msg(track->library, MSGL_DBG2, "Style format: %s",
835
73
               track->style_format);
836
73
        if (track->track_type == TRACK_TYPE_ASS)
837
64
            custom_format_line_compatibility(track, p, ass_style_format);
838
9
        else
839
9
            custom_format_line_compatibility(track, p, ssa_style_format);
840
12.0k
    } else if (!strncmp(str, "Style:", 6)) {
841
10.5k
        char *p = str + 6;
842
10.5k
        skip_spaces(&p);
843
10.5k
        ret = process_style(track, p);
844
10.5k
    }
845
12.1k
    return ret;
846
12.1k
}
847
848
static inline void parse_script_type(ASS_Track *track, const char *str)
849
3
{
850
    // VSF compat: don't check for leading 'v' and
851
    // parse value from the last non-space backwards
852
3
    const char *p = str + strlen(str);
853
3
    rskip_spaces((char **) &p, (char *) str);
854
3
    size_t len = p - str;
855
3
    if (len < 4) // rskip_spaces stops _at_ last space
856
3
        return;
857
858
0
    int ver = TRACK_TYPE_SSA;
859
0
    if (*(p-1) == '+') {
860
0
        ver = TRACK_TYPE_ASS;
861
0
        --len; --p;
862
0
    }
863
864
0
    if (len >= 4 && !strncmp(p-4, "4.00", 4))
865
0
        track->track_type = ver;
866
0
}
867
868
static inline void check_duplicate_info_line(const ASS_Track *const track,
869
                                             const ScriptInfo si,
870
                                             const char *const name)
871
1.94k
{
872
1.94k
    if (track->parser_priv->header_flags & si)
873
1.91k
        ass_msg(track->library, MSGL_WARN,
874
1.91k
            "Duplicate Script Info Header '%s'. Previous value overwritten!",
875
1.91k
            name);
876
31
    else
877
31
        track->parser_priv->header_flags |= si;
878
1.94k
}
879
880
static int process_info_line(ASS_Track *track, char *str)
881
2.52k
{
882
2.52k
    if (!strncmp(str, "PlayResX:", 9)) {
883
5
        check_duplicate_info_line(track, SINFO_PLAYRESX, "PlayResX");
884
5
        track->PlayResX = parse_int_header(str + 9);
885
2.51k
    } else if (!strncmp(str, "PlayResY:", 9)) {
886
6
        check_duplicate_info_line(track, SINFO_PLAYRESY, "PlayResY");
887
6
        track->PlayResY = parse_int_header(str + 9);
888
2.51k
    } else if (!strncmp(str, "LayoutResX:", 11)) {
889
1
        check_duplicate_info_line(track, SINFO_LAYOUTRESX, "LayoutResX");
890
1
        track->LayoutResX = parse_int_header(str + 11);
891
2.50k
    } else if (!strncmp(str, "LayoutResY:", 11)) {
892
0
        check_duplicate_info_line(track, SINFO_LAYOUTRESY, "LayoutResY");
893
0
        track->LayoutResY = parse_int_header(str + 11);
894
2.50k
    } else if (!strncmp(str, "Timer:", 6)) {
895
1
        check_duplicate_info_line(track, SINFO_TIMER, "Timer");
896
1
        track->Timer = ass_atof(str + 6);
897
2.50k
    } else if (!strncmp(str, "WrapStyle:", 10)) {
898
0
        check_duplicate_info_line(track, SINFO_WRAPSTYLE, "WrapStyle");
899
0
        track->WrapStyle = parse_int_header(str + 10);
900
2.50k
    } else if (!strncmp(str, "ScaledBorderAndShadow:", 22)) {
901
385
        check_duplicate_info_line(track, SINFO_SCALEDBORDER,
902
385
                                    "ScaledBorderAndShadow");
903
385
        track->ScaledBorderAndShadow = parse_bool(str + 22);
904
2.12k
    } else if (!strncmp(str, "Kerning:", 8)) {
905
1
        check_duplicate_info_line(track, SINFO_KERNING, "Kerning");
906
1
        track->Kerning = parse_bool(str + 8);
907
2.12k
    } else if (!strncmp(str, "YCbCr Matrix:", 13)) {
908
1.54k
        check_duplicate_info_line(track, SINFO_COLOURMATRIX, "YCbCr Matrix");
909
1.54k
        track->YCbCrMatrix = parse_ycbcr_matrix(str + 13);
910
1.54k
    } else if (!strncmp(str, "Language:", 9)) {
911
0
        check_duplicate_info_line(track, SINFO_LANGUAGE, "Language");
912
0
        char *p = str + 9;
913
0
        while (*p && ass_isspace(*p)) p++;
914
0
        free(track->Language);
915
0
        track->Language = strndup(p, 2);
916
577
    } else if (!strncmp(str, "ScriptType:", 11)) {
917
3
        check_duplicate_info_line(track, SINFO_SCRIPTTYPE, "ScriptType");
918
3
        parse_script_type(track, str + 11);
919
574
    } else if (!strncmp(str, "; Script generated by ", 22)) {
920
267
        if (!strncmp(str + 22,"FFmpeg/Lavc", 11))
921
132
            track->parser_priv->header_flags |= GENBY_FFMPEG;
922
267
    }
923
2.52k
    return 0;
924
2.52k
}
925
926
static void event_format_fallback(ASS_Track *track)
927
126
{
928
126
    track->parser_priv->state = PST_EVENTS;
929
126
    if (track->track_type == TRACK_TYPE_SSA)
930
105
        track->event_format = strdup(ssa_event_format);
931
21
    else
932
21
        track->event_format = strdup(ass_event_format);
933
126
    ass_msg(track->library, MSGL_V,
934
126
            "No event format found, using fallback");
935
126
}
936
937
/**
938
 * \brief Return if track is post-signature and pre-SBAS ffmpeg track
939
 * \param track track
940
*/
941
static bool detect_legacy_conv_subs(ASS_Track *track)
942
43
{
943
    /*
944
     * FFmpeg and libav convert srt subtitles to ass.
945
     * In legacy versions, they did not set the 'ScaledBorderAndShadow' header,
946
     * but expected it to default to yes (which libass did).
947
     * To avoid breaking them, we try to detect these
948
     * converted subs by common properties of ffmpeg/libav's converted subs.
949
     * Since files with custom format lines (-2014.10.11) default to SBAS=1
950
     * regardless of being ffmpeg generated or not, we are only concerned with
951
     * post-signature and pre-SBAS ffmpeg-files (2014.10.11-2020.04.17).
952
     * We want to avoid matching modified ffmpeg files though.
953
     *
954
     * Relevant ffmpeg commits are:
955
     *  2c77c90684e24ef16f7e7c4462e011434cee6a98  2010.12.29
956
     *    Initial conversion format.
957
     *    Style "Format:" line is mix of SSA and ASS
958
     *    Event "Format:" line
959
     *     "Format: Layer, Start, End, Text\r\n"
960
     *    Only Header in ScriptInfo is "ScriptType: v4.00+"
961
     *  0e7782c08ec77739edb0b98ba5d896b45e98235f  2012.06.15
962
     *    Adds 'Style' to Event "Format:" line
963
     *  5039aadf68deb9ad6dd0737ea11259fe53d3727b  2014.06.18
964
     *    Adds PlayerRes(X|Y) (384x288)
965
     *    (moved below ScriptType: a few minutes later)
966
     *  40b9f28641b696c6bb73ce49dc97c2ce2700cbdb  2014.10.11 14:31:23 +0200
967
     *    Regular full ASS Event and Style "Format:" lines
968
     *  52b0a0ecaa02e17f7e01bead8c3f215f1cfd48dc  2014.10.11 18:37:43 +0200 <==
969
     *    Signature comment
970
     *  56bc0a6736cdc7edab837ff8f304661fd16de0e4  2015.02.08
971
     *    Allow custom PlayRes(X|Y)
972
     *  a8ba2a2c1294a330a0e79ae7f0d3a203a7599166  2020.04.17
973
     *    Set 'ScaledBorderAndShadow: yes'
974
     *
975
     * libav outputs initial ffmpeg format. (no longer maintained)
976
     */
977
978
    // GENBY_FFMPEG and exact ffmpeg headers required
979
43
    if (track->parser_priv->header_flags
980
43
            != (SINFO_SCRIPTTYPE | SINFO_PLAYRESX | SINFO_PLAYRESY | GENBY_FFMPEG))
981
19
        return false;
982
983
    // Legacy ffmpeg only ever has one style
984
    // Check 2 not 1 because libass also adds a def style
985
24
    if (track->n_styles != 2
986
4
        || strncmp(track->styles[1].Name, "Default", 7))
987
20
        return false;
988
989
4
    return true;
990
24
}
991
992
993
static int process_events_line(ASS_Track *track, char *str)
994
12.8k
{
995
12.8k
    if (!strncmp(str, "Format:", 7)) {
996
43
        char *p = str + 7;
997
43
        skip_spaces(&p);
998
43
        free(track->event_format);
999
43
        track->event_format = strdup(p);
1000
43
        if (!track->event_format)
1001
0
            return -1;
1002
43
        ass_msg(track->library, MSGL_DBG2, "Event format: %s", track->event_format);
1003
43
        if (track->track_type == TRACK_TYPE_ASS)
1004
1
            custom_format_line_compatibility(track, p, ass_event_format);
1005
42
        else
1006
42
            custom_format_line_compatibility(track, p, ssa_event_format);
1007
1008
        // Guess if we are dealing with legacy ffmpeg subs and change accordingly
1009
        // If file has no event format it was probably not created by ffmpeg/libav
1010
43
        if (detect_legacy_conv_subs(track)) {
1011
4
            track->ScaledBorderAndShadow = 1;
1012
4
            ass_msg(track->library, MSGL_INFO,
1013
4
                    "Track treated as legacy ffmpeg sub.");
1014
4
        }
1015
12.7k
    } else if (!strncmp(str, "Dialogue:", 9)) {
1016
        // This should never be reached for embedded subtitles.
1017
        // They have slightly different format and are parsed in ass_process_chunk,
1018
        // called directly from demuxer
1019
8.68k
        int eid;
1020
8.68k
        ASS_Event *event;
1021
1022
        // We can't parse events without event_format
1023
8.68k
        if (!track->event_format) {
1024
126
            event_format_fallback(track);
1025
126
            if (!track->event_format)
1026
0
                return -1;
1027
126
        }
1028
1029
8.68k
        str += 9;
1030
8.68k
        skip_spaces(&str);
1031
1032
8.68k
        eid = ass_alloc_event(track);
1033
8.68k
        if (eid < 0)
1034
0
            return -1;
1035
8.68k
        event = track->events + eid;
1036
1037
8.68k
        int ret = process_event_tail(track, event, str, 0);
1038
8.68k
        if (!ret) {
1039
7.73k
            update_prune_ts(track, event->Start + event->Duration);
1040
7.73k
            return 0;
1041
7.73k
        }
1042
        // If something went wrong, discard the useless Event
1043
949
        ass_free_event(track, eid);
1044
949
        track->n_events--;
1045
949
        return ret;
1046
8.68k
    } else if (!strncmp(str, "Comment:", 8)) {
1047
        // Ignore Comments
1048
3.95k
    } else {
1049
3.95k
        ass_msg(track->library, MSGL_V, "Not understood: '%.30s'", str);
1050
3.95k
    }
1051
4.12k
    return 0;
1052
12.8k
}
1053
1054
static unsigned char *decode_chars(const unsigned char *src,
1055
                                   unsigned char *dst, size_t cnt_in)
1056
3.07k
{
1057
3.07k
    uint32_t value = 0;
1058
15.3k
    for (size_t i = 0; i < cnt_in; i++)
1059
12.2k
        value |= (uint32_t) ((src[i] - 33u) & 63) << 6 * (3 - i);
1060
1061
3.07k
    *dst++ = value >> 16;
1062
3.07k
    if (cnt_in >= 3)
1063
3.07k
        *dst++ = value >> 8 & 0xff;
1064
3.07k
    if (cnt_in >= 4)
1065
3.06k
        *dst++ = value & 0xff;
1066
3.07k
    return dst;
1067
3.07k
}
1068
1069
static void reset_embedded_font_parsing(ASS_ParserPriv *parser_priv)
1070
4
{
1071
4
    free(parser_priv->fontname);
1072
4
    free(parser_priv->fontdata);
1073
4
    parser_priv->fontname = NULL;
1074
4
    parser_priv->fontdata = NULL;
1075
4
    parser_priv->fontdata_size = 0;
1076
4
    parser_priv->fontdata_used = 0;
1077
4
}
1078
1079
static int decode_font(ASS_Track *track)
1080
4
{
1081
4
    unsigned char *p;
1082
4
    unsigned char *q;
1083
4
    size_t i;
1084
4
    size_t size;                   // original size
1085
4
    size_t dsize;                  // decoded size
1086
4
    unsigned char *buf = 0;
1087
1088
4
    ass_msg(track->library, MSGL_V, "Font: %zu bytes encoded data",
1089
4
            track->parser_priv->fontdata_used);
1090
4
    size = track->parser_priv->fontdata_used;
1091
4
    if (size % 4 == 1) {
1092
1
        ass_msg(track->library, MSGL_ERR, "Bad encoded data size");
1093
1
        goto error_decode_font;
1094
1
    }
1095
3
    buf = malloc(size / 4 * 3 + FFMAX(size % 4, 1) - 1);
1096
3
    if (!buf)
1097
0
        goto error_decode_font;
1098
3
    q = buf;
1099
3
    for (i = 0, p = (unsigned char *) track->parser_priv->fontdata;
1100
3.07k
         i < size / 4; i++, p += 4) {
1101
3.06k
        q = decode_chars(p, q, 4);
1102
3.06k
    }
1103
3
    if (size % 4 == 2) {
1104
0
        q = decode_chars(p, q, 2);
1105
3
    } else if (size % 4 == 3) {
1106
2
        q = decode_chars(p, q, 3);
1107
2
    }
1108
3
    dsize = q - buf;
1109
3
    assert(dsize == size / 4 * 3 + FFMAX(size % 4, 1) - 1);
1110
1111
3
    if (track->library->extract_fonts) {
1112
0
        ass_add_font(track->library, track->parser_priv->fontname,
1113
0
                     (char *) buf, dsize);
1114
0
    }
1115
1116
4
error_decode_font:
1117
4
    free(buf);
1118
4
    reset_embedded_font_parsing(track->parser_priv);
1119
4
    return 0;
1120
3
}
1121
1122
static int process_fonts_line(ASS_Track *track, char *str)
1123
12
{
1124
12
    size_t len;
1125
1126
12
    if (!strncmp(str, "fontname:", 9)) {
1127
4
        char *p = str + 9;
1128
4
        skip_spaces(&p);
1129
4
        if (track->parser_priv->fontname) {
1130
0
            decode_font(track);
1131
0
        }
1132
4
        track->parser_priv->fontname = strdup(p);
1133
4
        if (!track->parser_priv->fontname)
1134
0
            return -1;
1135
4
        ass_msg(track->library, MSGL_V, "Fontname: %s",
1136
4
                track->parser_priv->fontname);
1137
4
        return 0;
1138
4
    }
1139
1140
8
    if (!track->parser_priv->fontname) {
1141
1
        ass_msg(track->library, MSGL_V, "Not understood: '%s'", str);
1142
1
        return 1;
1143
1
    }
1144
1145
7
    len = strlen(str);
1146
7
    if (track->parser_priv->fontdata_used >=
1147
7
        SIZE_MAX - FFMAX(len, 100 * 1024)) {
1148
0
        goto mem_fail;
1149
7
    } else if (track->parser_priv->fontdata_used + len >
1150
7
               track->parser_priv->fontdata_size) {
1151
4
        size_t new_size =
1152
4
                track->parser_priv->fontdata_size + FFMAX(len, 100 * 1024);
1153
4
        if (!ASS_REALLOC_ARRAY(track->parser_priv->fontdata, new_size))
1154
0
            goto mem_fail;
1155
4
        track->parser_priv->fontdata_size = new_size;
1156
4
    }
1157
7
    if (!track->parser_priv->fontdata)
1158
0
        return 0;
1159
7
    memcpy(track->parser_priv->fontdata + track->parser_priv->fontdata_used,
1160
7
           str, len);
1161
7
    track->parser_priv->fontdata_used += len;
1162
1163
7
    return 0;
1164
1165
0
mem_fail:
1166
0
    reset_embedded_font_parsing(track->parser_priv);
1167
0
    return -1;
1168
7
}
1169
1170
/**
1171
 * \brief Parse a header line
1172
 * \param track track
1173
 * \param str string to parse, zero-terminated
1174
*/
1175
static int process_line(ASS_Track *track, char *str)
1176
28.4k
{
1177
28.4k
    skip_spaces(&str);
1178
28.4k
    if (!ass_strncasecmp(str, "[Script Info]", 13)) {
1179
37
        track->parser_priv->state = PST_INFO;
1180
28.4k
    } else if (!ass_strncasecmp(str, "[V4 Styles]", 11)) {
1181
161
        track->parser_priv->state = PST_STYLES;
1182
161
        track->track_type = TRACK_TYPE_SSA;
1183
28.2k
    } else if (!ass_strncasecmp(str, "[V4+ Styles]", 12)) {
1184
59
        track->parser_priv->state = PST_STYLES;
1185
59
        track->track_type = TRACK_TYPE_ASS;
1186
28.2k
    } else if (!ass_strncasecmp(str, "[Events]", 8)) {
1187
607
        track->parser_priv->state = PST_EVENTS;
1188
27.6k
    } else if (!ass_strncasecmp(str, "[Fonts]", 7)) {
1189
5
        track->parser_priv->state = PST_FONTS;
1190
27.6k
    } else {
1191
27.6k
        switch (track->parser_priv->state) {
1192
2.52k
        case PST_INFO:
1193
2.52k
            process_info_line(track, str);
1194
2.52k
            break;
1195
12.1k
        case PST_STYLES:
1196
12.1k
            process_styles_line(track, str);
1197
12.1k
            break;
1198
12.8k
        case PST_EVENTS:
1199
12.8k
            process_events_line(track, str);
1200
12.8k
            break;
1201
12
        case PST_FONTS:
1202
12
            process_fonts_line(track, str);
1203
12
            break;
1204
126
        default:
1205
126
            break;
1206
27.6k
        }
1207
27.6k
    }
1208
28.4k
    return 0;
1209
28.4k
}
1210
1211
static int process_text(ASS_Track *track, char *str)
1212
196
{
1213
196
    char *p = str;
1214
28.4k
    while (1) {
1215
28.4k
        char *q;
1216
28.6k
        while (1) {
1217
28.6k
            if ((*p == '\r') || (*p == '\n'))
1218
166
                ++p;
1219
28.4k
            else if (p[0] == '\xef' && p[1] == '\xbb' && p[2] == '\xbf')
1220
0
                p += 3;         // U+FFFE (BOM)
1221
28.4k
            else
1222
28.4k
                break;
1223
28.6k
        }
1224
770k
        for (q = p; ((*q != '\0') && (*q != '\r') && (*q != '\n')); ++q) {
1225
741k
        };
1226
28.4k
        if (q == p)
1227
0
            break;
1228
28.4k
        if (*q != '\0')
1229
28.3k
            *(q++) = '\0';
1230
28.4k
        process_line(track, p);
1231
28.4k
        if (*q == '\0')
1232
196
            break;
1233
28.2k
        p = q;
1234
28.2k
    }
1235
    // there is no explicit end-of-font marker in ssa/ass
1236
196
    if (track->parser_priv->fontname)
1237
4
        decode_font(track);
1238
196
    return 0;
1239
196
}
1240
1241
/**
1242
 * \brief Process a chunk of subtitle stream data.
1243
 * \param track track
1244
 * \param data string to parse
1245
 * \param size length of data
1246
*/
1247
void ass_process_data(ASS_Track *track, const char *data, int size)
1248
0
{
1249
0
    char *str = malloc(size + 1);
1250
0
    if (!str)
1251
0
        return;
1252
1253
0
    memcpy(str, data, size);
1254
0
    str[size] = '\0';
1255
1256
0
    ass_msg(track->library, MSGL_V, "Event: %s", str);
1257
0
    process_text(track, str);
1258
0
    free(str);
1259
0
}
1260
1261
/**
1262
 * \brief Process CodecPrivate section of subtitle stream
1263
 * \param track track
1264
 * \param data string to parse
1265
 * \param size length of data
1266
 CodecPrivate section contains [Stream Info] and [V4+ Styles] ([V4 Styles] for SSA) sections
1267
*/
1268
void ass_process_codec_private(ASS_Track *track, const char *data, int size)
1269
0
{
1270
0
    ass_process_data(track, data, size);
1271
1272
    // probably an mkv produced by ancient mkvtoolnix
1273
    // such files don't have [Events] and Format: headers
1274
0
    if (!track->event_format)
1275
0
        event_format_fallback(track);
1276
1277
0
    ass_process_force_style(track);
1278
0
}
1279
1280
static int check_duplicate_event(ASS_Track *track, int ReadOrder)
1281
0
{
1282
0
    if (track->parser_priv->read_order_bitmap)
1283
0
        return test_and_set_read_order_bit(track, ReadOrder) > 0;
1284
    // ignoring last event, it is the one we are comparing with
1285
0
    for (int i = 0; i < track->n_events - 1; i++)
1286
0
        if (track->events[i].ReadOrder == ReadOrder)
1287
0
            return 1;
1288
0
    return 0;
1289
0
}
1290
1291
void ass_set_check_readorder(ASS_Track *track, int check_readorder)
1292
0
{
1293
0
    track->parser_priv->check_readorder = check_readorder == 1;
1294
0
}
1295
1296
/**
1297
 * \brief Process a chunk of subtitle stream data. In Matroska, this contains exactly 1 event (or a commentary).
1298
 * \param track track
1299
 * \param data string to parse
1300
 * \param size length of data
1301
 * \param timecode starting time of the event (milliseconds)
1302
 * \param duration duration of the event (milliseconds)
1303
*/
1304
void ass_process_chunk(ASS_Track *track, const char *data, int size,
1305
                       long long timecode, long long duration)
1306
0
{
1307
0
    char *str = NULL;
1308
0
    int eid;
1309
0
    char *p;
1310
0
    char *token;
1311
0
    ASS_Event *event;
1312
0
    int check_readorder = track->parser_priv->check_readorder;
1313
1314
0
    if (check_readorder && !track->parser_priv->read_order_bitmap) {
1315
0
        for (int i = 0; i < track->n_events; i++) {
1316
0
            if (test_and_set_read_order_bit(track, track->events[i].ReadOrder) < 0)
1317
0
                break;
1318
0
        }
1319
0
    }
1320
1321
0
    if (!track->event_format) {
1322
0
        ass_msg(track->library, MSGL_WARN, "Event format header missing");
1323
0
        goto cleanup;
1324
0
    }
1325
1326
0
    str = malloc(size + 1);
1327
0
    if (!str)
1328
0
        goto cleanup;
1329
0
    memcpy(str, data, size);
1330
0
    str[size] = '\0';
1331
0
    ass_msg(track->library, MSGL_V, "Event at %" PRId64 ", +%" PRId64 ": %s",
1332
0
           (int64_t) timecode, (int64_t) duration, str);
1333
1334
0
    eid = ass_alloc_event(track);
1335
0
    if (eid < 0)
1336
0
        goto cleanup;
1337
0
    event = track->events + eid;
1338
1339
0
    p = str;
1340
1341
0
    do {
1342
0
        NEXTVAL(p, token);
1343
0
        event->ReadOrder = atoi(token);
1344
0
        if (check_readorder && check_duplicate_event(track, event->ReadOrder))
1345
0
            break;
1346
1347
0
        NEXTVAL(p, token);
1348
0
        event->Layer = parse_int_header(token);
1349
1350
0
        if (process_event_tail(track, event, p, 3))
1351
0
            break;
1352
1353
0
        event->Start = timecode;
1354
0
        event->Duration = duration;
1355
0
        update_prune_ts(track, event->Start + event->Duration);
1356
0
        goto cleanup;
1357
//              dump_events(tid);
1358
0
    } while (0);
1359
    // some error
1360
0
    ass_free_event(track, eid);
1361
0
    track->n_events--;
1362
1363
0
cleanup:
1364
0
    free(str);
1365
0
}
1366
1367
/**
1368
 * \brief Flush buffered events.
1369
 * \param track track
1370
*/
1371
void ass_flush_events(ASS_Track *track)
1372
0
{
1373
0
    if (track->events) {
1374
0
        int eid;
1375
0
        for (eid = 0; eid < track->n_events; eid++)
1376
0
            ass_free_event(track, eid);
1377
0
        track->n_events = 0;
1378
0
    }
1379
0
    free(track->parser_priv->read_order_bitmap);
1380
0
    track->parser_priv->read_order_bitmap = NULL;
1381
0
    track->parser_priv->read_order_elems = 0;
1382
0
}
1383
1384
void ass_configure_prune(ASS_Track *track, long long delay)
1385
0
{
1386
0
    track->parser_priv->prune_delay = delay;
1387
0
}
1388
1389
void ass_prune_events(ASS_Track *track, long long deadline)
1390
0
{
1391
0
    if (deadline < track->parser_priv->prune_next_ts)
1392
0
        return;
1393
1394
0
    const bool check_readorder = track->parser_priv->check_readorder;
1395
0
    const int old_n_events = track->n_events;
1396
1397
0
    int n_kept = 0;
1398
0
    ASS_Event *events = track->events;
1399
1400
0
    track->parser_priv->prune_next_ts = LLONG_MAX;
1401
0
    for (int k = 0; k < old_n_events;) {
1402
        // discardable sequence
1403
0
        for (; k < old_n_events && events[k].Start + events[k].Duration < deadline; k++) {
1404
0
            if (check_readorder)
1405
0
                clear_read_order_bit(track, events[k].ReadOrder);
1406
0
            ass_free_event(track, k);
1407
0
        }
1408
1409
        // to-be-kept sequence
1410
0
        int move_from = k;
1411
0
        for (long long ts; k < old_n_events && (ts = events[k].Start + events[k].Duration) >= deadline; k++)
1412
0
            update_prune_ts(track, ts);
1413
1414
        // Relocate kept events
1415
0
        if (move_from < k) {
1416
0
            int cnt = k - move_from;
1417
0
            memmove(events + n_kept, events + move_from, cnt * sizeof(*track->events));
1418
0
            n_kept += cnt;
1419
0
        }
1420
0
    }
1421
0
    track->n_events = n_kept;
1422
0
}
1423
1424
#ifdef CONFIG_ICONV
1425
/** \brief recode buffer to utf-8
1426
 * constraint: codepage != 0
1427
 * \param data pointer to text buffer
1428
 * \param size buffer size
1429
 * \return a pointer to recoded buffer, caller is responsible for freeing it
1430
**/
1431
static char *sub_recode(ASS_Library *library, char *data, size_t size,
1432
                        const char *codepage)
1433
0
{
1434
0
    iconv_t icdsc;
1435
0
    char *tocp = "UTF-8";
1436
0
    char *outbuf;
1437
0
    assert(codepage);
1438
1439
0
    if ((icdsc = iconv_open(tocp, codepage)) != (iconv_t) (-1)) {
1440
0
        ass_msg(library, MSGL_V, "Opened iconv descriptor");
1441
0
    } else {
1442
0
        ass_msg(library, MSGL_ERR, "Error opening iconv descriptor");
1443
0
        return NULL;
1444
0
    }
1445
1446
0
    {
1447
0
        size_t osize = size;
1448
0
        size_t ileft = size;
1449
0
        size_t oleft = size - 1;
1450
0
        char *ip;
1451
0
        char *op;
1452
0
        size_t rc;
1453
0
        int clear = 0;
1454
1455
0
        outbuf = malloc(osize);
1456
0
        if (!outbuf)
1457
0
            goto out;
1458
0
        ip = data;
1459
0
        op = outbuf;
1460
1461
0
        while (1) {
1462
0
            if (ileft)
1463
0
                rc = iconv(icdsc, &ip, &ileft, &op, &oleft);
1464
0
            else {              // clear the conversion state and leave
1465
0
                clear = 1;
1466
0
                rc = iconv(icdsc, NULL, NULL, &op, &oleft);
1467
0
            }
1468
0
            if (rc == (size_t) (-1)) {
1469
0
                if (errno == E2BIG) {
1470
0
                    size_t offset = op - outbuf;
1471
0
                    char *nbuf = realloc(outbuf, osize + size);
1472
0
                    if (!nbuf) {
1473
0
                        free(outbuf);
1474
0
                        outbuf = 0;
1475
0
                        goto out;
1476
0
                    }
1477
0
                    outbuf = nbuf;
1478
0
                    op = outbuf + offset;
1479
0
                    osize += size;
1480
0
                    oleft += size;
1481
0
                } else {
1482
0
                    ass_msg(library, MSGL_WARN, "Error recoding file");
1483
0
                    free(outbuf);
1484
0
                    outbuf = NULL;
1485
0
                    goto out;
1486
0
                }
1487
0
            } else if (clear)
1488
0
                break;
1489
0
        }
1490
0
        outbuf[osize - oleft - 1] = 0;
1491
0
    }
1492
1493
0
out:
1494
0
    if (icdsc != (iconv_t) (-1)) {
1495
0
        (void) iconv_close(icdsc);
1496
0
        ass_msg(library, MSGL_V, "Closed iconv descriptor");
1497
0
    }
1498
1499
0
    return outbuf;
1500
0
}
1501
#endif                          // ICONV
1502
1503
/**
1504
 * \brief read file contents into newly allocated buffer
1505
 * \param fname file name
1506
 * \param hint file name origin
1507
 * \param bufsize out: file size
1508
 * \return pointer to file contents. Caller is responsible for its deallocation.
1509
 */
1510
char *ass_load_file(ASS_Library *library, const char *fname, FileNameSource hint, size_t *bufsize)
1511
0
{
1512
0
    int res;
1513
0
    long sz;
1514
0
    long bytes_read;
1515
0
    char *buf;
1516
1517
0
    FILE *fp = ass_open_file(fname, hint);
1518
0
    if (!fp) {
1519
0
        ass_msg(library, MSGL_WARN,
1520
0
                "ass_read_file(%s): fopen failed", fname);
1521
0
        return 0;
1522
0
    }
1523
0
    res = fseek(fp, 0, SEEK_END);
1524
0
    if (res == -1) {
1525
0
        ass_msg(library, MSGL_WARN,
1526
0
                "ass_read_file(%s): fseek failed", fname);
1527
0
        fclose(fp);
1528
0
        return 0;
1529
0
    }
1530
1531
0
    sz = ftell(fp);
1532
0
    rewind(fp);
1533
1534
0
    ass_msg(library, MSGL_V, "File size: %ld", sz);
1535
1536
0
    buf = sz < SIZE_MAX ? malloc(sz + 1) : NULL;
1537
0
    if (!buf) {
1538
0
        fclose(fp);
1539
0
        return NULL;
1540
0
    }
1541
0
    assert(buf);
1542
0
    bytes_read = 0;
1543
0
    do {
1544
0
        res = fread(buf + bytes_read, 1, sz - bytes_read, fp);
1545
0
        if (res <= 0) {
1546
0
            ass_msg(library, MSGL_INFO, "Read failed, %d: %s", errno,
1547
0
                    strerror(errno));
1548
0
            fclose(fp);
1549
0
            free(buf);
1550
0
            return 0;
1551
0
        }
1552
0
        bytes_read += res;
1553
0
    } while (sz - bytes_read > 0);
1554
0
    buf[sz] = '\0';
1555
0
    fclose(fp);
1556
1557
0
    if (bufsize)
1558
0
        *bufsize = sz;
1559
0
    return buf;
1560
0
}
1561
1562
/*
1563
 * \param buf pointer to subtitle text in utf-8
1564
 */
1565
static ASS_Track *parse_memory(ASS_Library *library, char *buf)
1566
196
{
1567
196
    ASS_Track *track;
1568
196
    int i;
1569
1570
196
    track = ass_new_track(library);
1571
196
    if (!track)
1572
0
        return NULL;
1573
1574
    // process header
1575
196
    process_text(track, buf);
1576
1577
    // external SSA/ASS subs does not have ReadOrder field
1578
7.93k
    for (i = 0; i < track->n_events; ++i)
1579
7.73k
        track->events[i].ReadOrder = i;
1580
1581
196
    if (track->track_type == TRACK_TYPE_UNKNOWN) {
1582
31
        ass_free_track(track);
1583
31
        return 0;
1584
31
    }
1585
1586
165
    ass_process_force_style(track);
1587
1588
165
    return track;
1589
196
}
1590
1591
/**
1592
 * \brief Read subtitles from memory.
1593
 * \param library libass library object
1594
 * \param buf pointer to subtitles text
1595
 * \param bufsize size of buffer
1596
 * \param codepage recode buffer contents from given codepage
1597
 * \return newly allocated track
1598
*/
1599
ASS_Track *ass_read_memory(ASS_Library *library, char *buf,
1600
                           size_t bufsize, const char *codepage)
1601
196
{
1602
196
    ASS_Track *track;
1603
196
    int copied = 0;
1604
1605
196
    if (!buf)
1606
0
        return 0;
1607
1608
196
#ifdef CONFIG_ICONV
1609
196
    if (codepage) {
1610
0
        buf = sub_recode(library, buf, bufsize, codepage);
1611
0
        if (!buf)
1612
0
            return 0;
1613
0
        else
1614
0
            copied = 1;
1615
0
    }
1616
196
#endif
1617
196
    if (!copied) {
1618
196
        char *newbuf = malloc(bufsize + 1);
1619
196
        if (!newbuf)
1620
0
            return 0;
1621
196
        memcpy(newbuf, buf, bufsize);
1622
196
        newbuf[bufsize] = '\0';
1623
196
        buf = newbuf;
1624
196
    }
1625
196
    track = parse_memory(library, buf);
1626
196
    free(buf);
1627
196
    if (!track)
1628
31
        return 0;
1629
1630
165
    ass_msg(library, MSGL_INFO, "Added subtitle file: "
1631
165
            "<memory> (%d styles, %d events)",
1632
165
            track->n_styles, track->n_events);
1633
165
    return track;
1634
196
}
1635
1636
static char *read_file_recode(ASS_Library *library, const char *fname,
1637
                              const char *codepage, size_t *size)
1638
0
{
1639
0
    char *buf;
1640
0
    size_t bufsize;
1641
1642
0
    buf = ass_load_file(library, fname, FN_EXTERNAL, &bufsize);
1643
0
    if (!buf)
1644
0
        return 0;
1645
0
#ifdef CONFIG_ICONV
1646
0
    if (codepage) {
1647
0
        char *tmpbuf = sub_recode(library, buf, bufsize, codepage);
1648
0
        free(buf);
1649
0
        buf = tmpbuf;
1650
0
    }
1651
0
    if (!buf)
1652
0
        return 0;
1653
0
#endif
1654
0
    *size = bufsize;
1655
0
    return buf;
1656
0
}
1657
1658
/**
1659
 * \brief Read subtitles from file.
1660
 * \param library libass library object
1661
 * \param fname file name
1662
 * \param codepage recode buffer contents from given codepage
1663
 * \return newly allocated track
1664
*/
1665
ASS_Track *ass_read_file(ASS_Library *library, const char *fname,
1666
                         const char *codepage)
1667
0
{
1668
0
    char *buf;
1669
0
    ASS_Track *track;
1670
0
    size_t bufsize;
1671
1672
0
    buf = read_file_recode(library, fname, codepage, &bufsize);
1673
0
    if (!buf)
1674
0
        return 0;
1675
0
    track = parse_memory(library, buf);
1676
0
    free(buf);
1677
0
    if (!track)
1678
0
        return 0;
1679
1680
0
    track->name = strdup(fname);
1681
1682
0
    ass_msg(library, MSGL_INFO,
1683
0
            "Added subtitle file: '%s' (%d styles, %d events)",
1684
0
            fname, track->n_styles, track->n_events);
1685
1686
0
    return track;
1687
0
}
1688
1689
/**
1690
 * \brief read styles from file into already initialized track
1691
 */
1692
int ass_read_styles(ASS_Track *track, const char *fname, const char *codepage)
1693
0
{
1694
0
    char *buf;
1695
0
    ParserState old_state;
1696
0
    size_t sz;
1697
1698
0
    buf = ass_load_file(track->library, fname, FN_EXTERNAL, &sz);
1699
0
    if (!buf)
1700
0
        return 1;
1701
0
#ifdef CONFIG_ICONV
1702
0
    if (codepage) {
1703
0
        char *tmpbuf;
1704
0
        tmpbuf = sub_recode(track->library, buf, sz, codepage);
1705
0
        free(buf);
1706
0
        buf = tmpbuf;
1707
0
    }
1708
0
    if (!buf)
1709
0
        return 1;
1710
0
#endif
1711
1712
0
    old_state = track->parser_priv->state;
1713
0
    track->parser_priv->state = PST_STYLES;
1714
0
    process_text(track, buf);
1715
0
    free(buf);
1716
0
    track->parser_priv->state = old_state;
1717
1718
0
    return 0;
1719
0
}
1720
1721
long long ass_step_sub(ASS_Track *track, long long now, int movement)
1722
0
{
1723
0
    int i;
1724
0
    ASS_Event *best = NULL;
1725
0
    long long target = now;
1726
0
    int direction = (movement > 0 ? 1 : -1) * !!movement;
1727
1728
0
    if (track->n_events == 0)
1729
0
        return 0;
1730
1731
0
    do {
1732
0
        ASS_Event *closest = NULL;
1733
0
        long long closest_time = now;
1734
0
        for (i = 0; i < track->n_events; i++) {
1735
0
            if (direction < 0) {
1736
0
                long long end =
1737
0
                    track->events[i].Start + track->events[i].Duration;
1738
0
                if (end < target) {
1739
0
                    if (!closest || end > closest_time) {
1740
0
                        closest = &track->events[i];
1741
0
                        closest_time = end;
1742
0
                    }
1743
0
                }
1744
0
            } else if (direction > 0) {
1745
0
                long long start = track->events[i].Start;
1746
0
                if (start > target) {
1747
0
                    if (!closest || start < closest_time) {
1748
0
                        closest = &track->events[i];
1749
0
                        closest_time = start;
1750
0
                    }
1751
0
                }
1752
0
            } else {
1753
0
                long long start = track->events[i].Start;
1754
0
                if (start < target) {
1755
0
                    if (!closest || start >= closest_time) {
1756
0
                        closest = &track->events[i];
1757
0
                        closest_time = start;
1758
0
                    }
1759
0
                }
1760
0
            }
1761
0
        }
1762
0
        target = closest_time + direction;
1763
0
        movement -= direction;
1764
0
        if (closest)
1765
0
            best = closest;
1766
0
    } while (movement);
1767
1768
0
    return best ? best->Start - now : 0;
1769
0
}
1770
1771
ASS_Track *ass_new_track(ASS_Library *library)
1772
196
{
1773
196
    int def_sid = -1;
1774
196
    ASS_Track *track = calloc(1, sizeof(ASS_Track));
1775
196
    if (!track)
1776
0
        goto fail;
1777
196
    track->library = library;
1778
196
    track->ScaledBorderAndShadow = 0;
1779
196
    track->parser_priv = calloc(1, sizeof(ASS_ParserPriv));
1780
196
    if (!track->parser_priv)
1781
0
        goto fail;
1782
196
    def_sid = ass_alloc_style(track);
1783
196
    if (def_sid < 0)
1784
0
        goto fail;
1785
196
    set_default_style(track->styles + def_sid);
1786
196
    track->default_style = def_sid;
1787
196
    if (!track->styles[def_sid].Name || !track->styles[def_sid].FontName)
1788
0
        goto fail;
1789
196
    track->parser_priv->check_readorder = 1;
1790
196
    track->parser_priv->prune_delay = -1;
1791
196
    track->parser_priv->prune_next_ts = LLONG_MAX;
1792
196
    return track;
1793
1794
0
fail:
1795
0
    if (track) {
1796
0
        if (def_sid >= 0) {
1797
0
            ass_free_style(track, def_sid);
1798
0
            free(track->styles);
1799
0
        }
1800
0
        free(track->parser_priv);
1801
0
        free(track);
1802
0
    }
1803
0
    return NULL;
1804
196
}
1805
1806
int ass_track_set_feature(ASS_Track *track, ASS_Feature feature, int enable)
1807
0
{
1808
0
    if (feature >= sizeof(track->parser_priv->feature_flags) * CHAR_BIT || feature < 0)
1809
0
        return -1;
1810
1811
    // all supported non-meta features
1812
0
    static const uint32_t supported =
1813
0
#ifdef USE_FRIBIDI_EX_API
1814
0
        FEATURE_MASK(ASS_FEATURE_BIDI_BRACKETS) |
1815
0
#endif
1816
#ifdef CONFIG_UNIBREAK
1817
        FEATURE_MASK(ASS_FEATURE_WRAP_UNICODE) |
1818
#endif
1819
0
        FEATURE_MASK(ASS_FEATURE_WHOLE_TEXT_LAYOUT) |
1820
0
        0;
1821
0
    uint32_t requested = 0;
1822
1823
0
    switch (feature) {
1824
0
    case ASS_FEATURE_INCOMPATIBLE_EXTENSIONS:
1825
0
        requested = supported;
1826
0
        break;
1827
0
    default:
1828
0
        if (!(FEATURE_MASK(feature) & supported))
1829
0
            return -1;
1830
0
        requested = FEATURE_MASK(feature);
1831
0
    }
1832
1833
0
    if (enable)
1834
0
        track->parser_priv->feature_flags |= requested;
1835
0
    else
1836
0
        track->parser_priv->feature_flags &= ~requested;
1837
1838
0
    return 0;
1839
0
}
1840
1841
/**
1842
 * \brief Prepare track for rendering
1843
 */
1844
void ass_lazy_track_init(ASS_Library *lib, ASS_Track *track)
1845
14.2k
{
1846
14.2k
    if (track->PlayResX > 0 && track->PlayResY > 0)
1847
14.0k
        return;
1848
125
    if (track->PlayResX <= 0 && track->PlayResY <= 0) {
1849
124
        ass_msg(lib, MSGL_WARN,
1850
124
               "Neither PlayResX nor PlayResY defined. Assuming 384x288");
1851
124
        track->PlayResX = 384;
1852
124
        track->PlayResY = 288;
1853
124
    } else {
1854
1
        if (track->PlayResY <= 0 && track->PlayResX == 1280) {
1855
0
            track->PlayResY = 1024;
1856
0
            ass_msg(lib, MSGL_WARN,
1857
0
                   "PlayResY undefined, setting to %d", track->PlayResY);
1858
1
        } else if (track->PlayResY <= 0) {
1859
            // overflow-safe `* 3/4`
1860
1
            track->PlayResY = FFMAX(1, (track->PlayResX - 1u) - (track->PlayResX - 1u) / 4u);
1861
1
            ass_msg(lib, MSGL_WARN,
1862
1
                   "PlayResY undefined, setting to %d", track->PlayResY);
1863
1
        } else if (track->PlayResX <= 0 && track->PlayResY == 1024) {
1864
0
            track->PlayResX = 1280;
1865
0
            ass_msg(lib, MSGL_WARN,
1866
0
                   "PlayResX undefined, setting to %d", track->PlayResX);
1867
0
        } else if (track->PlayResX <= 0) {
1868
0
            track->PlayResX = FFMIN(track->PlayResY + track->PlayResY / 3u, (unsigned) INT_MAX);
1869
0
            ass_msg(lib, MSGL_WARN,
1870
0
                   "PlayResX undefined, setting to %d", track->PlayResX);
1871
0
        }
1872
1
    }
1873
125
}