summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--options/m_option.h1
-rw-r--r--options/options.c16
-rw-r--r--options/options.h9
-rw-r--r--player/command.c8
-rw-r--r--sub/dec_sub.c19
-rw-r--r--sub/dec_sub.h2
-rw-r--r--sub/filter_sdh.c61
-rw-r--r--sub/sd.h36
-rw-r--r--sub/sd_ass.c115
9 files changed, 217 insertions, 50 deletions
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
@@ -195,6 +195,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
const struct m_sub_options mp_subtitle_sub_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 <http://www.gnu.org/licenses/>.
*/
-#include <stdlib.h>
#include <assert.h>
+#include <stdlib.h>
#include <string.h>
+#include <limits.h>
+
#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;
}