summaryrefslogtreecommitdiffstats
path: root/sub/sd_ass.c
diff options
context:
space:
mode:
Diffstat (limited to 'sub/sd_ass.c')
-rw-r--r--sub/sd_ass.c222
1 files changed, 193 insertions, 29 deletions
diff --git a/sub/sd_ass.c b/sub/sd_ass.c
index baec35faa9..2948475e7a 100644
--- a/sub/sd_ass.c
+++ b/sub/sd_ass.c
@@ -29,6 +29,7 @@
#include "options/options.h"
#include "common/common.h"
#include "common/msg.h"
+#include "demux/stheader.h"
#include "video/csputils.h"
#include "video/mp_image.h"
#include "dec_sub.h"
@@ -37,16 +38,44 @@
struct sd_ass_priv {
struct ass_track *ass_track;
+ struct ass_track *shadow_track; // for --sub-ass=no rendering
bool is_converted;
+ bool on_top;
struct sub_bitmap *parts;
bool flush_on_seek;
int extend_event;
char last_text[500];
struct mp_image_params video_params;
struct mp_image_params last_params;
+ double sub_speed;
};
static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts);
+static void fill_plaintext(struct sd *sd, double pts);
+
+// 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 MPOpts *opts)
+{
+ if (opts->ass_styles_file && opts->ass_style_override)
+ ass_read_styles(track, opts->ass_styles_file, NULL);
+
+ if (track->n_styles == 0) {
+ if (!track->PlayResY) {
+ track->PlayResY = MP_ASS_FONT_PLAYRESY;
+ track->PlayResX = track->PlayResY * 4 / 3;
+ }
+ track->Kerning = true;
+ int sid = ass_alloc_style(track);
+ track->default_style = sid;
+ ASS_Style *style = track->styles + sid;
+ style->Name = strdup("Default");
+ mp_ass_set_style(style, track->PlayResY, opts->sub_text_style);
+ }
+
+ if (opts->ass_style_override)
+ ass_process_force_style(track);
+}
static bool supports_format(const char *format)
{
@@ -76,6 +105,11 @@ static int init(struct sd *sd)
if (!ctx->is_converted)
ctx->ass_track->track_type = TRACK_TYPE_ASS;
+ ctx->shadow_track = ass_new_track(sd->ass_library);
+ ctx->shadow_track->PlayResX = 384;
+ ctx->shadow_track->PlayResY = 288;
+ mp_ass_add_default_styles(ctx->shadow_track, opts);
+
if (sd->extradata) {
ass_process_codec_private(ctx->ass_track, sd->extradata,
sd->extradata_len);
@@ -85,6 +119,19 @@ static int init(struct sd *sd)
pthread_mutex_unlock(sd->ass_lock);
+ ctx->sub_speed = 1.0;
+
+ if (sd->video_fps && sd->sh && sd->sh->sub->frame_based > 0) {
+ MP_VERBOSE(sd, "Frame based format, dummy FPS: %f, video FPS: %f\n",
+ sd->sh->sub->frame_based, sd->video_fps);
+ ctx->sub_speed *= sd->sh->sub->frame_based / sd->video_fps;
+ }
+
+ if (opts->sub_fps && sd->video_fps)
+ ctx->sub_speed *= opts->sub_fps / sd->video_fps;
+
+ ctx->sub_speed *= opts->sub_speed;
+
return 0;
}
@@ -147,12 +194,11 @@ static void decode(struct sd *sd, struct demux_packet *packet)
event->Text = strdup(text);
}
-static void configure_ass(struct sd *sd, struct mp_osd_res *dim)
+static void configure_ass(struct sd *sd, struct mp_osd_res *dim,
+ bool converted, ASS_Track *track)
{
- struct sd_ass_priv *ctx = sd->priv;
struct MPOpts *opts = sd->opts;
ASS_Renderer *priv = sd->ass_renderer;
- ASS_Track *track = ctx->ass_track;
ass_set_frame_size(priv, dim->w, dim->h);
ass_set_margins(priv, dim->mt, dim->mb, dim->ml, dim->mr);
@@ -166,7 +212,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 (ctx->is_converted || opts->ass_style_override == 3) {
+ if (converted || opts->ass_style_override == 3) {
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;
@@ -175,7 +221,7 @@ 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 (ctx->is_converted || opts->ass_style_override) {
+ if (converted || opts->ass_style_override) {
set_sub_pos = 100 - opts->sub_pos;
set_line_spacing = opts->ass_line_spacing;
set_hinting = opts->ass_hinting;
@@ -203,7 +249,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim)
mp_ass_set_style(&style, 288, opts->sub_text_style);
ass_set_selective_style_override(priv, &style);
free(style.FontName);
- if (ctx->is_converted && track->default_style < track->n_styles) {
+ if (converted && track->default_style < track->n_styles) {
mp_ass_set_style(track->styles + track->default_style,
track->PlayResY, opts->sub_text_style);
}
@@ -212,11 +258,91 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim)
ass_set_line_spacing(priv, set_line_spacing);
}
+static bool has_overrides(char *s)
+{
+ if (!s)
+ return false;
+ return strstr(s, "\\pos") || strstr(s, "\\move") || strstr(s, "\\clip") ||
+ strstr(s, "\\iclip") || strstr(s, "\\org") || strstr(s, "\\p");
+}
+
+#define END(ev) ((ev)->Start + (ev)->Duration)
+
+static long long find_timestamp(struct sd *sd, double pts)
+{
+ struct sd_ass_priv *priv = sd->priv;
+ if (pts == MP_NOPTS_VALUE)
+ return 0;
+
+ pts /= priv->sub_speed;
+
+ long long ts = llrint(pts * 1000);
+
+ if (!sd->opts->sub_fix_timing)
+ return ts;
+
+ // Try to fix small gaps and overlaps.
+ ASS_Track *track = priv->ass_track;
+ int threshold = SUB_GAP_THRESHOLD * 1000;
+ int keep = SUB_GAP_KEEP * 1000;
+
+ // Find the "current" event.
+ ASS_Event *ev[2] = {0};
+ int n_ev = 0;
+ for (int n = 0; n < track->n_events; n++) {
+ ASS_Event *event = &track->events[n];
+ if (ts >= event->Start - threshold && ts <= END(event) + threshold) {
+ if (n_ev >= MP_ARRAY_SIZE(ev))
+ return ts; // multiple overlaps - give up (probably complex subs)
+ ev[n_ev++] = event;
+ }
+ }
+
+ if (n_ev != 2)
+ return ts;
+
+ // Simple/minor heuristic against destroying typesetting.
+ if (ev[0]->Style != ev[1]->Style || has_overrides(ev[0]->Text) ||
+ has_overrides(ev[1]->Text))
+ return ts;
+
+ // Sort by start timestamps.
+ if (ev[0]->Start > ev[1]->Start)
+ MPSWAP(ASS_Event*, ev[0], ev[1]);
+
+ // We want to fix partial overlaps only.
+ if (END(ev[0]) >= END(ev[1]))
+ return ts;
+
+ if (ev[0]->Duration < keep || ev[1]->Duration < keep)
+ return ts;
+
+ // Gap between the events -> move ts to show the end of the first event.
+ if (ts >= END(ev[0]) && ts < ev[1]->Start && END(ev[0]) < ev[1]->Start &&
+ END(ev[0]) + threshold >= ev[1]->Start)
+ return END(ev[0]) - 1;
+
+ // Overlap -> move ts to the (exclusive) end of the first event.
+ // Relies on the fact that the ASS_Renderer has no overlap registered, even
+ // if there is one. This happens to work because we never render the
+ // overlapped state, and libass never resolves a collision.
+ if (ts >= ev[1]->Start && ts <= END(ev[0]) && END(ev[0]) > ev[1]->Start &&
+ END(ev[0]) <= ev[1]->Start + threshold)
+ return END(ev[0]);
+
+ return ts;
+}
+
+#undef END
+
static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
struct sub_bitmaps *res)
{
struct sd_ass_priv *ctx = sd->priv;
struct MPOpts *opts = sd->opts;
+ bool no_ass = !opts->ass_enabled || ctx->on_top;
+ bool converted = ctx->is_converted || no_ass;
+ ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track;
if (pts == MP_NOPTS_VALUE || !sd->ass_renderer)
return;
@@ -225,8 +351,8 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
ASS_Renderer *renderer = sd->ass_renderer;
double scale = dim.display_par;
- if (!ctx->is_converted && (!opts->ass_style_override ||
- opts->ass_vsfilter_aspect_compat))
+ if (!converted && (!opts->ass_style_override ||
+ opts->ass_vsfilter_aspect_compat))
{
// Let's use the original video PAR for vsfilter compatibility:
double par = scale
@@ -235,20 +361,22 @@ static void get_bitmaps(struct sd *sd, struct mp_osd_res dim, double pts,
if (isnormal(par))
scale = par;
}
- configure_ass(sd, &dim);
+ configure_ass(sd, &dim, converted, track);
ass_set_pixel_aspect(renderer, scale);
- if (!ctx->is_converted && (!opts->ass_style_override ||
- opts->ass_vsfilter_blur_compat))
+ if (!converted && (!opts->ass_style_override ||
+ opts->ass_vsfilter_blur_compat))
{
ass_set_storage_size(renderer, ctx->video_params.w, ctx->video_params.h);
} else {
ass_set_storage_size(renderer, 0, 0);
}
- mp_ass_render_frame(renderer, ctx->ass_track, pts * 1000 + .5,
- &ctx->parts, res);
+ if (no_ass)
+ fill_plaintext(sd, pts);
+ long long ts = find_timestamp(sd, pts);
+ mp_ass_render_frame(renderer, track, ts, &ctx->parts, res);
talloc_steal(ctx, ctx->parts);
- if (!ctx->is_converted)
+ if (!converted)
mangle_colors(sd, res);
pthread_mutex_unlock(sd->ass_lock);
@@ -333,7 +461,7 @@ static char *get_text(struct sd *sd, double pts)
if (pts == MP_NOPTS_VALUE)
return NULL;
- long long ipts = pts * 1000 + 0.5;
+ long long ipts = find_timestamp(sd, pts);
struct buf b = {ctx->last_text, sizeof(ctx->last_text) - 1};
@@ -360,6 +488,42 @@ static char *get_text(struct sd *sd, double pts)
return ctx->last_text;
}
+static void fill_plaintext(struct sd *sd, double pts)
+{
+ struct sd_ass_priv *ctx = sd->priv;
+ ASS_Track *track = ctx->shadow_track;
+
+ ass_flush_events(track);
+
+ char *text = get_text(sd, pts);
+ if (!text)
+ return;
+
+ bstr dst = {0};
+ while (*text) {
+ if (*text == '{')
+ bstr_xappend(NULL, &dst, bstr0("\\"));
+ bstr_xappend(NULL, &dst, (bstr){text, 1});
+ // Break ASS escapes with U+2060 WORD JOINER
+ if (*text == '\\')
+ mp_append_utf8_bstr(NULL, &dst, 0x2060);
+ text++;
+ }
+
+ if (!dst.start || !dst.start[0])
+ return;
+
+ int n = ass_alloc_event(track);
+ ASS_Event *event = track->events + n;
+ event->Start = 0;
+ event->Duration = INT_MAX;
+ event->Style = track->default_style;
+ event->Text = strdup(dst.start);
+
+ if (track->default_style < track->n_styles)
+ track->styles[track->default_style].Alignment = ctx->on_top ? 6 : 2;
+}
+
static void fix_events(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
@@ -390,15 +554,19 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
switch (cmd) {
case SD_CTRL_SUB_STEP: {
double *a = arg;
- long long res = ass_step_sub(ctx->ass_track, a[0] * 1000 + 0.5, a[1]);
+ long long ts = llrint(a[0] * (1000.0 / ctx->sub_speed));
+ long long res = ass_step_sub(ctx->ass_track, ts, a[1]);
if (!res)
return false;
- a[0] = res / 1000.0;
+ a[0] = res / (1000.0 / ctx->sub_speed);
return true;
+ }
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;
default:
return CONTROL_UNKNOWN;
}
@@ -499,20 +667,16 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
struct mp_csp_params vs_params = MP_CSP_PARAMS_DEFAULTS;
vs_params.colorspace = csp;
vs_params.levels_in = levels;
- vs_params.int_bits_in = 8;
- vs_params.int_bits_out = 8;
struct mp_cmat vs_yuv2rgb, vs_rgb2yuv;
- mp_get_yuv2rgb_coeffs(&vs_params, &vs_yuv2rgb);
- mp_invert_yuv2rgb(&vs_rgb2yuv, &vs_yuv2rgb);
+ mp_get_csp_matrix(&vs_params, &vs_yuv2rgb);
+ mp_invert_cmat(&vs_rgb2yuv, &vs_yuv2rgb);
// Proper conversion to RGB
struct mp_csp_params rgb_params = MP_CSP_PARAMS_DEFAULTS;
rgb_params.colorspace = params.colorspace;
rgb_params.levels_in = params.colorlevels;
- rgb_params.int_bits_in = 8;
- rgb_params.int_bits_out = 8;
struct mp_cmat vs2rgb;
- mp_get_yuv2rgb_coeffs(&rgb_params, &vs2rgb);
+ mp_get_csp_matrix(&rgb_params, &vs2rgb);
for (int n = 0; n < parts->num_parts; n++) {
struct sub_bitmap *sb = &parts->parts[n];
@@ -521,9 +685,9 @@ static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts)
int g = (color >> 16u) & 0xff;
int b = (color >> 8u) & 0xff;
int a = 0xff - (color & 0xff);
- int c[3] = {r, g, b};
- mp_map_int_color(&vs_rgb2yuv, 8, c);
- mp_map_int_color(&vs2rgb, 8, c);
- sb->libass.color = MP_ASS_RGBA(c[0], c[1], c[2], a);
+ int rgb[3] = {r, g, b}, yuv[3];
+ mp_map_fixp_color(&vs_rgb2yuv, 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);
}
}