Coverage Report

Created: 2025-10-27 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/sub/ass_mp.c
Line
Count
Source
1
/*
2
 * Copyright (C) 2006 Evgeniy Stepanov <eugeni.stepanov@gmail.com>
3
 *
4
 * This file is part of mpv.
5
 *
6
 * mpv is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * mpv is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
18
 */
19
20
#include <inttypes.h>
21
#include <string.h>
22
#include <stdlib.h>
23
#include <stdarg.h>
24
#include <stdbool.h>
25
#include <assert.h>
26
#include <math.h>
27
28
#include <ass/ass.h>
29
#include <ass/ass_types.h>
30
31
#include "common/common.h"
32
#include "common/msg.h"
33
#include "options/path.h"
34
#include "ass_mp.h"
35
#include "img_convert.h"
36
#include "osd.h"
37
#include "stream/stream.h"
38
#include "options/options.h"
39
#include "video/out/bitmap_packer.h"
40
#include "video/mp_image.h"
41
42
// res_y should be track->PlayResY
43
// It determines scaling of font sizes and more.
44
void mp_ass_set_style(ASS_Style *style, double res_y,
45
                      const struct osd_style_opts *opts)
46
287
{
47
287
    if (!style)
48
0
        return;
49
50
287
    if (opts->font) {
51
287
        if (!style->FontName || strcmp(style->FontName, opts->font) != 0) {
52
45
            free(style->FontName);
53
45
            style->FontName = strdup(opts->font);
54
45
        }
55
287
    }
56
57
    // libass_font_size = FontSize * (window_height / res_y)
58
    // scale translates parameters from PlayResY=720 to res_y
59
287
    double scale = res_y / 720.0;
60
61
287
    style->FontSize = opts->font_size * scale;
62
287
    style->PrimaryColour = MP_ASS_COLOR(opts->color);
63
287
    style->SecondaryColour = style->PrimaryColour;
64
287
    style->OutlineColour = MP_ASS_COLOR(opts->outline_color);
65
287
    style->BackColour = MP_ASS_COLOR(opts->back_color);
66
287
    style->BorderStyle = opts->border_style;
67
287
    style->Outline = opts->outline_size * scale;
68
287
    style->Shadow = opts->shadow_offset * scale;
69
287
    style->Spacing = opts->spacing * scale;
70
287
    style->MarginL = opts->margin_x * scale;
71
287
    style->MarginR = style->MarginL;
72
287
    style->MarginV = opts->margin_y * scale;
73
287
    style->ScaleX = 1.;
74
287
    style->ScaleY = 1.;
75
287
    style->Alignment = 1 + (opts->align_x + 1) + (opts->align_y + 2) % 3 * 4;
76
287
#ifdef ASS_JUSTIFY_LEFT
77
287
    style->Justify = opts->justify;
78
287
#endif
79
287
    style->Blur = opts->blur;
80
287
    style->Bold = opts->bold;
81
287
    style->Italic = opts->italic;
82
287
}
83
84
void mp_ass_configure_fonts(ASS_Renderer *priv, struct osd_style_opts *opts,
85
                            struct mpv_global *global, struct mp_log *log)
86
1.03k
{
87
1.03k
    void *tmp = talloc_new(NULL);
88
1.03k
    char *default_font = mp_find_config_file(tmp, global, "subfont.ttf");
89
1.03k
    char *config       = mp_find_config_file(tmp, global, "fonts.conf");
90
91
1.03k
    if (default_font && !mp_path_exists(default_font))
92
0
        default_font = NULL;
93
94
1.03k
    int font_provider = ASS_FONTPROVIDER_AUTODETECT;
95
1.03k
    if (opts->font_provider == 1)
96
0
        font_provider = ASS_FONTPROVIDER_NONE;
97
1.03k
    if (opts->font_provider == 2)
98
0
        font_provider = ASS_FONTPROVIDER_FONTCONFIG;
99
100
1.03k
    mp_verbose(log, "Setting up fonts...\n");
101
1.03k
    ass_set_fonts(priv, default_font, opts->font, font_provider, config, 1);
102
1.03k
    mp_verbose(log, "Done.\n");
103
104
1.03k
    talloc_free(tmp);
105
1.03k
}
106
107
static const int map_ass_level[] = {
108
    MSGL_ERR,           // 0 "FATAL errors"
109
    MSGL_WARN,
110
    MSGL_INFO,
111
    MSGL_V,
112
    MSGL_V,
113
    MSGL_DEBUG,         // 5 application recommended level
114
    MSGL_TRACE,
115
    MSGL_TRACE,         // 7 "verbose DEBUG"
116
};
117
118
MP_PRINTF_ATTRIBUTE(2, 0)
119
static void message_callback(int level, const char *format, va_list va, void *ctx)
120
16.3k
{
121
16.3k
    struct mp_log *log = ctx;
122
16.3k
    if (!log)
123
0
        return;
124
16.3k
    level = map_ass_level[level];
125
16.3k
    mp_msg_va(log, level, format, va);
126
    // libass messages lack trailing \n
127
16.3k
    mp_msg(log, level, "\n");
128
16.3k
}
129
130
ASS_Library *mp_ass_init(struct mpv_global *global,
131
                         struct osd_style_opts *opts, struct mp_log *log)
132
1.03k
{
133
1.03k
    char *path = opts->fonts_dir && opts->fonts_dir[0] ?
134
0
                 mp_get_user_path(NULL, global, opts->fonts_dir) :
135
1.03k
                 mp_find_config_file(NULL, global, "fonts");
136
1.03k
    mp_dbg(log, "ASS library version: 0x%x (runtime 0x%x)\n",
137
1.03k
           (unsigned)LIBASS_VERSION, ass_library_version());
138
1.03k
    ASS_Library *priv = ass_library_init();
139
1.03k
    if (!priv)
140
0
        abort();
141
1.03k
    ass_set_message_cb(priv, message_callback, log);
142
1.03k
    if (path)
143
0
        ass_set_fonts_dir(priv, path);
144
1.03k
    talloc_free(path);
145
1.03k
    return priv;
146
1.03k
}
147
148
void mp_ass_flush_old_events(ASS_Track *track, long long ts)
149
0
{
150
0
    int n = 0;
151
0
    for (; n < track->n_events; n++) {
152
0
        if ((track->events[n].Start + track->events[n].Duration) >= ts)
153
0
            break;
154
0
        ass_free_event(track, n);
155
0
        track->n_events--;
156
0
    }
157
0
    for (int i = 0; n > 0 && i < track->n_events; i++) {
158
0
        track->events[i] = track->events[i+n];
159
0
    }
160
0
}
161
162
static void draw_ass_rgba(unsigned char *src, int src_w, int src_h,
163
                          int src_stride, unsigned char *dst, size_t dst_stride,
164
                          int dst_x, int dst_y, uint32_t color)
165
0
{
166
0
    const unsigned int r = (color >> 24) & 0xff;
167
0
    const unsigned int g = (color >> 16) & 0xff;
168
0
    const unsigned int b = (color >>  8) & 0xff;
169
0
    const unsigned int a = 0xff - (color & 0xff);
170
171
0
    dst += dst_y * dst_stride + dst_x * 4;
172
173
0
    for (int y = 0; y < src_h; y++, dst += dst_stride, src += src_stride) {
174
0
        uint32_t *dstrow = (uint32_t *) dst;
175
0
        for (int x = 0; x < src_w; x++) {
176
0
            const unsigned int v = src[x];
177
0
            int rr = (r * a * v);
178
0
            int gg = (g * a * v);
179
0
            int bb = (b * a * v);
180
0
            int aa =      a * v;
181
0
            uint32_t dstpix = dstrow[x];
182
0
            unsigned int dstb =  dstpix        & 0xFF;
183
0
            unsigned int dstg = (dstpix >>  8) & 0xFF;
184
0
            unsigned int dstr = (dstpix >> 16) & 0xFF;
185
0
            unsigned int dsta = (dstpix >> 24) & 0xFF;
186
0
            dstb = (bb       + dstb * (255 * 255 - aa)) / (255 * 255);
187
0
            dstg = (gg       + dstg * (255 * 255 - aa)) / (255 * 255);
188
0
            dstr = (rr       + dstr * (255 * 255 - aa)) / (255 * 255);
189
0
            dsta = (aa * 255 + dsta * (255 * 255 - aa)) / (255 * 255);
190
0
            dstrow[x] = dstb | (dstg << 8) | (dstr << 16) | (dsta << 24);
191
0
        }
192
0
    }
193
0
}
194
195
struct mp_ass_packer {
196
    struct sub_bitmap *cached_parts; // only for the array memory
197
    struct mp_image *cached_img;
198
    struct sub_bitmaps cached_subs;
199
    bool cached_subs_valid;
200
    struct sub_bitmap rgba_imgs[MP_SUB_BB_LIST_MAX];
201
    struct bitmap_packer *packer;
202
};
203
204
// Free with talloc_free().
205
struct mp_ass_packer *mp_ass_packer_alloc(void *ta_parent)
206
1.03k
{
207
1.03k
    struct mp_ass_packer *p = talloc_zero(ta_parent, struct mp_ass_packer);
208
1.03k
    p->packer = talloc_zero(p, struct bitmap_packer);
209
1.03k
    p->packer->padding = 1; // assume bilinear sampling
210
1.03k
    return p;
211
1.03k
}
212
213
static bool pack(struct mp_ass_packer *p, struct sub_bitmaps *res, int imgfmt)
214
76
{
215
76
    packer_set_size(p->packer, res->num_parts);
216
217
457
    for (int n = 0; n < res->num_parts; n++)
218
381
        p->packer->in[n] = (struct pos){res->parts[n].w, res->parts[n].h};
219
220
76
    if (p->packer->count == 0 || packer_pack(p->packer) < 0)
221
62
        return false;
222
223
14
    struct pos bb[2];
224
14
    packer_get_bb(p->packer, bb);
225
226
14
    res->packed_w = bb[1].x;
227
14
    res->packed_h = bb[1].y;
228
229
14
    if (!p->cached_img || p->cached_img->w < res->packed_w ||
230
3
                          p->cached_img->h < res->packed_h ||
231
3
                          p->cached_img->imgfmt != imgfmt)
232
11
    {
233
11
        talloc_free(p->cached_img);
234
11
        p->cached_img = mp_image_alloc(imgfmt, p->packer->w, p->packer->h);
235
11
        if (!p->cached_img) {
236
0
            packer_reset(p->packer);
237
0
            return false;
238
0
        }
239
11
        talloc_steal(p, p->cached_img);
240
11
    }
241
242
14
    if (!mp_image_make_writeable(p->cached_img)) {
243
0
        packer_reset(p->packer);
244
0
        return false;
245
0
    }
246
247
14
    res->packed = p->cached_img;
248
249
395
    for (int n = 0; n < res->num_parts; n++) {
250
381
        struct sub_bitmap *b = &res->parts[n];
251
381
        struct pos pos = p->packer->result[n];
252
253
381
        b->src_x = pos.x;
254
381
        b->src_y = pos.y;
255
381
    }
256
257
14
    return true;
258
14
}
259
260
static void fill_padding_1(uint8_t *base, int w, int h, int stride, int padding)
261
381
{
262
6.56k
    for (int row = 0; row < h; ++row) {
263
6.18k
        uint8_t *row_ptr = base + row * stride;
264
6.18k
        uint8_t left_pixel = row_ptr[0];
265
6.18k
        uint8_t right_pixel = row_ptr[w - 1];
266
267
12.3k
        for (int i = 1; i <= padding; ++i)
268
6.18k
            row_ptr[-i] = left_pixel;
269
270
12.3k
        for (int i = 0; i < padding; ++i)
271
6.18k
            row_ptr[w + i] = right_pixel;
272
6.18k
    }
273
274
381
    int row_bytes = (w + 2 * padding);
275
381
    uint8_t *top_row = base - padding;
276
762
    for (int i = 1; i <= padding; ++i)
277
381
        memcpy(base - i * stride - padding, top_row, row_bytes);
278
279
381
    uint8_t *last_row = base + (h - 1) * stride - padding;
280
762
    for (int i = 0; i < padding; ++i)
281
381
        memcpy(base + (h + i) * stride - padding, last_row, row_bytes);
282
381
}
283
284
static void fill_padding_4(uint8_t *base, int w, int h, int stride, int padding)
285
0
{
286
0
    for (int row = 0; row < h; ++row) {
287
0
        uint32_t *row_ptr = (uint32_t *)(base + row * stride);
288
0
        uint32_t left_pixel = row_ptr[0];
289
0
        uint32_t right_pixel = row_ptr[w - 1];
290
291
0
        for (int i = 1; i <= padding; ++i)
292
0
            row_ptr[-i] = left_pixel;
293
294
0
        for (int i = 0; i < padding; ++i)
295
0
            row_ptr[w + i] = right_pixel;
296
0
    }
297
298
0
    int row_bytes = (w + 2 * padding) * 4;
299
0
    uint8_t *top_row = base - padding * 4;
300
0
    for (int i = 1; i <= padding; ++i)
301
0
        memcpy(base - i * stride - padding * 4, top_row, row_bytes);
302
303
0
    uint8_t *last_row = base + (h - 1) * stride - padding * 4;
304
0
    for (int i = 0; i < padding; ++i)
305
0
        memcpy(base + (h + i) * stride - padding * 4, last_row, row_bytes);
306
0
}
307
308
static bool pack_libass(struct mp_ass_packer *p, struct sub_bitmaps *res)
309
76
{
310
76
    if (!pack(p, res, IMGFMT_Y8))
311
62
        return false;
312
313
14
    int padding = p->packer->padding;
314
14
    uint8_t *base = res->packed->planes[0];
315
14
    int stride = res->packed->stride[0];
316
317
395
    for (int n = 0; n < res->num_parts; n++) {
318
381
        struct sub_bitmap *b = &res->parts[n];
319
381
        void *pdata = base + b->src_y * stride + b->src_x;
320
381
        memcpy_pic(pdata, b->bitmap, b->w, b->h, stride, b->stride);
321
381
        fill_padding_1(pdata, b->w, b->h, stride, padding);
322
323
381
        b->bitmap = pdata;
324
381
        b->stride = stride;
325
381
    }
326
327
14
    return true;
328
76
}
329
330
static bool pack_rgba(struct mp_ass_packer *p, struct sub_bitmaps *res)
331
0
{
332
0
    struct mp_rect bb_list[MP_SUB_BB_LIST_MAX];
333
0
    int num_bb = mp_get_sub_bb_list(res, bb_list, MP_SUB_BB_LIST_MAX);
334
335
0
    struct sub_bitmaps imgs = {
336
0
        .change_id = res->change_id,
337
0
        .format = SUBBITMAP_BGRA,
338
0
        .parts = p->rgba_imgs,
339
0
        .num_parts = num_bb,
340
0
    };
341
342
0
    for (int n = 0; n < imgs.num_parts; n++) {
343
0
        imgs.parts[n].w = bb_list[n].x1 - bb_list[n].x0;
344
0
        imgs.parts[n].h = bb_list[n].y1 - bb_list[n].y0;
345
0
    }
346
347
0
    if (!pack(p, &imgs, IMGFMT_BGRA))
348
0
        return false;
349
350
0
    int padding = p->packer->padding;
351
0
    uint8_t *base = imgs.packed->planes[0];
352
0
    int stride = imgs.packed->stride[0];
353
354
0
    for (int n = 0; n < num_bb; n++) {
355
0
        struct mp_rect bb = bb_list[n];
356
0
        struct sub_bitmap *b = &imgs.parts[n];
357
358
0
        b->x = bb.x0;
359
0
        b->y = bb.y0;
360
0
        b->w = b->dw = mp_rect_w(bb);
361
0
        b->h = b->dh = mp_rect_h(bb);
362
0
        b->stride = stride;
363
0
        b->bitmap = base + b->stride * b->src_y + b->src_x * 4;
364
0
        memset_pic(b->bitmap, 0, b->w * 4, b->h, b->stride);
365
366
0
        for (int i = 0; i < res->num_parts; i++) {
367
0
            struct sub_bitmap *s = &res->parts[i];
368
369
            // Assume mp_get_sub_bb_list() never splits sub bitmaps
370
            // So we don't clip/adjust the size of the sub bitmap
371
0
            if (s->x >= b->x + b->w || s->x + s->w <= b->x ||
372
0
                s->y >= b->y + b->h || s->y + s->h <= b->y)
373
0
                continue;
374
375
0
            draw_ass_rgba(s->bitmap, s->w, s->h, s->stride,
376
0
                          b->bitmap, b->stride,
377
0
                          s->x - b->x, s->y - b->y,
378
0
                          s->libass.color);
379
0
        }
380
0
        fill_padding_4(b->bitmap, b->w, b->h, b->stride, padding);
381
0
    }
382
383
0
    *res = imgs;
384
0
    return true;
385
0
}
386
387
// Pack the contents of image_lists[0] to image_lists[num_image_lists-1] into
388
// a single image, and make *out point to it. *out is completely overwritten.
389
// If libass reported any change, image_lists_changed must be set (it then
390
// repacks all images). preferred_osd_format can be set to a desired
391
// sub_bitmap_format. Currently, only SUBBITMAP_LIBASS is supported.
392
void mp_ass_packer_pack(struct mp_ass_packer *p, ASS_Image **image_lists,
393
                        int num_image_lists, bool image_lists_changed, bool video_color_space,
394
                        int preferred_osd_format, struct sub_bitmaps *out)
395
84
{
396
84
    int format = preferred_osd_format == SUBBITMAP_BGRA ? SUBBITMAP_BGRA
397
84
                                                        : SUBBITMAP_LIBASS;
398
399
84
    if (p->cached_subs_valid && !image_lists_changed &&
400
8
        p->cached_subs.format == format)
401
8
    {
402
8
        *out = p->cached_subs;
403
8
        return;
404
8
    }
405
406
76
    *out = (struct sub_bitmaps){.change_id = 1};
407
76
    p->cached_subs_valid = false;
408
409
76
    struct sub_bitmaps res = {
410
76
        .change_id = image_lists_changed,
411
76
        .format = SUBBITMAP_LIBASS,
412
76
        .parts = p->cached_parts,
413
76
        .video_color_space = video_color_space,
414
76
    };
415
416
152
    for (int n = 0; n < num_image_lists; n++) {
417
457
        for (struct ass_image *img = image_lists[n]; img; img = img->next) {
418
381
            if (img->w == 0 || img->h == 0)
419
0
                continue;
420
381
            MP_TARRAY_GROW(p, p->cached_parts, res.num_parts);
421
381
            res.parts = p->cached_parts;
422
381
            struct sub_bitmap *b = &res.parts[res.num_parts];
423
381
            b->bitmap = img->bitmap;
424
381
            b->stride = img->stride;
425
381
            b->libass.color = img->color;
426
381
            b->dw = b->w = img->w;
427
381
            b->dh = b->h = img->h;
428
381
            b->x = img->dst_x;
429
381
            b->y = img->dst_y;
430
381
            res.num_parts++;
431
381
        }
432
76
    }
433
434
76
    bool r = false;
435
76
    if (format == SUBBITMAP_BGRA) {
436
0
        r = pack_rgba(p, &res);
437
76
    } else {
438
76
        r = pack_libass(p, &res);
439
76
    }
440
441
76
    if (!r)
442
62
        return;
443
444
14
    *out = res;
445
14
    p->cached_subs = res;
446
14
    p->cached_subs.change_id = 0;
447
14
    p->cached_subs_valid = true;
448
14
}
449
450
// Set *out_rc to [x0, y0, x1, y1] of the graphical bounding box in script
451
// coordinates.
452
// Set it to [inf, inf, -inf, -inf] if empty.
453
void mp_ass_get_bb(ASS_Image *image_list, ASS_Track *track,
454
                   struct mp_osd_res *res, double *out_rc)
455
0
{
456
0
    double rc[4] = {INFINITY, INFINITY, -INFINITY, -INFINITY};
457
458
0
    for (ASS_Image *img = image_list; img; img = img->next) {
459
0
        if (img->w == 0 || img->h == 0)
460
0
            continue;
461
0
        rc[0] = MPMIN(rc[0], img->dst_x);
462
0
        rc[1] = MPMIN(rc[1], img->dst_y);
463
0
        rc[2] = MPMAX(rc[2], img->dst_x + img->w);
464
0
        rc[3] = MPMAX(rc[3], img->dst_y + img->h);
465
0
    }
466
467
0
    double scale = track->PlayResY / (double)MPMAX(res->h, 1);
468
0
    if (scale > 0) {
469
0
        for (int i = 0; i < 4; i++)
470
0
            out_rc[i] = rc[i] * scale;
471
0
    }
472
0
}