Coverage Report

Created: 2025-08-11 06:44

/src/mpv/sub/sd_ass.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
#include <stdlib.h>
19
#include <assert.h>
20
#include <string.h>
21
#include <math.h>
22
#include <limits.h>
23
24
#include <libavutil/common.h>
25
#include <ass/ass.h>
26
27
#include "mpv_talloc.h"
28
29
#include "config.h"
30
#include "options/m_config.h"
31
#include "options/options.h"
32
#include "options/path.h"
33
#include "common/common.h"
34
#include "common/msg.h"
35
#include "demux/demux.h"
36
#include "demux/packet_pool.h"
37
#include "video/csputils.h"
38
#include "video/mp_image.h"
39
#include "dec_sub.h"
40
#include "ass_mp.h"
41
#include "sd.h"
42
43
struct sd_ass_priv {
44
    struct ass_library *ass_library;
45
    struct ass_renderer *ass_renderer;
46
    struct ass_track *ass_track;
47
    struct ass_track *shadow_track; // for --sub-ass=no rendering
48
    bool ass_configured;
49
    bool is_converted;
50
    struct lavc_conv *converter;
51
    struct sd_filter **filters;
52
    int num_filters;
53
    bool clear_once;
54
    struct mp_ass_packer *packer;
55
    struct sub_bitmap_copy_cache *copy_cache;
56
    bstr last_text;
57
    struct mp_image_params video_params;
58
    struct mp_image_params last_params;
59
    struct mp_osd_res osd;
60
    struct seen_packet *seen_packets;
61
    int num_seen_packets;
62
    int *packets_animated;
63
    int num_packets_animated;
64
    bool check_animated;
65
};
66
67
struct seen_packet {
68
    int64_t pos;
69
    double pts;
70
};
71
72
#undef OPT_BASE_STRUCT
73
#define OPT_BASE_STRUCT struct mp_sub_filter_opts
74
75
const struct m_sub_options mp_sub_filter_opts = {
76
    .opts = (const struct m_option[]){
77
        {"sdh", OPT_BOOL(sub_filter_SDH)},
78
        {"sdh-harder", OPT_BOOL(sub_filter_SDH_harder)},
79
        {"sdh-enclosures", OPT_STRINGLIST(sub_filter_SDH_enclosures)},
80
        {"regex-enable", OPT_BOOL(rf_enable)},
81
        {"regex-plain", OPT_BOOL(rf_plain)},
82
        {"regex", OPT_STRINGLIST(rf_items)},
83
        {"jsre", OPT_STRINGLIST(jsre_items)},
84
        {"regex-warn", OPT_BOOL(rf_warn)},
85
        {0}
86
    },
87
    .size = sizeof(OPT_BASE_STRUCT),
88
    .defaults = &(OPT_BASE_STRUCT){
89
        .sub_filter_SDH_enclosures = (char *[]) {
90
            "()",
91
            "[]",
92
            "\uFF08\uFF09",
93
            NULL
94
        },
95
        .rf_enable = true,
96
    },
97
    .change_flags = UPDATE_SUB_FILT,
98
};
99
100
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts);
101
static void fill_plaintext(struct sd *sd, double pts);
102
103
static const struct sd_filter_functions *const filters[] = {
104
    // Note: list order defines filter order.
105
    &sd_filter_sdh,
106
#if HAVE_POSIX
107
    &sd_filter_regex,
108
#endif
109
#if HAVE_JAVASCRIPT
110
    &sd_filter_jsre,
111
#endif
112
    NULL,
113
};
114
115
// Add default styles, if the track does not have any styles yet.
116
// Apply style overrides if the user provides any.
117
static void mp_ass_add_default_styles(struct sd *sd, ASS_Track *track, struct mp_subtitle_opts *opts,
118
                                      struct mp_subtitle_shared_opts *shared_opts)
119
1.98k
{
120
1.98k
    if (opts->ass_styles_file && shared_opts->ass_style_override[sd->order]) {
121
0
        char *file = mp_get_user_path(NULL, sd->global, opts->ass_styles_file);
122
0
        ass_read_styles(track, file, NULL);
123
0
        talloc_free(file);
124
0
    }
125
126
1.98k
    if (track->n_styles == 0) {
127
0
        if (!track->PlayResY) {
128
0
            track->PlayResX = MP_ASS_FONT_PLAYRESX;
129
0
            track->PlayResY = MP_ASS_FONT_PLAYRESY;
130
0
        }
131
0
        track->Kerning = true;
132
0
        int sid = ass_alloc_style(track);
133
0
        track->default_style = sid;
134
0
        ASS_Style *style = track->styles + sid;
135
0
        style->Name = strdup("Default");
136
0
        mp_ass_set_style(style, track->PlayResY, opts->sub_style);
137
0
    }
138
139
1.98k
    if (shared_opts->ass_style_override[sd->order])
140
1.98k
        ass_process_force_style(track);
141
1.98k
}
142
143
static const char *const font_mimetypes[] = {
144
    "application/x-truetype-font",
145
    "application/vnd.ms-opentype",
146
    "application/x-font-otf",
147
    "application/x-font-ttf",
148
    "application/x-font", // probably incorrect
149
    "application/font-sfnt",
150
    "font/collection",
151
    "font/otf",
152
    "font/sfnt",
153
    "font/ttf",
154
    NULL
155
};
156
157
static const char *const font_exts[] = {".ttf", ".ttc", ".otf", ".otc", NULL};
158
159
static bool attachment_is_font(struct mp_log *log, struct demux_attachment *f)
160
73
{
161
73
    if (!f->name || !f->type || !f->data || !f->data_size)
162
0
        return false;
163
143
    for (int n = 0; font_mimetypes[n]; n++) {
164
136
        if (strcmp(font_mimetypes[n], f->type) == 0)
165
66
            return true;
166
136
    }
167
    // fallback: match against file extension
168
7
    char *ext = strlen(f->name) > 4 ? f->name + strlen(f->name) - 4 : "";
169
31
    for (int n = 0; font_exts[n]; n++) {
170
25
        if (strcasecmp(ext, font_exts[n]) == 0) {
171
1
            mp_warn(log, "Loading font attachment '%s' with MIME type %s. "
172
1
                    "Assuming this is a broken Matroska file, which was "
173
1
                    "muxed without setting a correct font MIME type.\n",
174
1
                    f->name, f->type);
175
1
            return true;
176
1
        }
177
25
    }
178
6
    return false;
179
7
}
180
181
static void add_subtitle_fonts(struct sd *sd)
182
994
{
183
994
    struct sd_ass_priv *ctx = sd->priv;
184
994
    struct mp_subtitle_opts *opts = sd->opts;
185
994
    if (!opts->ass_enabled || !opts->use_embedded_fonts || !sd->attachments)
186
0
        return;
187
1.06k
    for (int i = 0; i < sd->attachments->num_entries; i++) {
188
73
        struct demux_attachment *f = &sd->attachments->entries[i];
189
73
        if (attachment_is_font(sd->log, f))
190
67
            ass_add_font(ctx->ass_library, f->name, f->data, f->data_size);
191
73
    }
192
994
}
193
194
static void filters_destroy(struct sd *sd)
195
1.98k
{
196
1.98k
    struct sd_ass_priv *ctx = sd->priv;
197
198
1.98k
    for (int n = 0; n < ctx->num_filters; n++) {
199
0
        struct sd_filter *ft = ctx->filters[n];
200
0
        if (ft->driver->uninit)
201
0
            ft->driver->uninit(ft);
202
0
        talloc_free(ft);
203
0
    }
204
1.98k
    ctx->num_filters = 0;
205
1.98k
}
206
207
static void filters_init(struct sd *sd)
208
994
{
209
994
    struct sd_ass_priv *ctx = sd->priv;
210
211
994
    filters_destroy(sd);
212
213
2.98k
    for (int n = 0; filters[n]; n++) {
214
1.98k
        struct sd_filter *ft = talloc_ptrtype(ctx, ft);
215
1.98k
        *ft = (struct sd_filter){
216
1.98k
            .global = sd->global,
217
1.98k
            .log = sd->log,
218
1.98k
            .packet_pool = demux_packet_pool_get(sd->global),
219
1.98k
            .opts = mp_get_config_group(ft, sd->global, &mp_sub_filter_opts),
220
1.98k
            .driver = filters[n],
221
1.98k
            .codec = "ass",
222
1.98k
            .event_format = talloc_strdup(ft, ctx->ass_track->event_format),
223
1.98k
        };
224
1.98k
        if (ft->driver->init(ft)) {
225
0
            MP_TARRAY_APPEND(ctx, ctx->filters, ctx->num_filters, ft);
226
1.98k
        } else {
227
1.98k
            talloc_free(ft);
228
1.98k
        }
229
1.98k
    }
230
994
}
231
232
static void enable_output(struct sd *sd, bool enable)
233
3.97k
{
234
3.97k
    struct sd_ass_priv *ctx = sd->priv;
235
3.97k
    if (enable == !!ctx->ass_renderer)
236
1.98k
        return;
237
1.98k
    if (ctx->ass_renderer) {
238
994
        ass_renderer_done(ctx->ass_renderer);
239
994
        ctx->ass_renderer = NULL;
240
994
    } else {
241
994
        ctx->ass_renderer = ass_renderer_init(ctx->ass_library);
242
243
994
        mp_ass_configure_fonts(ctx->ass_renderer, sd->opts->sub_style,
244
994
                               sd->global, sd->log);
245
994
    }
246
1.98k
}
247
248
static void assobjects_init(struct sd *sd)
249
994
{
250
994
    struct sd_ass_priv *ctx = sd->priv;
251
994
    struct mp_subtitle_opts *opts = sd->opts;
252
994
    struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
253
254
994
    ctx->ass_library = mp_ass_init(sd->global, sd->opts->sub_style, sd->log);
255
994
    ass_set_extract_fonts(ctx->ass_library, opts->use_embedded_fonts);
256
257
994
    add_subtitle_fonts(sd);
258
259
994
    if (shared_opts->ass_style_override[sd->order])
260
994
        ass_set_style_overrides(ctx->ass_library, opts->ass_style_override_list);
261
262
994
    ctx->ass_track = ass_new_track(ctx->ass_library);
263
994
    ctx->ass_track->track_type = TRACK_TYPE_ASS;
264
265
994
    ctx->shadow_track = ass_new_track(ctx->ass_library);
266
994
    ctx->shadow_track->PlayResX = MP_ASS_FONT_PLAYRESX;
267
994
    ctx->shadow_track->PlayResY = MP_ASS_FONT_PLAYRESY;
268
994
    mp_ass_add_default_styles(sd, ctx->shadow_track, opts, shared_opts);
269
270
994
    char *extradata = sd->codec->extradata;
271
994
    int extradata_size = sd->codec->extradata_size;
272
994
    if (ctx->converter) {
273
629
        extradata = lavc_conv_get_extradata(ctx->converter);
274
629
        extradata_size = extradata ? strlen(extradata) : 0;
275
629
    }
276
994
    if (extradata)
277
974
        ass_process_codec_private(ctx->ass_track, extradata, extradata_size);
278
279
994
    mp_ass_add_default_styles(sd, ctx->ass_track, opts, shared_opts);
280
281
994
#if LIBASS_VERSION >= 0x01302000
282
994
    ass_set_check_readorder(ctx->ass_track, sd->opts->sub_clear_on_seek ? 0 : 1);
283
994
#endif
284
285
994
#if LIBASS_VERSION >= 0x01703010
286
994
    ass_configure_prune(ctx->ass_track, sd->opts->ass_prune_delay * 1000.0);
287
994
#endif
288
289
994
    enable_output(sd, true);
290
994
    ass_set_cache_limits(ctx->ass_renderer, sd->opts->sub_glyph_limit, sd->opts->sub_bitmap_max_size);
291
994
}
292
293
static void assobjects_destroy(struct sd *sd)
294
994
{
295
994
    struct sd_ass_priv *ctx = sd->priv;
296
297
994
    ass_free_track(ctx->ass_track);
298
994
    ass_free_track(ctx->shadow_track);
299
994
    enable_output(sd, false);
300
994
    ass_library_done(ctx->ass_library);
301
994
}
302
303
static int init(struct sd *sd)
304
1.03k
{
305
1.03k
    struct sd_ass_priv *ctx = talloc_zero(sd, struct sd_ass_priv);
306
1.03k
    sd->priv = ctx;
307
308
    // Note: accept "null" as alias for "ass", so EDL delay_open subtitle
309
    //       streams work.
310
1.03k
    if (strcmp(sd->codec->codec, "ass") != 0 &&
311
1.03k
        strcmp(sd->codec->codec, "null") != 0)
312
673
    {
313
673
        ctx->is_converted = true;
314
673
        ctx->converter = lavc_conv_create(sd);
315
673
        if (!ctx->converter)
316
44
            return -1;
317
673
    }
318
319
994
    assobjects_init(sd);
320
994
    filters_init(sd);
321
322
994
    ctx->packer = mp_ass_packer_alloc(ctx);
323
324
    // Subtitles does not have any profile value, so put the converted type as a profile.
325
994
    const char *_Atomic *desc = ctx->converter ? &sd->codec->codec_profile : &sd->codec->codec_desc;
326
994
    switch (ctx->ass_track->track_type) {
327
760
    case TRACK_TYPE_ASS:
328
760
        *desc = "Advanced Sub Station Alpha";
329
760
        break;
330
234
    case TRACK_TYPE_SSA:
331
234
        *desc = "Sub Station Alpha";
332
234
        break;
333
994
    }
334
335
994
    return 0;
336
994
}
337
338
// Check if subtitle has events that would cause it to be animated inside {}
339
static bool is_animated(const char *str)
340
76
{
341
76
    const char *begin = str;
342
77
    while ((str = strchr(str, '{'))) {
343
75
        if (str++ > begin && str[-2] == '\\')
344
0
            continue;
345
346
75
        const char *end = strchr(str, '}');
347
75
        if (!end)
348
1
            return false;
349
350
300
        while ((str = memchr(str, '\\', end - str))) {
351
598
            while (str[0] == '\\')
352
299
                ++str;
353
299
            while (str[0] == ' ' || str[0] == '\t')
354
0
                ++str;
355
299
            if (str[0] == 'k' || str[0] == 'K' || str[0] == 't' ||
356
299
                (str[0] == 'f' && str[1] == 'a' && str[2] == 'd') ||
357
299
                (str[0] == 'm' && str[1] == 'o' && str[2] == 'v' && str[3] == 'e'))
358
73
            {
359
73
                return true;
360
73
            }
361
299
        }
362
363
1
        str = end + 1;
364
1
    }
365
366
2
    return false;
367
76
}
368
369
// Note: pkt is not necessarily a fully valid refcounted packet.
370
static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
371
1.11k
{
372
1.11k
    struct sd_ass_priv *ctx = sd->priv;
373
1.11k
    struct demux_packet *orig_pkt = pkt;
374
1.11k
    ASS_Track *track = ctx->ass_track;
375
1.11k
    int old_n_events = track->n_events;
376
377
1.11k
    for (int n = 0; n < ctx->num_filters; n++) {
378
0
        struct sd_filter *ft = ctx->filters[n];
379
0
        struct demux_packet *npkt = ft->driver->filter(ft, pkt);
380
0
        if (pkt != npkt && pkt != orig_pkt)
381
0
            talloc_free(pkt);
382
0
        pkt = npkt;
383
0
        if (!pkt)
384
0
            return;
385
0
    }
386
387
1.11k
    ass_process_chunk(ctx->ass_track, pkt->buffer, pkt->len,
388
1.11k
                      llrint(pkt->pts * 1000),
389
1.11k
                      llrint(pkt->duration * 1000));
390
391
    // This bookkeeping only has any practical use for ASS subs
392
    // over a VO with no video.
393
1.11k
    if (!ctx->is_converted) {
394
200
        if (!pkt->seen) {
395
366
            for (int n = track->n_events - 1; n >= 0; n--) {
396
265
                if (n + 1 == old_n_events || pkt->animated == 1)
397
93
                    break;
398
172
                ASS_Event *event = &track->events[n];
399
                // Might as well mark pkt->animated here with effects if we can.
400
172
                pkt->animated = (event->Effect && event->Effect[0]) ? 1 : -1;
401
172
                if (ctx->check_animated && pkt->animated != 1)
402
76
                    pkt->animated = is_animated(event->Text);
403
172
            }
404
194
            MP_TARRAY_APPEND(ctx, ctx->packets_animated, ctx->num_packets_animated, pkt->animated);
405
194
        } else {
406
6
            if (ctx->check_animated && ctx->packets_animated[pkt->seen_pos] == -1) {
407
0
                for (int n = track->n_events - 1; n >= 0; n--) {
408
0
                    if (n + 1 == old_n_events || pkt->animated == 1)
409
0
                        break;
410
0
                    ASS_Event *event = &track->events[n];
411
0
                    ctx->packets_animated[pkt->seen_pos] = is_animated(event->Text);
412
0
                    pkt->animated = ctx->packets_animated[pkt->seen_pos];
413
0
                }
414
6
            } else {
415
6
                pkt->animated = ctx->packets_animated[pkt->seen_pos];
416
6
            }
417
6
        }
418
200
    }
419
420
1.11k
    if (pkt != orig_pkt)
421
0
        talloc_free(pkt);
422
1.11k
}
423
424
// Test if the packet with the given file position and pts was already consumed.
425
// Return false if the packet is new (and add it to the internal list), and
426
// return true if it was already seen.
427
static bool check_packet_seen(struct sd *sd, struct demux_packet *packet)
428
1.11k
{
429
1.11k
    struct sd_ass_priv *priv = sd->priv;
430
1.11k
    int a = 0;
431
1.11k
    int b = priv->num_seen_packets;
432
3.13k
    while (a < b) {
433
2.02k
        int mid = a + (b - a) / 2;
434
2.02k
        struct seen_packet *seen_packet = &priv->seen_packets[mid];
435
2.02k
        if (packet->pos == seen_packet->pos && packet->pts == seen_packet->pts) {
436
6
            packet->seen_pos = mid;
437
6
            return true;
438
6
        }
439
2.02k
        if (packet->pos > seen_packet->pos ||
440
2.02k
            (packet->pos == seen_packet->pos && packet->pts > seen_packet->pts)) {
441
2.01k
            a = mid + 1;
442
2.01k
        } else {
443
3
            b = mid;
444
3
        }
445
2.02k
    }
446
1.10k
    packet->seen_pos = a;
447
1.10k
    MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a,
448
1.10k
                        (struct seen_packet){packet->pos, packet->pts});
449
1.10k
    return false;
450
1.10k
}
451
452
1.01k
#define UNKNOWN_DURATION (INT_MAX / 1000)
453
454
static void decode(struct sd *sd, struct demux_packet *packet)
455
1.11k
{
456
1.11k
    struct sd_ass_priv *ctx = sd->priv;
457
1.11k
    ASS_Track *track = ctx->ass_track;
458
459
1.11k
    packet->sub_duration = packet->duration;
460
461
1.11k
    if (ctx->converter) {
462
914
        if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 &&
463
914
            check_packet_seen(sd, packet))
464
0
            return;
465
466
914
        double sub_pts = 0;
467
914
        double sub_duration = 0;
468
914
        char **r = lavc_conv_decode(ctx->converter, packet, &sub_pts,
469
914
                                    &sub_duration);
470
914
        if (sd->opts->sub_stretch_durations ||
471
914
            packet->duration < 0 || sub_duration == UINT32_MAX) {
472
500
            MP_VERBOSE(sd, "Subtitle with unknown duration.\n");
473
500
            sub_duration = UNKNOWN_DURATION;
474
500
        }
475
476
1.82k
        for (int n = 0; r && r[n]; n++) {
477
914
            struct demux_packet pkt2 = {
478
914
                .pts = sub_pts,
479
914
                .duration = sub_duration,
480
914
                .buffer = r[n],
481
914
                .len = strlen(r[n]),
482
914
            };
483
914
            filter_and_add(sd, &pkt2);
484
914
        }
485
2.08k
        for (int n = track->n_events - 1; n >= 0; n--) {
486
1.64k
            if (track->events[track->n_events - 1].Start == track->events[n].Start)
487
1.13k
                continue;
488
511
            if (track->events[n].Duration == UNKNOWN_DURATION * 1000) {
489
480
                if (track->events[n].Start < track->events[n + 1].Start) {
490
448
                    track->events[n].Duration = track->events[n + 1].Start -
491
448
                                                track->events[n].Start;
492
448
                } else if (track->events[n].Start == track->events[n + 1].Start) {
493
1
                    track->events[n].Duration = track->events[n + 1].Duration;
494
1
                }
495
480
            }
496
511
            if (n > 0 && track->events[n].Start != track->events[n - 1].Start)
497
469
                break;
498
511
        }
499
914
    } else {
500
        // Note that for this packet format, libass has an internal mechanism
501
        // for discarding duplicate (already seen) packets but we check this
502
        // anyways for our purposes for ASS subtitles.
503
200
        packet->seen = check_packet_seen(sd, packet);
504
200
        filter_and_add(sd, packet);
505
200
    }
506
1.11k
}
507
508
// Calculate the height used for scaling subtitle text size so --sub-scale-with-window
509
// can undo this scale and use frame size instead. The algorithm used is the following:
510
// - If use_margins is disabled, the text is scaled with the visual size of the video.
511
// - If use_margins is enabled, the text is scaled with the size of the video
512
//   as if the video is resized to "fit" the size of the frame.
513
static float get_libass_scale_height(struct mp_osd_res *dim, bool use_margins)
514
0
{
515
0
    float vidw = dim->w - (dim->ml + dim->mr);
516
0
    float vidh = dim->h - (dim->mt + dim->mb);
517
0
    if (!use_margins || vidw < 1.0)
518
0
        return vidh;
519
0
    else
520
0
        return MPMIN(dim->h, dim->w / vidw * vidh);
521
0
}
522
523
static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
524
                          bool converted, ASS_Track *track)
525
0
{
526
0
    struct mp_subtitle_opts *opts = sd->opts;
527
0
    struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
528
0
    struct sd_ass_priv *ctx = sd->priv;
529
0
    ASS_Renderer *priv = ctx->ass_renderer;
530
531
0
    ass_set_frame_size(priv, dim->w, dim->h);
532
0
    ass_set_margins(priv, dim->mt, dim->mb, dim->ml, dim->mr);
533
534
0
    bool set_use_margins = false;
535
0
    float set_sub_pos = 0.0f;
536
0
    float set_line_spacing = 0;
537
0
    float set_font_scale = 1;
538
0
    int set_hinting = 0;
539
0
    bool set_scale_with_window = false;
540
0
    bool set_scale_by_window = true;
541
0
    bool total_override = false;
542
    // With forced overrides, apply the --sub-* specific options
543
0
    if (converted || shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_FORCE) {
544
0
        set_scale_with_window = opts->sub_scale_with_window;
545
0
        set_use_margins = opts->sub_use_margins;
546
0
        set_scale_by_window = opts->sub_scale_by_window;
547
0
        total_override = true;
548
0
    } else {
549
0
        set_scale_with_window = opts->ass_scale_with_window;
550
0
        set_use_margins = opts->ass_use_margins;
551
0
    }
552
0
    if (converted || shared_opts->ass_style_override[sd->order]) {
553
0
        set_sub_pos = 100.0f - shared_opts->sub_pos[sd->order];
554
0
        set_line_spacing = opts->sub_line_spacing;
555
0
        set_hinting = opts->sub_hinting;
556
0
    }
557
0
    if (total_override || shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_SCALE) {
558
0
        set_font_scale = opts->sub_scale;
559
0
    }
560
0
    if (set_scale_with_window) {
561
0
        set_font_scale *= dim->h / MPMAX(get_libass_scale_height(dim, set_use_margins), 1);
562
0
    }
563
0
    if (!set_scale_by_window) {
564
0
        double factor = dim->h / 720.0;
565
0
        if (factor != 0.0)
566
0
            set_font_scale /= factor;
567
0
    }
568
0
    ass_set_use_margins(priv, set_use_margins);
569
0
    ass_set_line_position(priv, set_sub_pos);
570
0
    ass_set_shaper(priv, opts->sub_shaper);
571
0
    int set_force_flags = 0;
572
0
    if (total_override) {
573
0
        set_force_flags |= ASS_OVERRIDE_BIT_FONT_NAME
574
0
                            | ASS_OVERRIDE_BIT_FONT_SIZE_FIELDS
575
0
                            | ASS_OVERRIDE_BIT_COLORS
576
0
                            | ASS_OVERRIDE_BIT_BORDER;
577
0
        if (!opts->sub_scale_signs)
578
0
            set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
579
0
#if LIBASS_VERSION >= 0x01703020
580
0
        set_force_flags |= ASS_OVERRIDE_BIT_BLUR;
581
0
#endif
582
0
    }
583
0
    if (shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_SCALE &&
584
0
        !opts->sub_scale_signs)
585
0
        set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
586
0
    if (converted)
587
0
        set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT;
588
0
#if LIBASS_VERSION >= 0x01306000
589
0
    if ((converted || shared_opts->ass_style_override[sd->order]) && opts->ass_justify)
590
0
        set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY;
591
0
#endif
592
0
    ass_set_selective_style_override_enabled(priv, set_force_flags);
593
0
    ASS_Style style = {0};
594
0
    mp_ass_set_style(&style, MP_ASS_FONT_PLAYRESY, opts->sub_style);
595
0
    ass_set_selective_style_override(priv, &style);
596
0
    free(style.FontName);
597
0
    if (converted && track->default_style < track->n_styles) {
598
0
        mp_ass_set_style(track->styles + track->default_style,
599
0
                         track->PlayResY, opts->sub_style);
600
0
    }
601
0
    ass_set_font_scale(priv, set_font_scale);
602
0
    ass_set_hinting(priv, set_hinting);
603
0
    ass_set_line_spacing(priv, set_line_spacing);
604
0
#if LIBASS_VERSION >= 0x01600010
605
0
    if (converted) {
606
0
        ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1);
607
0
        if (!opts->sub_vsfilter_bidi_compat) {
608
0
            for (int n = 0; n < track->n_styles; n++) {
609
0
                track->styles[n].Encoding = -1;
610
0
            }
611
0
            ass_track_set_feature(track, ASS_FEATURE_BIDI_BRACKETS, 1);
612
0
            ass_track_set_feature(track, ASS_FEATURE_WHOLE_TEXT_LAYOUT, 1);
613
0
        }
614
0
    }
615
0
#endif
616
0
    if (converted) {
617
0
        bool override_playres = true;
618
0
        char **ass_style_override_list = opts->ass_style_override_list;
619
0
        for (int i = 0; ass_style_override_list && ass_style_override_list[i]; i++) {
620
0
            if (bstr_find0(bstr0(ass_style_override_list[i]), "PlayResX") >= 0)
621
0
                override_playres = false;
622
0
        }
623
624
        // srt to ass conversion from ffmpeg has fixed PlayResX of 384 with an
625
        // aspect of 4:3. Starting with libass f08f8ea5 (pre 0.17) PlayResX
626
        // affects shadow and border widths, among others, so to render borders
627
        // and shadows correctly, we adjust PlayResX according to the DAR.
628
        // But PlayResX also affects margins, so we adjust those too.
629
        // This should ensure basic srt-to-ass ffmpeg conversion has correct
630
        // borders, but there could be other issues with some srt extensions
631
        // and/or different source formats which would be exposed over time.
632
        // Make these adjustments only if the user didn't set PlayResX.
633
0
        if (override_playres) {
634
0
            int vidw = dim->w - (dim->ml + dim->mr);
635
0
            int vidh = dim->h - (dim->mt + dim->mb);
636
0
            track->PlayResX = track->PlayResY * (double)vidw / MPMAX(vidh, 1);
637
            // ffmpeg and mpv use a default PlayResX of 384 when it is not known,
638
            // this comes from VSFilter.
639
0
            double fix_margins = track->PlayResX / (double)MP_ASS_FONT_PLAYRESX;
640
0
            for (int n = 0; n < track->n_styles; n++) {
641
0
                track->styles[n].MarginL = lrint(track->styles[n].MarginL * fix_margins);
642
0
                track->styles[n].MarginR = lrint(track->styles[n].MarginR * fix_margins);
643
0
                track->styles[n].MarginV = lrint(track->styles[n].MarginV * set_font_scale);
644
0
            }
645
0
        }
646
0
    }
647
0
}
648
649
static bool has_overrides(char *s)
650
0
{
651
0
    if (!s)
652
0
        return false;
653
0
    return strstr(s, "\\pos") || strstr(s, "\\move") || strstr(s, "\\clip") ||
654
0
           strstr(s, "\\iclip") || strstr(s, "\\org") || strstr(s, "\\p");
655
0
}
656
657
0
#define END(ev) ((ev)->Start + (ev)->Duration)
658
659
static long long find_timestamp(struct sd *sd, double pts)
660
32
{
661
32
    struct sd_ass_priv *priv = sd->priv;
662
32
    if (pts == MP_NOPTS_VALUE)
663
0
        return 0;
664
665
32
    long long ts = llrint(pts * 1000);
666
667
32
    if (!sd->opts->sub_fix_timing ||
668
32
        sd->shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_NONE)
669
32
        return ts;
670
671
    // Try to fix small gaps and overlaps.
672
0
    ASS_Track *track = priv->ass_track;
673
0
    int threshold = SUB_GAP_THRESHOLD * 1000;
674
0
    int keep = SUB_GAP_KEEP * 1000;
675
676
    // Find the "current" event.
677
0
    ASS_Event *ev[2] = {0};
678
0
    int n_ev = 0;
679
0
    for (int n = 0; n < track->n_events; n++) {
680
0
        ASS_Event *event = &track->events[n];
681
0
        if (ts >= event->Start - threshold && ts <= END(event) + threshold) {
682
0
            if (n_ev >= MP_ARRAY_SIZE(ev))
683
0
                return ts; // multiple overlaps - give up (probably complex subs)
684
0
            ev[n_ev++] = event;
685
0
        }
686
0
    }
687
688
0
    if (n_ev != 2)
689
0
        return ts;
690
691
    // Simple/minor heuristic against destroying typesetting.
692
0
    if (ev[0]->Style != ev[1]->Style || has_overrides(ev[0]->Text) ||
693
0
        has_overrides(ev[1]->Text))
694
0
        return ts;
695
696
    // Sort by start timestamps.
697
0
    if (ev[0]->Start > ev[1]->Start)
698
0
        MPSWAP(ASS_Event*, ev[0], ev[1]);
699
700
    // We want to fix partial overlaps only.
701
0
    if (END(ev[0]) >= END(ev[1]))
702
0
        return ts;
703
704
0
    if (ev[0]->Duration < keep || ev[1]->Duration < keep)
705
0
        return ts;
706
707
    // Gap between the events -> move ts to show the end of the first event.
708
0
    if (ts >= END(ev[0]) && ts < ev[1]->Start && END(ev[0]) < ev[1]->Start &&
709
0
        END(ev[0]) + threshold >= ev[1]->Start)
710
0
        return END(ev[0]) - 1;
711
712
    // Overlap -> move ts to the (exclusive) end of the first event.
713
    // Relies on the fact that the ASS_Renderer has no overlap registered, even
714
    // if there is one. This happens to work because we never render the
715
    // overlapped state, and libass never resolves a collision.
716
0
    if (ts >= ev[1]->Start && ts <= END(ev[0]) && END(ev[0]) > ev[1]->Start &&
717
0
        END(ev[0]) <= ev[1]->Start + threshold)
718
0
        return END(ev[0]);
719
720
0
    return ts;
721
0
}
722
723
#undef END
724
725
static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
726
                                       int format, double pts)
727
0
{
728
0
    struct sd_ass_priv *ctx = sd->priv;
729
0
    struct mp_subtitle_opts *opts = sd->opts;
730
0
    struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
731
0
    bool no_ass = !opts->ass_enabled ||
732
0
        shared_opts->ass_style_override[sd->order] == ASS_STYLE_OVERRIDE_STRIP;
733
0
    bool converted = (ctx->is_converted && !lavc_conv_is_styled(ctx->converter)) || no_ass;
734
0
    ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track;
735
0
    ASS_Renderer *renderer = ctx->ass_renderer;
736
0
    struct sub_bitmaps *res = &(struct sub_bitmaps){0};
737
738
    // Always update the osd_res
739
0
    struct mp_osd_res old_osd = ctx->osd;
740
0
    ctx->osd = dim;
741
742
0
    if (pts == MP_NOPTS_VALUE || !renderer)
743
0
        goto done;
744
745
    // Currently no supported text sub formats support a distinction between forced
746
    // and unforced lines, so we just assume everything's unforced and discard everything.
747
    // If we ever see a format that makes this distinction, we can add support here.
748
0
    if (opts->sub_forced_events_only)
749
0
        goto done;
750
751
0
    double scale = dim.display_par;
752
0
    if (!converted && (!shared_opts->ass_style_override[sd->order] ||
753
0
                       opts->ass_use_video_data >= 1))
754
0
    {
755
        // Let's factor in video PAR for vsfilter compatibility:
756
0
        double par = opts->ass_video_aspect > 0 ?
757
0
                opts->ass_video_aspect :
758
0
                ctx->video_params.p_w / (double)ctx->video_params.p_h;
759
0
        if (isnormal(par))
760
0
            scale *= par;
761
0
    }
762
0
    if (!ctx->ass_configured || !osd_res_equals(old_osd, ctx->osd)) {
763
0
        configure_ass(sd, &dim, converted, track);
764
0
        ctx->ass_configured = true;
765
0
    }
766
0
    ass_set_pixel_aspect(renderer, scale);
767
0
    if (!converted && (!shared_opts->ass_style_override[sd->order] ||
768
0
                       opts->ass_use_video_data >= 2))
769
0
    {
770
0
        ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h);
771
0
    } else {
772
0
        ass_set_storage_size(renderer, 0, 0);
773
0
    }
774
0
    long long ts = find_timestamp(sd, pts);
775
776
0
    if (no_ass)
777
0
        fill_plaintext(sd, pts);
778
779
0
    int changed;
780
0
    ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed);
781
0
    mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, !converted, format, res);
782
783
0
done:
784
    // mangle_colors() modifies the color field, so copy the thing _before_.
785
0
    res = sub_bitmaps_copy(&ctx->copy_cache, res);
786
787
0
    if (!converted && res)
788
0
        mangle_colors(sd, res);
789
790
0
    return res;
791
0
}
792
793
#define MAX_BUF_SIZE 1024 * 1024
794
#define MIN_EXPAND_SIZE 4096
795
796
static void append(bstr *b, char c)
797
188
{
798
188
    bstr_xappend(NULL, b, (bstr){&c, 1});
799
188
}
800
801
static void ass_to_plaintext(bstr *b, const char *in)
802
8
{
803
8
    const char *open_tag_pos = NULL;
804
8
    bool in_drawing = false;
805
413
    while (*in) {
806
405
        if (open_tag_pos) {
807
221
            if (in[0] == '}') {
808
3
                in += 1;
809
3
                open_tag_pos = NULL;
810
218
            } else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') {
811
0
                in += 2;
812
                // Skip text between \pN and \p0 tags. A \p without a number
813
                // is the same as \p0, and leading 0s are also allowed.
814
0
                in_drawing = false;
815
0
                while (in[0] >= '0' && in[0] <= '9') {
816
0
                    if (in[0] != '0')
817
0
                        in_drawing = true;
818
0
                    in += 1;
819
0
                }
820
218
            } else {
821
218
                in += 1;
822
218
            }
823
221
        } else {
824
184
            if (in[0] == '\\' && (in[1] == 'N' || in[1] == 'n')) {
825
0
                in += 2;
826
0
                append(b, '\n');
827
184
            } else if (in[0] == '\\' && in[1] == 'h') {
828
0
                in += 2;
829
0
                append(b, ' ');
830
184
            } else if (in[0] == '{') {
831
4
                open_tag_pos = in;
832
4
                in += 1;
833
180
            } else {
834
180
                if (!in_drawing)
835
180
                    append(b, in[0]);
836
180
                in += 1;
837
180
            }
838
184
        }
839
405
    }
840
    // A '{' without a closing '}' is always visible.
841
8
    if (open_tag_pos) {
842
1
        bstr_xappend(NULL, b, bstr0(open_tag_pos));
843
1
    }
844
8
}
845
846
// Empty string counts as whitespace.
847
static bool is_whitespace_only(bstr b)
848
8
{
849
8
    for (int n = 0; n < b.len; n++) {
850
8
        if (b.start[n] != ' ' && b.start[n] != '\t')
851
8
            return false;
852
8
    }
853
0
    return true;
854
8
}
855
856
static bstr get_text_buf(struct sd *sd, double pts, enum sd_text_type type)
857
137
{
858
137
    struct sd_ass_priv *ctx = sd->priv;
859
137
    ASS_Track *track = ctx->ass_track;
860
861
137
    if (pts == MP_NOPTS_VALUE)
862
105
        return (bstr){0};
863
32
    long long ipts = find_timestamp(sd, pts);
864
865
32
    bstr *b = &ctx->last_text;
866
867
32
    if (!b->start)
868
13
        b->start = talloc_size(ctx, 4096);
869
870
32
    b->len = 0;
871
872
70
    for (int i = 0; i < track->n_events; ++i) {
873
38
        ASS_Event *event = track->events + i;
874
38
        if (ipts >= event->Start && ipts < event->Start + event->Duration) {
875
8
            if (event->Text) {
876
8
                int start = b->len;
877
8
                if (type == SD_TEXT_TYPE_PLAIN) {
878
8
                    ass_to_plaintext(b, event->Text);
879
8
                } else if (type == SD_TEXT_TYPE_ASS_FULL) {
880
0
                    long long s = event->Start;
881
0
                    long long e = s + event->Duration;
882
883
0
                    ASS_Style *style = (event->Style < 0 || event->Style >= track->n_styles) ? NULL : &track->styles[event->Style];
884
885
0
                    int sh = (s / 60 / 60 / 1000);
886
0
                    int sm = (s / 60 / 1000) % 60;
887
0
                    int ss = (s / 1000) % 60;
888
0
                    int sc = (s / 10) % 100;
889
0
                    int eh = (e / 60 / 60 / 1000);
890
0
                    int em = (e / 60 / 1000) % 60;
891
0
                    int es = (e / 1000) % 60;
892
0
                    int ec = (e / 10) % 100;
893
894
0
                    bstr_xappend_asprintf(NULL, b, "Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s",
895
0
                        event->Layer,
896
0
                        sh, sm, ss, sc,
897
0
                        eh, em, es, ec,
898
0
                        (style && style->Name) ? style->Name : "", event->Name,
899
0
                        event->MarginL, event->MarginR, event->MarginV,
900
0
                        event->Effect, event->Text);
901
0
                } else {
902
0
                    bstr_xappend(NULL, b, bstr0(event->Text));
903
0
                }
904
8
                if (is_whitespace_only(bstr_cut(*b, start))) {
905
0
                    b->len = start;
906
8
                } else {
907
8
                    append(b, '\n');
908
8
                }
909
8
            }
910
8
        }
911
38
    }
912
913
32
    bstr_eatend(b, (bstr)bstr0_lit("\n"));
914
915
32
    return *b;
916
137
}
917
918
static char *get_text(struct sd *sd, double pts, enum sd_text_type type)
919
137
{
920
137
    return bstrto0(NULL, get_text_buf(sd, pts, type));
921
137
}
922
923
static struct sd_times get_times(struct sd *sd, double pts)
924
0
{
925
0
    struct sd_ass_priv *ctx = sd->priv;
926
0
    ASS_Track *track = ctx->ass_track;
927
0
    struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE };
928
929
0
    if (pts == MP_NOPTS_VALUE)
930
0
        return res;
931
932
0
    long long ipts = find_timestamp(sd, pts);
933
934
0
    for (int i = 0; i < track->n_events; ++i) {
935
0
        ASS_Event *event = track->events + i;
936
0
        if (ipts >= event->Start && ipts < event->Start + event->Duration) {
937
0
            double start = event->Start / 1000.0;
938
0
            double end = event->Duration == UNKNOWN_DURATION ?
939
0
                MP_NOPTS_VALUE : (event->Start + event->Duration) / 1000.0;
940
941
0
            if (res.start == MP_NOPTS_VALUE || res.start > start)
942
0
                res.start = start;
943
944
0
            if (res.end == MP_NOPTS_VALUE || res.end < end)
945
0
                res.end = end;
946
0
        }
947
0
    }
948
949
0
    return res;
950
0
}
951
952
static void fill_plaintext(struct sd *sd, double pts)
953
0
{
954
0
    struct sd_ass_priv *ctx = sd->priv;
955
0
    ASS_Track *track = ctx->shadow_track;
956
957
0
    ass_flush_events(track);
958
959
0
    bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN);
960
0
    if (!text.len)
961
0
        return;
962
963
0
    bstr dst = {0};
964
965
0
    while (text.len) {
966
0
        if (*text.start == '{') {
967
0
            bstr_xappend(NULL, &dst, bstr0("\\{"));
968
0
            text = bstr_cut(text, 1);
969
0
        } else if (*text.start == '\\') {
970
0
            bstr_xappend(NULL, &dst, bstr0("\\"));
971
            // Break ASS escapes with U+2060 WORD JOINER
972
0
            mp_append_utf8_bstr(NULL, &dst, 0x2060);
973
0
            text = bstr_cut(text, 1);
974
0
        }
975
976
0
        int i = bstrcspn(text, "{\\");
977
0
        bstr_xappend(NULL, &dst, (bstr){text.start, i});
978
0
        text = bstr_cut(text, i);
979
0
    }
980
981
0
    if (!dst.start)
982
0
        return;
983
984
0
    int n = ass_alloc_event(track);
985
0
    ASS_Event *event = track->events + n;
986
0
    event->Start = 0;
987
0
    event->Duration = INT_MAX;
988
0
    event->Style = track->default_style;
989
0
    event->Text = strdup(dst.start);
990
991
0
    talloc_free(dst.start);
992
0
}
993
994
static void reset(struct sd *sd)
995
1.98k
{
996
1.98k
    struct sd_ass_priv *ctx = sd->priv;
997
1.98k
    if (sd->opts->sub_clear_on_seek || ctx->clear_once) {
998
0
        ass_flush_events(ctx->ass_track);
999
0
        ctx->num_seen_packets = 0;
1000
0
        sd->preload_ok = false;
1001
0
        ctx->clear_once = false;
1002
0
    }
1003
1.98k
    if (ctx->converter)
1004
1.25k
        lavc_conv_reset(ctx->converter);
1005
1.98k
}
1006
1007
static void uninit(struct sd *sd)
1008
994
{
1009
994
    struct sd_ass_priv *ctx = sd->priv;
1010
1011
994
    filters_destroy(sd);
1012
994
    if (ctx->converter)
1013
629
        lavc_conv_uninit(ctx->converter);
1014
994
    assobjects_destroy(sd);
1015
994
    talloc_free(ctx->copy_cache);
1016
994
}
1017
1018
static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
1019
39.1k
{
1020
39.1k
    struct sd_ass_priv *ctx = sd->priv;
1021
39.1k
    switch (cmd) {
1022
0
    case SD_CTRL_SUB_STEP: {
1023
0
        double *a = arg;
1024
0
        long long ts = llrint(a[0] * 1000.0);
1025
0
        long long res = ass_step_sub(ctx->ass_track, ts, a[1]);
1026
0
        if (!res)
1027
0
            return false;
1028
        // Try to account for overlapping durations
1029
0
        a[0] += res / 1000.0 + SUB_SEEK_OFFSET;
1030
0
        return true;
1031
0
    }
1032
20.6k
    case SD_CTRL_SET_ANIMATED_CHECK:
1033
20.6k
        ctx->check_animated = *(bool *)arg;
1034
20.6k
        return CONTROL_OK;
1035
18.5k
    case SD_CTRL_SET_VIDEO_PARAMS:
1036
18.5k
        ctx->video_params = *(struct mp_image_params *)arg;
1037
18.5k
        return CONTROL_OK;
1038
0
    case SD_CTRL_UPDATE_OPTS: {
1039
0
        uint64_t flags = *(uint64_t *)arg;
1040
0
        if (flags & UPDATE_SUB_FILT) {
1041
0
            filters_destroy(sd);
1042
0
            filters_init(sd);
1043
0
            ctx->clear_once = true; // allow reloading on seeks
1044
0
            reset(sd);
1045
0
        }
1046
0
        if (flags & UPDATE_SUB_HARD) {
1047
            // ass_track will be recreated, so clear duplicate cache
1048
0
            ctx->clear_once = true;
1049
0
            reset(sd);
1050
0
            assobjects_destroy(sd);
1051
0
            assobjects_init(sd);
1052
0
        }
1053
0
        ctx->ass_configured = false; // ass always needs to be reconfigured
1054
0
        return CONTROL_OK;
1055
0
    }
1056
0
    default:
1057
0
        return CONTROL_UNKNOWN;
1058
39.1k
    }
1059
39.1k
}
1060
1061
const struct sd_functions sd_ass = {
1062
    .name = "ass",
1063
    .accept_packets_in_advance = true,
1064
    .init = init,
1065
    .decode = decode,
1066
    .get_bitmaps = get_bitmaps,
1067
    .get_text = get_text,
1068
    .get_times = get_times,
1069
    .control = control,
1070
    .reset = reset,
1071
    .select = enable_output,
1072
    .uninit = uninit,
1073
};
1074
1075
// Disgusting hack for (xy-)vsfilter color compatibility.
1076
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
1077
0
{
1078
0
    struct mp_subtitle_opts *opts = sd->opts;
1079
0
    struct sd_ass_priv *ctx = sd->priv;
1080
0
    enum pl_color_system csp = 0;
1081
0
    enum pl_color_levels levels = 0;
1082
0
    if (opts->ass_vsfilter_color_compat == 0) // "no"
1083
0
        return;
1084
0
    bool force_601 = opts->ass_vsfilter_color_compat == 3;
1085
0
    ASS_Track *track = ctx->ass_track;
1086
0
    static const int ass_csp[] = {
1087
0
        [YCBCR_BT601_TV]        = PL_COLOR_SYSTEM_BT_601,
1088
0
        [YCBCR_BT601_PC]        = PL_COLOR_SYSTEM_BT_601,
1089
0
        [YCBCR_BT709_TV]        = PL_COLOR_SYSTEM_BT_709,
1090
0
        [YCBCR_BT709_PC]        = PL_COLOR_SYSTEM_BT_709,
1091
0
        [YCBCR_SMPTE240M_TV]    = PL_COLOR_SYSTEM_SMPTE_240M,
1092
0
        [YCBCR_SMPTE240M_PC]    = PL_COLOR_SYSTEM_SMPTE_240M,
1093
0
    };
1094
0
    static const int ass_levels[] = {
1095
0
        [YCBCR_BT601_TV]        = PL_COLOR_LEVELS_LIMITED,
1096
0
        [YCBCR_BT601_PC]        = PL_COLOR_LEVELS_FULL,
1097
0
        [YCBCR_BT709_TV]        = PL_COLOR_LEVELS_LIMITED,
1098
0
        [YCBCR_BT709_PC]        = PL_COLOR_LEVELS_FULL,
1099
0
        [YCBCR_SMPTE240M_TV]    = PL_COLOR_LEVELS_LIMITED,
1100
0
        [YCBCR_SMPTE240M_PC]    = PL_COLOR_LEVELS_FULL,
1101
0
    };
1102
0
    int trackcsp = track->YCbCrMatrix;
1103
0
    if (force_601)
1104
0
        trackcsp = YCBCR_BT601_TV;
1105
    // NONE is a bit random, but the intention is: don't modify colors.
1106
0
    if (trackcsp == YCBCR_NONE)
1107
0
        return;
1108
0
    if (trackcsp < MP_ARRAY_SIZE(ass_csp))
1109
0
        csp = ass_csp[trackcsp];
1110
0
    if (trackcsp <  MP_ARRAY_SIZE(ass_levels))
1111
0
        levels = ass_levels[trackcsp];
1112
0
    if (trackcsp == YCBCR_DEFAULT) {
1113
0
        csp = PL_COLOR_SYSTEM_BT_601;
1114
0
        levels = PL_COLOR_LEVELS_LIMITED;
1115
0
    }
1116
    // Unknown colorspace (either YCBCR_UNKNOWN, or a valid value unknown to us)
1117
0
    if (!csp || !levels)
1118
0
        return;
1119
1120
0
    struct mp_image_params params = ctx->video_params;
1121
1122
0
    if (force_601) {
1123
0
        params.repr = (struct pl_color_repr){
1124
0
            .sys = PL_COLOR_SYSTEM_BT_709,
1125
0
            .levels = PL_COLOR_LEVELS_LIMITED,
1126
0
        };
1127
0
    }
1128
1129
0
    if ((csp == params.repr.sys && levels == params.repr.levels) ||
1130
0
            params.repr.sys == PL_COLOR_SYSTEM_RGB) // Even VSFilter doesn't mangle on RGB video
1131
0
        return;
1132
1133
0
    bool basic_conv = params.repr.sys == PL_COLOR_SYSTEM_BT_709 &&
1134
0
                      params.repr.levels == PL_COLOR_LEVELS_LIMITED &&
1135
0
                      csp == PL_COLOR_SYSTEM_BT_601 &&
1136
0
                      levels == PL_COLOR_LEVELS_LIMITED;
1137
1138
    // With "basic", only do as much as needed for basic compatibility.
1139
0
    if (opts->ass_vsfilter_color_compat == 1 && !basic_conv)
1140
0
        return;
1141
1142
0
    if (params.repr.sys != ctx->last_params.repr.sys ||
1143
0
        params.repr.levels != ctx->last_params.repr.levels)
1144
0
    {
1145
0
        int msgl = basic_conv ? MSGL_V : MSGL_WARN;
1146
0
        ctx->last_params = params;
1147
0
        MP_MSG(sd, msgl, "mangling colors like vsfilter: "
1148
0
               "RGB -> %s %s -> %s %s -> RGB\n",
1149
0
               m_opt_choice_str(pl_csp_names, csp),
1150
0
               m_opt_choice_str(pl_csp_levels_names, levels),
1151
0
               m_opt_choice_str(pl_csp_names, params.repr.sys),
1152
0
               m_opt_choice_str(pl_csp_names, params.repr.levels));
1153
0
    }
1154
1155
    // Conversion that VSFilter would use
1156
0
    struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS;
1157
0
    vs_params.repr.sys = csp;
1158
0
    vs_params.repr.levels = levels;
1159
0
    struct pl_transform3x3 vs_yuv2rgb;
1160
0
    mp_get_csp_matrix(&vs_params, &vs_yuv2rgb);
1161
0
    pl_transform3x3_invert(&vs_yuv2rgb);
1162
1163
    // Proper conversion to RGB
1164
0
    struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS;
1165
0
    rgb_params.repr = params.repr;
1166
0
    rgb_params.color = params.color;
1167
0
    struct pl_transform3x3 vs2rgb;
1168
0
    mp_get_csp_matrix(&rgb_params, &vs2rgb);
1169
1170
0
    for (int n = 0; n < parts->num_parts; n++) {
1171
0
        struct sub_bitmap *sb = &parts->parts[n];
1172
0
        uint32_t color = sb->libass.color;
1173
0
        int r = (color >> 24u) & 0xff;
1174
0
        int g = (color >> 16u) & 0xff;
1175
0
        int b = (color >>  8u) & 0xff;
1176
0
        int a = 0xff - (color & 0xff);
1177
0
        int rgb[3] = {r, g, b}, yuv[3];
1178
0
        mp_map_fixp_color(&vs_yuv2rgb, 8, rgb, 8, yuv);
1179
0
        mp_map_fixp_color(&vs2rgb, 8, yuv, 8, rgb);
1180
0
        sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a);
1181
0
    }
1182
0
}
1183
1184
int sd_ass_fmt_offset(const char *evt_fmt)
1185
0
{
1186
    // "Text" is always last (as it's arbitrary content in buf), e.g. format:
1187
    // "Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text"
1188
0
    int n = 0;
1189
0
    while (evt_fmt && (evt_fmt = strchr(evt_fmt, ',')))
1190
0
         evt_fmt++, n++;
1191
0
    return n-1;  // buffer is without the format's Start/End, with ReadOrder
1192
0
}
1193
1194
bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset)
1195
0
{
1196
    // e.g. pkt->buffer ("4" is ReadOrder): "4,0,Default,,0,0,0,,fifth line"
1197
0
    bstr txt = {(char *)pkt->buffer, pkt->len}, t0 = txt;
1198
0
    while (offset-- > 0) {
1199
0
        int n = bstrchr(txt, ',');
1200
0
        if (n < 0) {  // shouldn't happen
1201
0
            MP_WARN(ft, "Malformed event '%.*s'\n", BSTR_P(t0));
1202
0
            return (bstr){NULL, 0};
1203
0
        }
1204
0
        txt = bstr_cut(txt, n+1);
1205
0
    }
1206
0
    return txt;
1207
0
}
1208
1209
bstr sd_ass_to_plaintext(char **out, const char *in)
1210
0
{
1211
0
    bstr b = {*out};
1212
0
    ass_to_plaintext(&b, in);
1213
0
    *out = b.start;
1214
0
    return b;
1215
0
}