diff options
Diffstat (limited to 'sub/sd_ass.c')
-rw-r--r-- | sub/sd_ass.c | 451 |
1 files changed, 299 insertions, 152 deletions
diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 939c000156..41c14623a1 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -43,22 +43,30 @@ struct sd_ass_priv { struct ass_renderer *ass_renderer; struct ass_track *ass_track; struct ass_track *shadow_track; // for --sub-ass=no rendering + bool ass_configured; bool is_converted; struct lavc_conv *converter; 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]; + bstr last_text; struct mp_image_params video_params; struct mp_image_params last_params; - int64_t *seen_packets; + struct mp_osd_res osd; + 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); @@ -76,15 +84,16 @@ 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) { if (!track->PlayResY) { + track->PlayResX = MP_ASS_FONT_PLAYRESX; track->PlayResY = MP_ASS_FONT_PLAYRESY; - track->PlayResX = track->PlayResY * 4 / 3; } track->Kerning = true; int sid = ass_alloc_style(track); @@ -94,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); } @@ -175,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); @@ -205,22 +214,23 @@ 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->log); + 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) - ass_set_style_overrides(ctx->ass_library, opts->ass_force_style_list); + 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); ctx->ass_track->track_type = TRACK_TYPE_ASS; ctx->shadow_track = ass_new_track(ctx->ass_library); - ctx->shadow_track->PlayResX = 384; - ctx->shadow_track->PlayResY = 288; - mp_ass_add_default_styles(ctx->shadow_track, opts); + 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, shared_opts, sd->order); char *extradata = sd->codec->extradata; int extradata_size = sd->codec->extradata_size; @@ -231,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); @@ -261,9 +271,7 @@ static int init(struct sd *sd) strcmp(sd->codec->codec, "null") != 0) { ctx->is_converted = true; - ctx->converter = lavc_conv_create(sd->log, sd->codec->codec, - sd->codec->extradata, - sd->codec->extradata_size); + ctx->converter = lavc_conv_create(sd); if (!ctx->converter) return -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,16 +405,20 @@ 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; double sub_duration = 0; char **r = lavc_conv_decode(ctx->converter, packet, &sub_pts, &sub_duration); - if (packet->duration < 0 || sub_duration == UINT32_MAX) { + if (sd->opts->sub_stretch_durations || + packet->duration < 0 || sub_duration == UINT32_MAX) { if (!ctx->duration_unknown) { MP_WARN(sd, "Subtitle with unknown duration.\n"); ctx->duration_unknown = true; @@ -359,16 +436,22 @@ static void decode(struct sd *sd, struct demux_packet *packet) filter_and_add(sd, &pkt2); } if (ctx->duration_unknown) { - for (int n = 0; n < track->n_events - 1; n++) { + for (int n = track->n_events - 2; n >= 0; n--) { if (track->events[n].Duration == UNKNOWN_DURATION * 1000) { - track->events[n].Duration = track->events[n + 1].Start - - track->events[n].Start; + if (track->events[n].Start != track->events[n + 1].Start) { + track->events[n].Duration = track->events[n + 1].Start - + track->events[n].Start; + } else { + track->events[n].Duration = track->events[n + 1].Duration; + } } } } } 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); } } @@ -377,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; @@ -384,7 +468,7 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, ass_set_margins(priv, dim->mt, dim->mb, dim->ml, dim->mr); bool set_use_margins = false; - int set_sub_pos = 0; + float set_sub_pos = 0.0f; float set_line_spacing = 0; float set_font_scale = 1; int set_hinting = 0; @@ -392,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; @@ -401,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 - 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; @@ -422,17 +506,17 @@ 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 LIBASS_VERSION >= 0x01306000 + 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); ASS_Style style = {0}; - mp_ass_set_style(&style, 288, opts->sub_style); + mp_ass_set_style(&style, MP_ASS_FONT_PLAYRESY, opts->sub_style); ass_set_selective_style_override(priv, &style); free(style.FontName); if (converted && track->default_style < track->n_styles) { @@ -442,6 +526,47 @@ static void configure_ass(struct sd *sd, struct mp_osd_res *dim, ass_set_font_scale(priv, set_font_scale); ass_set_hinting(priv, set_hinting); ass_set_line_spacing(priv, set_line_spacing); +#if LIBASS_VERSION >= 0x01600010 + if (converted) { + ass_track_set_feature(track, ASS_FEATURE_WRAP_UNICODE, 1); + if (!opts->sub_vsfilter_bidi_compat) { + for (int n = 0; n < track->n_styles; n++) { + track->styles[n].Encoding = -1; + } + ass_track_set_feature(track, ASS_FEATURE_BIDI_BRACKETS, 1); + ass_track_set_feature(track, ASS_FEATURE_WHOLE_TEXT_LAYOUT, 1); + } + } +#endif + if (converted) { + bool override_playres = true; + char **ass_style_override_list = opts->ass_style_override_list; + for (int i = 0; ass_style_override_list && ass_style_override_list[i]; i++) { + if (bstr_find0(bstr0(ass_style_override_list[i]), "PlayResX") >= 0) + override_playres = false; + } + + // srt to ass conversion from ffmpeg has fixed PlayResX of 384 with an + // aspect of 4:3. Starting with libass f08f8ea5 (pre 0.17) PlayResX + // affects shadow and border widths, among others, so to render borders + // and shadows correctly, we adjust PlayResX according to the DAR. + // But PlayResX also affects margins, so we adjust those too. + // This should ensure basic srt-to-ass ffmpeg conversion has correct + // borders, but there could be other issues with some srt extensions + // and/or different source formats which would be exposed over time. + // Make these adjustments only if the user didn't set PlayResX. + if (override_playres) { + int vidw = dim->w - (dim->ml + dim->mr); + int vidh = dim->h - (dim->mt + dim->mb); + int old_playresx = track->PlayResX; + track->PlayResX = track->PlayResY * (double)vidw / MPMAX(vidh, 1); + double fix_margins = track->PlayResX / (double)old_playresx; + for (int n = 0; n < track->n_styles; n++) { + track->styles[n].MarginL = round(track->styles[n].MarginL * fix_margins); + track->styles[n].MarginR = round(track->styles[n].MarginR * fix_margins); + } + } + } } static bool has_overrides(char *s) @@ -462,7 +587,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. @@ -524,24 +649,28 @@ 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; - bool converted = ctx->is_converted || no_ass; + 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 && !lavc_conv_is_styled(ctx->converter)) || no_ass; ASS_Track *track = no_ass ? ctx->shadow_track : ctx->ass_track; ASS_Renderer *renderer = ctx->ass_renderer; struct sub_bitmaps *res = &(struct sub_bitmaps){0}; + // Always update the osd_res + struct mp_osd_res old_osd = ctx->osd; + ctx->osd = dim; + if (pts == MP_NOPTS_VALUE || !renderer) goto done; // Currently no supported text sub formats support a distinction between forced // and unforced lines, so we just assume everything's unforced and discard everything. // If we ever see a format that makes this distinction, we can add support here. - if (opts->forced_subs_only_current) + if (opts->sub_forced_events_only) 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: @@ -549,9 +678,12 @@ static struct sub_bitmaps *get_bitmaps(struct sd *sd, struct mp_osd_res dim, if (isnormal(par)) scale *= par; } - configure_ass(sd, &dim, converted, track); + if (!ctx->ass_configured || !osd_res_equals(old_osd, ctx->osd)) { + configure_ass(sd, &dim, converted, track); + 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); @@ -559,18 +691,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_. @@ -582,31 +709,24 @@ done: return res; } -struct buf { - char *start; - int size; - int len; -}; +#define MAX_BUF_SIZE 1024 * 1024 +#define MIN_EXPAND_SIZE 4096 -static void append(struct buf *b, char c) +static void append(bstr *b, char c) { - if (b->len < b->size) { - b->start[b->len] = c; - b->len++; - } + bstr_xappend(NULL, b, (bstr){&c, 1}); } -static void ass_to_plaintext(struct buf *b, const char *in) +static void ass_to_plaintext(bstr *b, const char *in) { - bool in_tag = false; const char *open_tag_pos = NULL; bool in_drawing = false; while (*in) { - if (in_tag) { + if (open_tag_pos) { if (in[0] == '}') { in += 1; - in_tag = false; - } else if (in[0] == '\\' && in[1] == 'p') { + open_tag_pos = NULL; + } 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. @@ -629,7 +749,6 @@ static void ass_to_plaintext(struct buf *b, const char *in) } else if (in[0] == '{') { open_tag_pos = in; in += 1; - in_tag = true; } else { if (!in_drawing) append(b, in[0]); @@ -638,65 +757,86 @@ static void ass_to_plaintext(struct buf *b, const char *in) } } // A '{' without a closing '}' is always visible. - if (in_tag) { - while (*open_tag_pos) - append(b, *open_tag_pos++); + if (open_tag_pos) { + bstr_xappend(NULL, b, bstr0(open_tag_pos)); } } -// Empty string counts as whitespace. Reads s[len-1] even if there are \0s. -static bool is_whitespace_only(char *s, int len) +// Empty string counts as whitespace. +static bool is_whitespace_only(bstr b) { - for (int n = 0; n < len; n++) { - if (s[n] != ' ' && s[n] != '\t') + for (int n = 0; n < b.len; n++) { + if (b.start[n] != ' ' && b.start[n] != '\t') return false; } return true; } -static char *get_text_buf(struct sd *sd, double pts, enum sd_text_type type) +static bstr get_text_buf(struct sd *sd, double pts, enum sd_text_type type) { struct sd_ass_priv *ctx = sd->priv; ASS_Track *track = ctx->ass_track; if (pts == MP_NOPTS_VALUE) - return NULL; + return (bstr){0}; long long ipts = find_timestamp(sd, pts); - struct buf b = {ctx->last_text, sizeof(ctx->last_text) - 1}; + bstr *b = &ctx->last_text; + + if (!b->start) + b->start = talloc_size(ctx, 4096); + + b->len = 0; for (int i = 0; i < track->n_events; ++i) { ASS_Event *event = track->events + i; if (ipts >= event->Start && ipts < event->Start + event->Duration) { if (event->Text) { - int start = b.len; + int start = b->len; if (type == SD_TEXT_TYPE_PLAIN) { - ass_to_plaintext(&b, event->Text); + ass_to_plaintext(b, event->Text); + } else if (type == SD_TEXT_TYPE_ASS_FULL) { + long long s = event->Start; + long long e = s + event->Duration; + + ASS_Style *style = (event->Style < 0 || event->Style >= track->n_styles) ? NULL : &track->styles[event->Style]; + + int sh = (s / 60 / 60 / 1000); + int sm = (s / 60 / 1000) % 60; + int ss = (s / 1000) % 60; + int sc = (s / 10) % 100; + int eh = (e / 60 / 60 / 1000); + int em = (e / 60 / 1000) % 60; + int es = (e / 1000) % 60; + int ec = (e / 10) % 100; + + bstr_xappend_asprintf(NULL, b, "Dialogue: %d,%d:%02d:%02d.%02d,%d:%02d:%02d.%02d,%s,%s,%04d,%04d,%04d,%s,%s", + event->Layer, + sh, sm, ss, sc, + eh, em, es, ec, + (style && style->Name) ? style->Name : "", event->Name, + event->MarginL, event->MarginR, event->MarginV, + event->Effect, event->Text); } else { - char *t = event->Text; - while (*t) - append(&b, *t++); + bstr_xappend(NULL, b, bstr0(event->Text)); } - if (is_whitespace_only(&b.start[start], b.len - start)) { - b.len = start; + if (is_whitespace_only(bstr_cut(*b, start))) { + b->len = start; } else { - append(&b, '\n'); + append(b, '\n'); } } } } - b.start[b.len] = '\0'; - - if (b.len > 0 && b.start[b.len - 1] == '\n') - b.start[b.len - 1] = '\0'; + bstr_eatend(b, (bstr)bstr0_lit("\n")); - return ctx->last_text; + return *b; } static char *get_text(struct sd *sd, double pts, enum sd_text_type type) { - return talloc_strdup(NULL, get_text_buf(sd, pts, type)); + return bstrto0(NULL, get_text_buf(sd, pts, type)); } static struct sd_times get_times(struct sd *sd, double pts) @@ -705,7 +845,7 @@ static struct sd_times get_times(struct sd *sd, double pts) ASS_Track *track = ctx->ass_track; struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE }; - if (pts == MP_NOPTS_VALUE || ctx->duration_unknown) + if (pts == MP_NOPTS_VALUE) return res; long long ipts = find_timestamp(sd, pts); @@ -735,23 +875,26 @@ static void fill_plaintext(struct sd *sd, double pts) ass_flush_events(track); - char *text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN); - if (!text) + bstr text = get_text_buf(sd, pts, SD_TEXT_TYPE_PLAIN); + if (!text.len) return; bstr dst = {0}; - if (ctx->on_top) - bstr_xappend(NULL, &dst, bstr0("{\\a6}")); - - while (*text) { - if (*text == '{') + while (text.len) { + if (*text.start == '{') { + bstr_xappend(NULL, &dst, bstr0("\\{")); + text = bstr_cut(text, 1); + } else if (*text.start == '\\') { bstr_xappend(NULL, &dst, bstr0("\\")); - bstr_xappend(NULL, &dst, (bstr){text, 1}); - // Break ASS escapes with U+2060 WORD JOINER - if (*text == '\\') + // Break ASS escapes with U+2060 WORD JOINER mp_append_utf8_bstr(NULL, &dst, 0x2060); - text++; + text = bstr_cut(text, 1); + } + + int i = bstrcspn(text, "{\\"); + bstr_xappend(NULL, &dst, (bstr){text.start, i}); + text = bstr_cut(text, i); } if (!dst.start) @@ -770,7 +913,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; @@ -801,26 +944,29 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) long long res = ass_step_sub(ctx->ass_track, ts, a[1]); if (!res) return false; - a[0] += res / 1000.0; + // Try to account for overlapping durations + a[0] += res / 1000.0 + SUB_SEEK_OFFSET; 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; case SD_CTRL_UPDATE_OPTS: { int flags = (uintptr_t)arg; if (flags & UPDATE_SUB_FILT) { filters_destroy(sd); filters_init(sd); ctx->clear_once = true; // allow reloading on seeks + reset(sd); } if (flags & UPDATE_SUB_HARD) { + // ass_track will be recreated, so clear duplicate cache + ctx->clear_once = true; + reset(sd); assobjects_destroy(sd); assobjects_init(sd); } + ctx->ass_configured = false; // ass always needs to be reconfigured return CONTROL_OK; } default: @@ -847,27 +993,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) @@ -880,8 +1026,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) @@ -890,49 +1036,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) + 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++) { @@ -943,7 +1091,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); } @@ -974,11 +1122,10 @@ bstr sd_ass_pkt_text(struct sd_filter *ft, struct demux_packet *pkt, int offset) return txt; } -bstr sd_ass_to_plaintext(char *out, size_t out_siz, const char *in) +bstr sd_ass_to_plaintext(char **out, const char *in) { - struct buf b = {out, out_siz, 0}; + bstr b = {*out}; ass_to_plaintext(&b, in); - if (b.len < out_siz) - out[b.len] = 0; - return (bstr){out, b.len}; + *out = b.start; + return b; } |