diff --git a/options/m_option.h b/options/m_option.h index a78def276b..9eb994a5cf 100644 --- a/options/m_option.h +++ b/options/m_option.h @@ -413,6 +413,7 @@ char *format_file_size(int64_t size); // certain groups of options. #define UPDATE_OPT_FIRST (1 << 8) #define UPDATE_TERM (1 << 8) // terminal options +#define UPDATE_SUB_FILT (1 << 9) // subtitle filter options #define UPDATE_OSD (1 << 10) // related to OSD rendering #define UPDATE_BUILTIN_SCRIPTS (1 << 11) // osc/ytdl/stats #define UPDATE_IMGPAR (1 << 12) // video image params overrides diff --git a/options/options.c b/options/options.c index 489f1bc4ed..73cd7ff76c 100644 --- a/options/options.c +++ b/options/options.c @@ -194,6 +194,19 @@ const struct m_sub_options vo_sub_opts = { }, }; +#undef OPT_BASE_STRUCT +#define OPT_BASE_STRUCT struct mp_sub_filter_opts + +const struct m_sub_options mp_sub_filter_opts = { + .opts = (const struct m_option[]){ + OPT_FLAG("sub-filter-sdh", sub_filter_SDH, 0), + OPT_FLAG("sub-filter-sdh-harder", sub_filter_SDH_harder, 0), + {0} + }, + .size = sizeof(OPT_BASE_STRUCT), + .change_flags = UPDATE_SUB_FILT, +}; + #undef OPT_BASE_STRUCT #define OPT_BASE_STRUCT struct mp_subtitle_opts @@ -212,8 +225,6 @@ const struct m_sub_options mp_subtitle_sub_opts = { OPT_FLOATRANGE("sub-gauss", sub_gauss, 0, 0.0, 3.0), OPT_FLAG("sub-gray", sub_gray, 0), OPT_FLAG("sub-ass", ass_enabled, 0), - OPT_FLAG("sub-filter-sdh", sub_filter_SDH, 0), - OPT_FLAG("sub-filter-sdh-harder", sub_filter_SDH_harder, 0), OPT_FLOATRANGE("sub-scale", sub_scale, 0, 0, 100), OPT_FLOATRANGE("sub-ass-line-spacing", ass_line_spacing, 0, -1000, 1000), OPT_FLAG("sub-use-margins", sub_use_margins, 0), @@ -555,6 +566,7 @@ static const m_option_t mp_opts[] = { ({"no", -1}, {"exact", 0}, {"fuzzy", 1}, {"all", 2})), OPT_SUBSTRUCT("", subs_rend, mp_subtitle_sub_opts, 0), + OPT_SUBSTRUCT("", subs_filt, mp_sub_filter_opts, 0), OPT_SUBSTRUCT("", osd_rend, mp_osd_render_sub_opts, 0), OPT_FLAG("osd-bar", osd_bar_visible, UPDATE_OSD), diff --git a/options/options.h b/options/options.h index bc0f1b3e89..8fbec3161c 100644 --- a/options/options.h +++ b/options/options.h @@ -82,8 +82,6 @@ struct mp_subtitle_opts { float sub_scale; float sub_gauss; int sub_gray; - int sub_filter_SDH; - int sub_filter_SDH_harder; int ass_enabled; float ass_line_spacing; int ass_use_margins; @@ -102,6 +100,11 @@ struct mp_subtitle_opts { int teletext_page; }; +struct mp_sub_filter_opts { + int sub_filter_SDH; + int sub_filter_SDH_harder; +}; + struct mp_osd_render_opts { float osd_bar_align_x; float osd_bar_align_y; @@ -173,6 +176,7 @@ typedef struct MPOpts { char *audio_spdif; struct mp_subtitle_opts *subs_rend; + struct mp_sub_filter_opts *subs_filt; struct mp_osd_render_opts *osd_rend; int osd_level; @@ -361,6 +365,7 @@ struct filter_opts { extern const struct m_sub_options vo_sub_opts; extern const struct m_sub_options dvd_conf; extern const struct m_sub_options mp_subtitle_sub_opts; +extern const struct m_sub_options mp_sub_filter_opts; extern const struct m_sub_options mp_osd_render_sub_opts; extern const struct m_sub_options filter_conf; extern const struct m_sub_options resample_conf; diff --git a/player/command.c b/player/command.c index 21c595ce57..37ace97ba6 100644 --- a/player/command.c +++ b/player/command.c @@ -6115,12 +6115,14 @@ void mp_option_change_callback(void *ctx, struct m_config_option *co, int flags, if (flags & UPDATE_TERM) mp_update_logging(mpctx, false); - if (flags & UPDATE_OSD) { + if (flags & (UPDATE_OSD | UPDATE_SUB_FILT)) { for (int n = 0; n < NUM_PTRACKS; n++) { struct track *track = mpctx->current_track[n][STREAM_SUB]; struct dec_sub *sub = track ? track->d_sub : NULL; - if (sub) - sub_update_opts(track->d_sub); + if (sub) { + sub_control(track->d_sub, SD_CTRL_UPDATE_OPTS, + (void *)(uintptr_t)flags); + } } osd_changed(mpctx->osd); mp_wakeup_core(mpctx); diff --git a/sub/dec_sub.c b/sub/dec_sub.c index 27cab201d9..f98a478be4 100644 --- a/sub/dec_sub.c +++ b/sub/dec_sub.c @@ -413,6 +413,7 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg) { int r = CONTROL_UNKNOWN; pthread_mutex_lock(&sub->lock); + bool propagate = false; switch (cmd) { case SD_CTRL_SET_VIDEO_DEF_FPS: sub->video_fps = *(double *)arg; @@ -427,23 +428,21 @@ int sub_control(struct dec_sub *sub, enum sd_ctrl cmd, void *arg) if (r == CONTROL_OK) a[0] = pts_from_subtitle(sub, arg2[0]); break; + case SD_CTRL_UPDATE_OPTS: + if (m_config_cache_update(sub->opts_cache)) + update_subtitle_speed(sub); + propagate = true; + break; } default: - if (sub->sd->driver->control) - r = sub->sd->driver->control(sub->sd, cmd, arg); + propagate = true; } + if (propagate && sub->sd->driver->control) + r = sub->sd->driver->control(sub->sd, cmd, arg); pthread_mutex_unlock(&sub->lock); return r; } -void sub_update_opts(struct dec_sub *sub) -{ - pthread_mutex_lock(&sub->lock); - if (m_config_cache_update(sub->opts_cache)) - update_subtitle_speed(sub); - pthread_mutex_unlock(&sub->lock); -} - void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink) { pthread_mutex_lock(&sub->lock); diff --git a/sub/dec_sub.h b/sub/dec_sub.h index 5449b97ad0..030b8d21e1 100644 --- a/sub/dec_sub.h +++ b/sub/dec_sub.h @@ -19,6 +19,7 @@ enum sd_ctrl { SD_CTRL_SET_VIDEO_PARAMS, SD_CTRL_SET_TOP, SD_CTRL_SET_VIDEO_DEF_FPS, + SD_CTRL_UPDATE_OPTS, }; struct sd_times { @@ -46,7 +47,6 @@ char *sub_get_text(struct dec_sub *sub, double pts); struct sd_times sub_get_times(struct dec_sub *sub, double pts); void sub_reset(struct dec_sub *sub); void sub_select(struct dec_sub *sub, bool selected); -void sub_update_opts(struct dec_sub *sub); void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink); void sub_set_play_dir(struct dec_sub *sub, int dir); diff --git a/sub/filter_sdh.c b/sub/filter_sdh.c index 72744ec20e..2b544ea222 100644 --- a/sub/filter_sdh.c +++ b/sub/filter_sdh.c @@ -15,9 +15,11 @@ * License along with mpv. If not, see . */ -#include #include +#include #include +#include + #include "misc/ctype.h" #include "common/common.h" #include "common/msg.h" @@ -43,7 +45,7 @@ static void init_buf(struct buffer *buf, int length) buf->length = length; } -static inline int append(struct sd *sd, struct buffer *buf, char c) +static inline int append(struct sd_filter *sd, struct buffer *buf, char c) { if (buf->pos >= 0 && buf->pos < buf->length) { buf->string[buf->pos++] = c; @@ -66,7 +68,7 @@ static inline int append(struct sd *sd, struct buffer *buf, char c) // // on return the read pointer is updated to the position after // the tags. -static void copy_ass(struct sd *sd, char **rpp, struct buffer *buf) +static void copy_ass(struct sd_filter *sd, char **rpp, struct buffer *buf) { char *rp = *rpp; @@ -83,7 +85,7 @@ static void copy_ass(struct sd *sd, char **rpp, struct buffer *buf) return; } -static bool skip_bracketed(struct sd *sd, char **rpp, struct buffer *buf); +static bool skip_bracketed(struct sd_filter *sd, char **rpp, struct buffer *buf); // check for speaker label, like MAN: // normal subtitles may include mixed case text with : after so @@ -101,7 +103,7 @@ static bool skip_bracketed(struct sd *sd, char **rpp, struct buffer *buf); // if no label was found read pointer and write position in buffer // will be unchanged // otherwise they point to next position after label and next write position -static void skip_speaker_label(struct sd *sd, char **rpp, struct buffer *buf) +static void skip_speaker_label(struct sd_filter *sd, char **rpp, struct buffer *buf) { int filter_harder = sd->opts->sub_filter_SDH_harder; char *rp = *rpp; @@ -187,7 +189,7 @@ static void skip_speaker_label(struct sd *sd, char **rpp, struct buffer *buf) // return true if bracketed text was removed. // if not valid SDH read pointer and write buffer position will be unchanged // otherwise they point to next position after text and next write position -static bool skip_bracketed(struct sd *sd, char **rpp, struct buffer *buf) +static bool skip_bracketed(struct sd_filter *sd, char **rpp, struct buffer *buf) { char *rp = *rpp; int old_pos = buf->pos; @@ -235,7 +237,7 @@ static bool skip_bracketed(struct sd *sd, char **rpp, struct buffer *buf) // return true if paranthesed text was removed. // if not valid SDH read pointer and write buffer position will be unchanged // otherwise they point to next position after text and next write position -static bool skip_parenthesed(struct sd *sd, char **rpp, struct buffer *buf) +static bool skip_parenthesed(struct sd_filter *sd, char **rpp, struct buffer *buf) { int filter_harder = sd->opts->sub_filter_SDH_harder; char *rp = *rpp; @@ -289,7 +291,8 @@ static bool skip_parenthesed(struct sd *sd, char **rpp, struct buffer *buf) // // when removing characters the following are moved back // -static void remove_leading_hyphen_space(struct sd *sd, int start_pos, struct buffer *buf) +static void remove_leading_hyphen_space(struct sd_filter *sd, int start_pos, + struct buffer *buf) { int old_pos = buf->pos; if (start_pos < 0 || start_pos >= old_pos) @@ -340,7 +343,8 @@ static void remove_leading_hyphen_space(struct sd *sd, int start_pos, struct buf // // Returns NULL if filtering resulted in all of ASS data being removed so no // subtitle should be output -char *filter_SDH(struct sd *sd, char *format, int n_ignored, char *data, int length) +static char *filter_SDH(struct sd_filter *sd, char *format, int n_ignored, + char *data, int length) { if (!format) { MP_VERBOSE(sd, "SDH filtering not possible - format missing\n"); @@ -462,3 +466,42 @@ char *filter_SDH(struct sd *sd, char *format, int n_ignored, char *data, int len return NULL; } } + +static bool sdh_init(struct sd_filter *ft) +{ + if (strcmp(ft->codec, "ass") != 0) + return false; + + if (!ft->opts->sub_filter_SDH) + return false; + + return true; +} + +static struct demux_packet *sdh_filter(struct sd_filter *ft, + struct demux_packet *pkt) +{ + char *line = (char *)pkt->buffer; + size_t len = pkt->len; + if (len >= INT_MAX) + return NULL; + + line = filter_SDH(ft, ft->event_format, 1, line, len); + if (!line) + return NULL; + + // Stupidly, this copies it again. One could possibly allocate the packet + // for writing in the first place (new_demux_packet()) and use + // demux_packet_shorten(). Or not allocate anything on no change. + struct demux_packet *npkt = new_demux_packet_from(line, strlen(line)); + if (npkt) + demux_packet_copy_attribs(npkt, pkt); + + talloc_free(line); + return npkt; +} + +const struct sd_filter_functions sd_filter_sdh = { + .init = sdh_init, + .filter = sdh_filter, +}; diff --git a/sub/sd.h b/sub/sd.h index 6c3fc4e285..0d361edba6 100644 --- a/sub/sd.h +++ b/sub/sd.h @@ -3,6 +3,7 @@ #include "dec_sub.h" #include "demux/packet.h" +#include "misc/bstr.h" // up to 210 ms overlaps or gaps are removed #define SUB_GAP_THRESHOLD 0.210 @@ -43,6 +44,7 @@ struct sd_functions { struct sd_times (*get_times)(struct sd *sd, double pts); }; +// lavc_conv.c struct lavc_conv; struct lavc_conv *lavc_conv_create(struct mp_log *log, const char *codec_name, char *extradata, int extradata_len); @@ -52,6 +54,38 @@ char **lavc_conv_decode(struct lavc_conv *priv, struct demux_packet *packet, void lavc_conv_reset(struct lavc_conv *priv); void lavc_conv_uninit(struct lavc_conv *priv); -char *filter_SDH(struct sd *sd, char *format, int n_ignored, char *data, int length); +struct sd_filter { + struct mpv_global *global; + struct mp_log *log; + struct mp_sub_filter_opts *opts; + const struct sd_filter_functions *driver; + + void *priv; + + // Static codec parameters. Set by sd; cannot be changed by filter. + char *codec; + char *event_format; +}; + +struct sd_filter_functions { + bool (*init)(struct sd_filter *ft); + + // Filter an ASS event (usually in the Matroska format, but event_format + // can be used to determine details). + // Returning NULL is interpreted as dropping the event completely. + // Returning pkt makes it no-op. + // If the returned packet is not pkt or NULL, it must have been properly + // allocated. + // pkt is owned by the caller (and freed by the caller when needed). + // Note: as by normal demux_packet rules, you must not modify any fields in + // it, or the data referenced by it. You must create a new demux_packet + // when modifying data. + struct demux_packet *(*filter)(struct sd_filter *ft, + struct demux_packet *pkt); + + void (*uninit)(struct sd_filter *ft); +}; + +extern const struct sd_filter_functions sd_filter_sdh; #endif diff --git a/sub/sd_ass.c b/sub/sd_ass.c index 5443a688d4..2b0cf13127 100644 --- a/sub/sd_ass.c +++ b/sub/sd_ass.c @@ -26,6 +26,8 @@ #include "mpv_talloc.h" +#include "config.h" +#include "options/m_config.h" #include "options/options.h" #include "common/common.h" #include "common/msg.h" @@ -43,6 +45,9 @@ struct sd_ass_priv { struct ass_track *shadow_track; // for --sub-ass=no rendering 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 *bs; @@ -57,6 +62,12 @@ struct sd_ass_priv { static void mangle_colors(struct sd *sd, struct sub_bitmaps *parts); static void fill_plaintext(struct sd *sd, double pts); +static const struct sd_filter_functions *const filters[] = { + // Note: list order defines filter order. + &sd_filter_sdh, + NULL, +}; + // 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) @@ -130,6 +141,43 @@ static void add_subtitle_fonts(struct sd *sd) } } +static void filters_destroy(struct sd *sd) +{ + struct sd_ass_priv *ctx = sd->priv; + + for (int n = 0; n < ctx->num_filters; n++) { + struct sd_filter *ft = ctx->filters[n]; + if (ft->driver->uninit) + ft->driver->uninit(ft); + talloc_free(ft); + } + ctx->num_filters = 0; +} + +static void filters_init(struct sd *sd) +{ + struct sd_ass_priv *ctx = sd->priv; + + filters_destroy(sd); + + for (int n = 0; filters[n]; n++) { + struct sd_filter *ft = talloc_ptrtype(ctx, ft); + *ft = (struct sd_filter){ + .global = sd->global, + .log = sd->log, + .opts = mp_get_config_group(ft, sd->global, &mp_sub_filter_opts), + .driver = filters[n], + .codec = "ass", + .event_format = ctx->ass_track->event_format, + }; + if (ft->driver->init(ft)) { + MP_TARRAY_APPEND(ctx, ctx->filters, ctx->num_filters, ft); + } else { + talloc_free(ft); + } + } +} + static void enable_output(struct sd *sd, bool enable) { struct sd_ass_priv *ctx = sd->priv; @@ -198,12 +246,37 @@ static int init(struct sd *sd) #endif enable_output(sd, true); + filters_init(sd); ctx->packer = mp_ass_packer_alloc(ctx); return 0; } +// 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; + + for (int n = 0; n < ctx->num_filters; n++) { + struct sd_filter *ft = ctx->filters[n]; + struct demux_packet *npkt = ft->driver->filter(ft, pkt); + if (pkt != npkt && pkt != orig_pkt) + talloc_free(pkt); + pkt = npkt; + if (!pkt) + return; + } + + ass_process_chunk(ctx->ass_track, pkt->buffer, pkt->len, + llrint(pkt->pts * 1000), + llrint(pkt->duration * 1000)); + + 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. @@ -251,15 +324,13 @@ static void decode(struct sd *sd, struct demux_packet *packet) } for (int n = 0; r && r[n]; n++) { - char *ass_line = r[n]; - if (sd->opts->sub_filter_SDH) - ass_line = filter_SDH(sd, track->event_format, 1, ass_line, 0); - if (ass_line) - ass_process_chunk(track, ass_line, strlen(ass_line), - llrint(sub_pts * 1000), - llrint(sub_duration * 1000)); - if (sd->opts->sub_filter_SDH) - talloc_free(ass_line); + struct demux_packet pkt2 = { + .pts = sub_pts, + .duration = sub_duration, + .buffer = r[n], + .len = strlen(r[n]), + }; + filter_and_add(sd, &pkt2); } if (ctx->duration_unknown) { for (int n = 0; n < track->n_events - 1; n++) { @@ -272,18 +343,7 @@ 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. - char *ass_line = packet->buffer; - int ass_len = packet->len; - if (sd->opts->sub_filter_SDH) { - ass_line = filter_SDH(sd, track->event_format, 1, ass_line, ass_len); - ass_len = ass_line ? strlen(ass_line) : 0; - } - if (ass_line) - ass_process_chunk(track, ass_line, ass_len, - llrint(packet->pts * 1000), - llrint(packet->duration * 1000)); - if (sd->opts->sub_filter_SDH) - talloc_free(ass_line); + filter_and_add(sd, packet); } } @@ -668,10 +728,11 @@ 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) { + if (sd->opts->sub_clear_on_seek || ctx->duration_unknown || ctx->clear_once) { ass_flush_events(ctx->ass_track); ctx->num_seen_packets = 0; sd->preload_ok = false; + ctx->clear_once = false; } if (ctx->converter) lavc_conv_reset(ctx->converter); @@ -681,6 +742,7 @@ static void uninit(struct sd *sd) { struct sd_ass_priv *ctx = sd->priv; + filters_destroy(sd); if (ctx->converter) lavc_conv_uninit(ctx->converter); ass_free_track(ctx->ass_track); @@ -708,6 +770,15 @@ static int control(struct sd *sd, enum sd_ctrl cmd, void *arg) 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 + } + return CONTROL_OK; + } default: return CONTROL_UNKNOWN; }