summaryrefslogtreecommitdiffstats
path: root/sub/sd_ass.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:13:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 01:13:14 +0000
commit60e8a3d404f0640fa5a3f834eae54b4f1fb9127d (patch)
tree1da89a218d0ecf010c67a87cb2f625c4cb18e7d7 /sub/sd_ass.c
parentAdding upstream version 0.37.0. (diff)
downloadmpv-60e8a3d404f0640fa5a3f834eae54b4f1fb9127d.tar.xz
mpv-60e8a3d404f0640fa5a3f834eae54b4f1fb9127d.zip
Adding upstream version 0.38.0.upstream/0.38.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sub/sd_ass.c')
-rw-r--r--sub/sd_ass.c229
1 files changed, 149 insertions, 80 deletions
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index 6742f6f..6fa4d1b 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -49,18 +49,24 @@ struct sd_ass_priv {
struct sd_filter **filters;
int num_filters;
bool clear_once;
- bool on_top;
struct mp_ass_packer *packer;
struct sub_bitmap_copy_cache *copy_cache;
char last_text[500];
struct mp_image_params video_params;
struct mp_image_params last_params;
struct mp_osd_res osd;
- int64_t *seen_packets;
+ struct seen_packet *seen_packets;
int num_seen_packets;
+ bool *packets_animated;
+ int num_packets_animated;
bool duration_unknown;
};
+struct seen_packet {
+ int64_t pos;
+ double pts;
+};
+
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts);
static void fill_plaintext(struct sd *sd, double pts);
@@ -78,9 +84,10 @@ static const struct sd_filter_functions *const filters[] = {
// Add default styles, if the track does not have any styles yet.
// Apply style overrides if the user provides any.
-static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts *opts)
+static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts *opts,
+ struct mp_subtitle_shared_opts *shared_opts, int order)
{
- if (opts->ass_styles_file && opts->ass_style_override)
+ if (opts->ass_styles_file && shared_opts->ass_style_override[order])
ass_read_styles(track, opts->ass_styles_file, NULL);
if (track->n_styles == 0) {
@@ -96,7 +103,7 @@ static void mp_ass_add_default_styles(ASS_Track *track, struct mp_subtitle_opts
mp_ass_set_style(style, track->PlayResY, opts->sub_style);
}
- if (opts->ass_style_override)
+ if (shared_opts->ass_style_override[order])
ass_process_force_style(track);
}
@@ -177,7 +184,7 @@ static void filters_init(struct sd *sd)
.opts = mp_get_config_group(ft, sd->global, &mp_sub_filter_opts),
.driver = filters[n],
.codec = "ass",
- .event_format = ctx->ass_track->event_format,
+ .event_format = talloc_strdup(ft, ctx->ass_track->event_format),
};
if (ft->driver->init(ft)) {
MP_TARRAY_APPEND(ctx, ctx->filters, ctx->num_filters, ft);
@@ -207,13 +214,14 @@ static void assobjects_init(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
struct mp_subtitle_opts *opts = sd->opts;
+ struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
ctx->ass_library = mp_ass_init(sd->global, sd->opts->sub_style, sd->log);
ass_set_extract_fonts(ctx->ass_library, opts->use_embedded_fonts);
add_subtitle_fonts(sd);
- if (opts->ass_style_override)
+ if (shared_opts->ass_style_override[sd->order])
ass_set_style_overrides(ctx->ass_library, opts->ass_style_override_list);
ctx->ass_track = ass_new_track(ctx->ass_library);
@@ -222,7 +230,7 @@ static void assobjects_init(struct sd *sd)
ctx->shadow_track = ass_new_track(ctx->ass_library);
ctx->shadow_track->PlayResX = MP_ASS_FONT_PLAYRESX;
ctx->shadow_track->PlayResY = MP_ASS_FONT_PLAYRESY;
- mp_ass_add_default_styles(ctx->shadow_track, opts);
+ mp_ass_add_default_styles(ctx->shadow_track, opts, shared_opts, sd->order);
char *extradata = sd->codec->extradata;
int extradata_size = sd->codec->extradata_size;
@@ -233,7 +241,7 @@ static void assobjects_init(struct sd *sd)
if (extradata)
ass_process_codec_private(ctx->ass_track, extradata, extradata_size);
- mp_ass_add_default_styles(ctx->ass_track, opts);
+ mp_ass_add_default_styles(ctx->ass_track, opts, shared_opts, sd->order);
#if LIBASS_VERSION >= 0x01302000
ass_set_check_readorder(ctx->ass_track, sd->opts->sub_clear_on_seek ? 0 : 1);
@@ -279,11 +287,55 @@ static int init(struct sd *sd)
return 0;
}
+// Check if subtitle has events that would cause it to be animated inside {}
+static bool is_animated(char *s)
+{
+ bool in_tag = false;
+ bool valid_event = false;
+ bool valid_tag = false;
+ while (*s) {
+ if (!in_tag && s[0] == '{')
+ in_tag = true;
+ if (s[0] == '\\') {
+ s++;
+ if (!s[0])
+ break;
+ if (s[0] == 'k' || s[0] == 'K' || s[0] == 't') {
+ valid_event = true;
+ continue;
+ // just bruteforce the multi-letter ones
+ } else if (s[0] == 'f') {
+ if (!strncmp(s, "fad", 3)) {
+ valid_event = true;
+ continue;
+ }
+ } else if (s[0] == 'm') {
+ if (!strncmp(s, "move", 4)) {
+ valid_event = true;
+ continue;
+ }
+ }
+ }
+ if (in_tag && valid_event && s[0] == '}') {
+ valid_tag = true;
+ break;
+ } else if (s[0] == '}') {
+ in_tag = false;
+ valid_event = false;
+ valid_tag = false;
+ }
+ s++;
+ }
+ return valid_tag;
+}
+
// Note: pkt is not necessarily a fully valid refcounted packet.
static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
{
struct sd_ass_priv *ctx = sd->priv;
struct demux_packet *orig_pkt = pkt;
+ ASS_Track *track = ctx->ass_track;
+ int old_n_events = track->n_events;
for (int n = 0; n < ctx->num_filters; n++) {
struct sd_filter *ft = ctx->filters[n];
@@ -299,30 +351,51 @@ static void filter_and_add(struct sd *sd, struct demux_packet *pkt)
llrint(pkt->pts * 1000),
llrint(pkt->duration * 1000));
+ // This bookkeeping is only ever needed for ASS subs
+ if (!ctx->is_converted) {
+ if (!pkt->seen) {
+ for (int n = track->n_events - 1; n >= 0; n--) {
+ if (n + 1 == old_n_events || pkt->animated)
+ break;
+ ASS_Event *event = &track->events[n];
+ pkt->animated = (event->Effect && event->Effect[0]) ||
+ is_animated(event->Text);
+ }
+ MP_TARRAY_APPEND(ctx, ctx->packets_animated, ctx->num_packets_animated, pkt->animated);
+ } else {
+ pkt->animated = ctx->packets_animated[pkt->seen_pos];
+ }
+ }
+
if (pkt != orig_pkt)
talloc_free(pkt);
}
-// Test if the packet with the given file position (used as unique ID) was
-// already consumed. Return false if the packet is new (and add it to the
-// internal list), and return true if it was already seen.
-static bool check_packet_seen(struct sd *sd, int64_t pos)
+// Test if the packet with the given file position and pts was already consumed.
+// Return false if the packet is new (and add it to the internal list), and
+// return true if it was already seen.
+static bool check_packet_seen(struct sd *sd, struct demux_packet *packet)
{
struct sd_ass_priv *priv = sd->priv;
int a = 0;
int b = priv->num_seen_packets;
while (a < b) {
int mid = a + (b - a) / 2;
- int64_t val = priv->seen_packets[mid];
- if (pos == val)
+ struct seen_packet *seen_packet = &priv->seen_packets[mid];
+ if (packet->pos == seen_packet->pos && packet->pts == seen_packet->pts) {
+ packet->seen_pos = mid;
return true;
- if (pos > val) {
+ }
+ if (packet->pos > seen_packet->pos ||
+ (packet->pos == seen_packet->pos && packet->pts > seen_packet->pts)) {
a = mid + 1;
} else {
b = mid;
}
}
- MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a, pos);
+ packet->seen_pos = a;
+ MP_TARRAY_INSERT_AT(priv, priv->seen_packets, priv->num_seen_packets, a,
+ (struct seen_packet){packet->pos, packet->pts});
return false;
}
@@ -332,9 +405,12 @@ static void decode(struct sd *sd, struct demux_packet *packet)
{
struct sd_ass_priv *ctx = sd->priv;
ASS_Track *track = ctx->ass_track;
+
+ packet->sub_duration = packet->duration;
+
if (ctx->converter) {
if (!sd->opts->sub_clear_on_seek && packet->pos >= 0 &&
- check_packet_seen(sd, packet->pos))
+ check_packet_seen(sd, packet))
return;
double sub_pts = 0;
@@ -373,7 +449,9 @@ static void decode(struct sd *sd, struct demux_packet *packet)
}
} else {
// Note that for this packet format, libass has an internal mechanism
- // for discarding duplicate (already seen) packets.
+ // for discarding duplicate (already seen) packets but we check this
+ // anyways for our purposes for ASS subtitles.
+ packet->seen = check_packet_seen(sd, packet);
filter_and_add(sd, packet);
}
}
@@ -382,6 +460,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
bool converted, ASS_Track *track)
{
struct mp_subtitle_opts *opts = sd->opts;
+ struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
struct sd_ass_priv *ctx = sd->priv;
ASS_Renderer *priv = ctx->ass_renderer;
@@ -397,7 +476,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
bool set_scale_by_window = true;
bool total_override = false;
// With forced overrides, apply the --sub-* specific options
- if (converted || opts->ass_style_override == 3) { // 'force'
+ if (converted || shared_opts->ass_style_override[sd->order] == 3) { // 'force'
set_scale_with_window = opts->sub_scale_with_window;
set_use_margins = opts->sub_use_margins;
set_scale_by_window = opts->sub_scale_by_window;
@@ -406,8 +485,8 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
set_scale_with_window = opts->ass_scale_with_window;
set_use_margins = opts->ass_use_margins;
}
- if (converted || opts->ass_style_override) {
- set_sub_pos = 100.0f - opts->sub_pos;
+ if (converted || shared_opts->ass_style_override[sd->order]) {
+ set_sub_pos = 100.0f - shared_opts->sub_pos[sd->order];
set_line_spacing = opts->ass_line_spacing;
set_hinting = opts->ass_hinting;
set_font_scale = opts->sub_scale;
@@ -427,12 +506,12 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
int set_force_flags = 0;
if (total_override)
set_force_flags |= ASS_OVERRIDE_BIT_STYLE | ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
- if (opts->ass_style_override == 4) // 'scale'
+ if (shared_opts->ass_style_override[sd->order] == 4) // 'scale'
set_force_flags |= ASS_OVERRIDE_BIT_SELECTIVE_FONT_SCALE;
if (converted)
set_force_flags |= ASS_OVERRIDE_BIT_ALIGNMENT;
#ifdef ASS_JUSTIFY_AUTO
- if ((converted || opts->ass_style_override) && opts->ass_justify)
+ if ((converted || shared_opts->ass_style_override[sd->order]) && opts->ass_justify)
set_force_flags |= ASS_OVERRIDE_BIT_JUSTIFY;
#endif
ass_set_selective_style_override_enabled(priv, set_force_flags);
@@ -499,7 +578,7 @@ static long long find_timestamp(struct sd *sd, double pts)
long long ts = llrint(pts * 1000);
- if (!sd->opts->sub_fix_timing || sd->opts->ass_style_override == 0)
+ if (!sd->opts->sub_fix_timing || sd->shared_opts->ass_style_override[sd->order] == 0)
return ts;
// Try to fix small gaps and overlaps.
@@ -561,8 +640,8 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
{
struct sd_ass_priv *ctx = sd->priv;
struct mp_subtitle_opts *opts = sd->opts;
- bool no_ass = !opts->ass_enabled || ctx->on_top ||
- opts->ass_style_override == 5;
+ struct mp_subtitle_shared_opts *shared_opts = sd->shared_opts;
+ bool no_ass = !opts->ass_enabled || shared_opts->ass_style_override[sd->order] == 5;
bool converted = ctx->is_converted || no_ass;
ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track;
ASS_Renderer *renderer = ctx->ass_renderer;
@@ -582,7 +661,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
goto done;
double scale = dim.display_par;
- if (!converted && (!opts->ass_style_override ||
+ if (!converted && (!shared_opts->ass_style_override[sd->order] ||
opts->ass_vsfilter_aspect_compat))
{
// Let's use the original video PAR for vsfilter compatibility:
@@ -595,7 +674,7 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
ctx->ass_configured = true;
}
ass_set_pixel_aspect(renderer, scale);
- if (!converted && (!opts->ass_style_override ||
+ if (!converted && (!shared_opts->ass_style_override[sd->order] ||
opts->ass_vsfilter_blur_compat))
{
ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h);
@@ -603,18 +682,13 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim,
ass_set_storage_size(renderer, 0, 0);
}
long long ts = find_timestamp(sd, pts);
- if (ctx->duration_unknown && pts != MP_NOPTS_VALUE) {
- mp_ass_flush_old_events(track, ts);
- ctx->num_seen_packets = 0;
- sd->preload_ok = false;
- }
if (no_ass)
fill_plaintext(sd, pts);
int changed;
ASS_Image *imgs = ass_render_frame(renderer, track, ts, &changed);
- mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, format, res);
+ mp_ass_packer_pack(ctx->packer, &imgs, 1, changed, !converted, format, res);
done:
// mangle_colors() modifies the color field, so copy the thing _before_.
@@ -650,7 +724,7 @@ static void ass_to_plaintext(struct buf *b, const char *in)
if (in[0] == '}') {
in += 1;
in_tag = false;
- } else if (in[0] == '\\' && in[1] == 'p') {
+ } else if (in[0] == '\\' && in[1] == 'p' && in[2] != 'o') {
in += 2;
// Skip text between \pN and \p0 tags. A \p without a number
// is the same as \p0, and leading 0s are also allowed.
@@ -785,9 +859,6 @@ static void fill_plaintext(struct sd *sd, double pts)
bstr dst = {0};
- if (ctx->on_top)
- bstr_xappend(NULL, &dst, bstr0("{\\a6}"));
-
while (*text) {
if (*text == '{')
bstr_xappend(NULL, &dst, bstr0("\\"));
@@ -814,7 +885,7 @@ static void fill_plaintext(struct sd *sd, double pts)
static void reset(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
- if (sd->opts->sub_clear_on_seek || ctx->duration_unknown || ctx->clear_once) {
+ if (sd->opts->sub_clear_on_seek || ctx->clear_once) {
ass_flush_events(ctx->ass_track);
ctx->num_seen_packets = 0;
sd->preload_ok = false;
@@ -852,9 +923,6 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
case SD_CTRL_SET_VIDEO_PARAMS:
ctx->video_params = *(struct mp_image_params *)arg;
return CONTROL_OK;
- case SD_CTRL_SET_TOP:
- ctx->on_top = *(bool *)arg;
- return CONTROL_OK;
case SD_CTRL_UPDATE_OPTS: {
int flags = (uintptr_t)arg;
if (flags & UPDATE_SUB_FILT) {
@@ -897,27 +965,27 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
{
struct mp_subtitle_opts *opts = sd->opts;
struct sd_ass_priv *ctx = sd->priv;
- enum mp_csp csp = 0;
- enum mp_csp_levels levels = 0;
+ enum pl_color_system csp = 0;
+ enum pl_color_levels levels = 0;
if (opts->ass_vsfilter_color_compat == 0) // "no"
return;
bool force_601 = opts->ass_vsfilter_color_compat == 3;
ASS_Track *track = ctx->ass_track;
static const int ass_csp[] = {
- [YCBCR_BT601_TV] = MP_CSP_BT_601,
- [YCBCR_BT601_PC] = MP_CSP_BT_601,
- [YCBCR_BT709_TV] = MP_CSP_BT_709,
- [YCBCR_BT709_PC] = MP_CSP_BT_709,
- [YCBCR_SMPTE240M_TV] = MP_CSP_SMPTE_240M,
- [YCBCR_SMPTE240M_PC] = MP_CSP_SMPTE_240M,
+ [YCBCR_BT601_TV] = PL_COLOR_SYSTEM_BT_601,
+ [YCBCR_BT601_PC] = PL_COLOR_SYSTEM_BT_601,
+ [YCBCR_BT709_TV] = PL_COLOR_SYSTEM_BT_709,
+ [YCBCR_BT709_PC] = PL_COLOR_SYSTEM_BT_709,
+ [YCBCR_SMPTE240M_TV] = PL_COLOR_SYSTEM_SMPTE_240M,
+ [YCBCR_SMPTE240M_PC] = PL_COLOR_SYSTEM_SMPTE_240M,
};
static const int ass_levels[] = {
- [YCBCR_BT601_TV] = MP_CSP_LEVELS_TV,
- [YCBCR_BT601_PC] = MP_CSP_LEVELS_PC,
- [YCBCR_BT709_TV] = MP_CSP_LEVELS_TV,
- [YCBCR_BT709_PC] = MP_CSP_LEVELS_PC,
- [YCBCR_SMPTE240M_TV] = MP_CSP_LEVELS_TV,
- [YCBCR_SMPTE240M_PC] = MP_CSP_LEVELS_PC,
+ [YCBCR_BT601_TV] = PL_COLOR_LEVELS_LIMITED,
+ [YCBCR_BT601_PC] = PL_COLOR_LEVELS_FULL,
+ [YCBCR_BT709_TV] = PL_COLOR_LEVELS_LIMITED,
+ [YCBCR_BT709_PC] = PL_COLOR_LEVELS_FULL,
+ [YCBCR_SMPTE240M_TV] = PL_COLOR_LEVELS_LIMITED,
+ [YCBCR_SMPTE240M_PC] = PL_COLOR_LEVELS_FULL,
};
int trackcsp = track->YCbCrMatrix;
if (force_601)
@@ -930,8 +998,8 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
if (trackcsp < sizeof(ass_levels) / sizeof(ass_levels[0]))
levels = ass_levels[trackcsp];
if (trackcsp == YCBCR_DEFAULT) {
- csp = MP_CSP_BT_601;
- levels = MP_CSP_LEVELS_TV;
+ csp = PL_COLOR_SYSTEM_BT_601;
+ levels = PL_COLOR_LEVELS_LIMITED;
}
// Unknown colorspace (either YCBCR_UNKNOWN, or a valid value unknown to us)
if (!csp || !levels)
@@ -940,50 +1008,51 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
struct mp_image_params params = ctx->video_params;
if (force_601) {
- params.color = (struct mp_colorspace){
- .space = MP_CSP_BT_709,
- .levels = MP_CSP_LEVELS_TV,
+ params.repr = (struct pl_color_repr){
+ .sys = PL_COLOR_SYSTEM_BT_709,
+ .levels = PL_COLOR_LEVELS_LIMITED,
};
}
- if ((csp == params.color.space && levels == params.color.levels) ||
- params.color.space == MP_CSP_RGB) // Even VSFilter doesn't mangle on RGB video
+ if ((csp == params.repr.sys && levels == params.repr.levels) ||
+ params.repr.sys == PL_COLOR_SYSTEM_RGB) // Even VSFilter doesn't mangle on RGB video
return;
- bool basic_conv = params.color.space == MP_CSP_BT_709 &&
- params.color.levels == MP_CSP_LEVELS_TV &&
- csp == MP_CSP_BT_601 &&
- levels == MP_CSP_LEVELS_TV;
+ bool basic_conv = params.repr.sys == PL_COLOR_SYSTEM_BT_709 &&
+ params.repr.levels == PL_COLOR_LEVELS_LIMITED &&
+ csp == PL_COLOR_SYSTEM_BT_601 &&
+ levels == PL_COLOR_LEVELS_LIMITED;
// With "basic", only do as much as needed for basic compatibility.
if (opts->ass_vsfilter_color_compat == 1 && !basic_conv)
return;
- if (params.color.space != ctx->last_params.color.space ||
- params.color.levels != ctx->last_params.color.levels)
+ if (params.repr.sys != ctx->last_params.repr.sys ||
+ params.repr.levels != ctx->last_params.repr.levels)
{
int msgl = basic_conv ? MSGL_V : MSGL_WARN;
ctx->last_params = params;
MP_MSG(sd, msgl, "mangling colors like vsfilter: "
"RGB -> %s %s -> %s %s -> RGB\n",
- m_opt_choice_str(mp_csp_names, csp),
- m_opt_choice_str(mp_csp_levels_names, levels),
- m_opt_choice_str(mp_csp_names, params.color.space),
- m_opt_choice_str(mp_csp_names, params.color.levels));
+ m_opt_choice_str(pl_csp_names, csp),
+ m_opt_choice_str(pl_csp_levels_names, levels),
+ m_opt_choice_str(pl_csp_names, params.repr.sys),
+ m_opt_choice_str(pl_csp_names, params.repr.levels));
}
// Conversion that VSFilter would use
struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS;
- vs_params.color.space = csp;
- vs_params.color.levels = levels;
- struct mp_cmat vs_yuv2rgb, vs_rgb2yuv;
+ vs_params.repr.sys = csp;
+ vs_params.repr.levels = levels;
+ struct pl_transform3x3 vs_yuv2rgb;
mp_get_csp_matrix(&vs_params, &vs_yuv2rgb);
- mp_invert_cmat(&vs_rgb2yuv, &vs_yuv2rgb);
+ pl_transform3x3_invert(&vs_yuv2rgb);
// Proper conversion to RGB
struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS;
+ rgb_params.repr = params.repr;
rgb_params.color = params.color;
- struct mp_cmat vs2rgb;
+ struct pl_transform3x3 vs2rgb;
mp_get_csp_matrix(&rgb_params, &vs2rgb);
for (int n = 0; n < parts->num_parts; n++) {
@@ -994,7 +1063,7 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
int b = (color >> 8u) & 0xff;
int a = 0xff - (color & 0xff);
int rgb[3] = {r, g, b}, yuv[3];
- mp_map_fixp_color(&vs_rgb2yuv, 8, rgb, 8, yuv);
+ mp_map_fixp_color(&vs_yuv2rgb, 8, rgb, 8, yuv);
mp_map_fixp_color(&vs2rgb, 8, yuv, 8, rgb);
sb->libass.color = MP_ASS_RGBA(rgb[0], rgb[1], rgb[2], a);
}