sub: add sub-lines property

Return all subtitle lines in memory with text, start and end.

I did not name this sub-all so it cannot be misinterpreted as referring
to all subtitle tracks.

Closes #12810.
This commit is contained in:
Guido Cella
2026-04-09 18:17:48 +02:00
committed by Kacper Michajłow
parent c662594f05
commit 5f02e1b8a2
7 changed files with 142 additions and 2 deletions
+1
View File
@@ -0,0 +1 @@
add `sub-lines` and `secondary-sub-lines` properties
+23
View File
@@ -3290,6 +3290,29 @@ Property list
``secondary-sub-end``
Same as ``sub-end``, but for the secondary subtitles.
``sub-lines``
The list of subtitle lines in memory. Not available if the subtitle is not
text-based (i.e. DVD/BD subtitles).
When querying the property with the client API using ``MPV_FORMAT_NODE``,
or with Lua ``mp.get_property_native``, this will return a mpv_node with
the following contents:
::
MPV_FORMAT_NODE_ARRAY
MPV_FORMAT_NODE_MAP (for each subtitle line)
"text" MPV_FORMAT_STRING
"start" MPV_FORMAT_DOUBLE
"end" MPV_FORMAT_DOUBLE
ASS tags are stripped from ``text``.
``end`` is absent if unknown.
``secondary-sub-lines``
Same as ``sub-lines``, but for the secondary subtitles.
``playlist-pos`` (RW)
Current position on playlist. The first entry is on position 0. Writing to
this property may start playback at the new position.
+43 -2
View File
@@ -3455,6 +3455,43 @@ static int mp_property_sub_end(void *ctx, struct m_property *prop,
return property_time(action, arg, end);
}
static int mp_property_sub_lines(void *ctx, struct m_property *prop,
int action, void *arg)
{
MPContext *mpctx = ctx;
int sub_index = *(int *)prop->priv;
struct track *track = mpctx->current_track[sub_index][STREAM_SUB];
struct dec_sub *sub = track ? track->d_sub : NULL;
if (!sub)
return M_PROPERTY_UNAVAILABLE;
switch (action) {
case M_PROPERTY_GET_TYPE:
*(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE};
return M_PROPERTY_OK;
case M_PROPERTY_GET:
case M_PROPERTY_GET_NODE: {
struct sub_lines *lines = sub_get_lines(sub);
if (!lines)
return M_PROPERTY_UNAVAILABLE;
struct mpv_node *node = arg;
node_init(node, MPV_FORMAT_NODE_ARRAY, NULL);
for (int i = 0; i < lines->num_entries; i++) {
struct sub_line *line = &lines->entries[i];
struct mpv_node *entry = node_array_add(node, MPV_FORMAT_NODE_MAP);
node_map_add_string(entry, "text", line->text);
node_map_add_double(entry, "start", line->start);
if (line->end != MP_NOPTS_VALUE)
node_map_add_double(entry, "end", line->end);
}
talloc_free(lines);
return M_PROPERTY_OK;
}
}
return M_PROPERTY_NOT_IMPLEMENTED;
}
static int mp_property_playlist_current_pos(void *ctx, struct m_property *prop,
int action, void *arg)
{
@@ -4650,6 +4687,10 @@ static const struct m_property mp_properties_base[] = {
.priv = (void *)&(const int){0}},
{"secondary-sub-end", mp_property_sub_end,
.priv = (void *)&(const int){1}},
{"sub-lines", mp_property_sub_lines,
.priv = (void *)&(const int){0}},
{"secondary-sub-lines", mp_property_sub_lines,
.priv = (void *)&(const int){1}},
{"vf", mp_property_vf},
{"af", mp_property_af},
@@ -4734,8 +4775,8 @@ static const char *const *const mp_event_property_change[] = {
"secondary-sub-text", "audio-bitrate", "video-bitrate", "sub-bitrate",
"decoder-frame-drop-count", "frame-drop-count", "video-frame-info",
"vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start",
"secondary-sub-end", "video-out-params", "video-dec-params", "video-params",
"deinterlace-active", "video-target-params"),
"secondary-sub-end", "sub-lines", "secondary-sub-lines", "video-out-params",
"video-dec-params", "video-params", "deinterlace-active", "video-target-params"),
E(MP_EVENT_DURATION_UPDATE, "duration"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight",
+21
View File
@@ -580,3 +580,24 @@ bool sub_is_secondary_visible(struct dec_sub *sub)
mp_mutex_unlock(&sub->lock);
return ret;
}
static int sub_line_cmp(const void *a, const void *b)
{
const struct sub_line *la = a, *lb = b;
if (la->start < lb->start) return -1;
if (la->start > lb->start) return 1;
return 0;
}
struct sub_lines *sub_get_lines(struct dec_sub *sub)
{
mp_mutex_lock(&sub->lock);
struct sub_lines *res = NULL;
if (sub->sd->driver->get_lines) {
res = sub->sd->driver->get_lines(sub->sd);
qsort(res->entries, res->num_entries, sizeof(res->entries[0]),
sub_line_cmp);
}
mp_mutex_unlock(&sub->lock);
return res;
}
+14
View File
@@ -39,6 +39,17 @@ struct attachment_list {
int num_entries;
};
struct sub_line {
char *text;
double start;
double end;
};
struct sub_lines {
struct sub_line *entries;
int num_entries;
};
struct dec_sub *sub_create(struct mpv_global *global, struct track *track,
struct attachment_list *attachments, int order);
void sub_destroy(struct dec_sub *sub);
@@ -53,6 +64,9 @@ struct sub_bitmaps *sub_get_bitmaps(struct dec_sub *sub, struct mp_osd_res dim,
char *sub_get_text(struct dec_sub *sub, double pts, enum sd_text_type type);
char *sub_ass_get_extradata(struct dec_sub *sub);
struct sd_times sub_get_times(struct dec_sub *sub, double pts);
// Return subtitle lines in memory. Call talloc_free() on the return value.
struct sub_lines *sub_get_lines(struct dec_sub *sub);
void sub_reset(struct dec_sub *sub);
void sub_select(struct dec_sub *sub, bool selected);
void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink);
+1
View File
@@ -52,6 +52,7 @@ struct sd_functions {
int format, double pts);
char *(*get_text)(struct sd *sd, double pts, enum sd_text_type type);
struct sd_times (*get_times)(struct sd *sd, double pts);
struct sub_lines *(*get_lines)(struct sd *sd);
};
// lavc_conv.c
+39
View File
@@ -1017,6 +1017,44 @@ static void uninit(struct sd *sd)
talloc_free(ctx->copy_cache);
}
static struct sub_lines *get_lines(struct sd *sd)
{
struct sd_ass_priv *ctx = sd->priv;
ASS_Track *track = ctx->ass_track;
struct sub_lines *res = talloc_zero(NULL, struct sub_lines);
for (int i = 0; i < track->n_events; i++) {
ASS_Event *event = &track->events[i];
if (!event->Text)
continue;
char *plain = NULL;
bstr result = sd_ass_to_plaintext(&plain, event->Text);
// ASS subtitle lines can have many empty lines after stripping tags,
// but empty lines are useful in LRC.
if (is_whitespace_only(result)) {
talloc_free(plain);
if (!strcmp(sd->codec->codec, "ass"))
continue;
plain = talloc_strdup(res, "");
} else {
talloc_steal(res, plain);
}
struct sub_line line = {
.text = plain,
.start = event->Start / 1000.0,
.end = event->Duration == UNKNOWN_DURATION * 1000
? MP_NOPTS_VALUE
: (event->Start + event->Duration) / 1000.0,
};
MP_TARRAY_APPEND(res, res->entries, res->num_entries, line);
}
return res;
}
static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
{
struct sd_ass_priv *ctx = sd->priv;
@@ -1072,6 +1110,7 @@ const struct sd_functions sd_ass = {
.get_bitmaps = get_bitmaps,
.get_text = get_text,
.get_times = get_times,
.get_lines = get_lines,
.control = control,
.reset = reset,
.select = enable_output,