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 authored and Kacper Michajłow committed 2026-04-25 19:58:52 +02:00
1 parent c662594f05
commit 5f02e1b8a2
7 files changed
+142 -2

No files matched your search

+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`` ``secondary-sub-end``
Same as ``sub-end``, but for the secondary subtitles. 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) ``playlist-pos`` (RW)
Current position on playlist. The first entry is on position 0. Writing to Current position on playlist. The first entry is on position 0. Writing to
this property may start playback at the new position. 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); 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, static int mp_property_playlist_current_pos(void *ctx, struct m_property *prop,
int action, void *arg) int action, void *arg)
{ {
@@ -4650,6 +4687,10 @@ static const struct m_property mp_properties_base[] = {
.priv = (void *)&(const int){0}}, .priv = (void *)&(const int){0}},
{"secondary-sub-end", mp_property_sub_end, {"secondary-sub-end", mp_property_sub_end,
.priv = (void *)&(const int){1}}, .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}, {"vf", mp_property_vf},
{"af", mp_property_af}, {"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", "secondary-sub-text", "audio-bitrate", "video-bitrate", "sub-bitrate",
"decoder-frame-drop-count", "frame-drop-count", "video-frame-info", "decoder-frame-drop-count", "frame-drop-count", "video-frame-info",
"vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start", "vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start",
"secondary-sub-end", "video-out-params", "video-dec-params", "video-params", "secondary-sub-end", "sub-lines", "secondary-sub-lines", "video-out-params",
"deinterlace-active", "video-target-params"), "video-dec-params", "video-params", "deinterlace-active", "video-target-params"),
E(MP_EVENT_DURATION_UPDATE, "duration"), E(MP_EVENT_DURATION_UPDATE, "duration"),
E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params", E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params",
"video-format", "video-codec", "video-bitrate", "dwidth", "dheight", "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); mp_mutex_unlock(&sub->lock);
return ret; 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; 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 dec_sub *sub_create(struct mpv_global *global, struct track *track,
struct attachment_list *attachments, int order); struct attachment_list *attachments, int order);
void sub_destroy(struct dec_sub *sub); 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_get_text(struct dec_sub *sub, double pts, enum sd_text_type type);
char *sub_ass_get_extradata(struct dec_sub *sub); char *sub_ass_get_extradata(struct dec_sub *sub);
struct sd_times sub_get_times(struct dec_sub *sub, double pts); 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_reset(struct dec_sub *sub);
void sub_select(struct dec_sub *sub, bool selected); void sub_select(struct dec_sub *sub, bool selected);
void sub_set_recorder_sink(struct dec_sub *sub, struct mp_recorder_sink *sink); 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); int format, double pts);
char *(*get_text)(struct sd *sd, double pts, enum sd_text_type type); char *(*get_text)(struct sd *sd, double pts, enum sd_text_type type);
struct sd_times (*get_times)(struct sd *sd, double pts); struct sd_times (*get_times)(struct sd *sd, double pts);
struct sub_lines *(*get_lines)(struct sd *sd);
}; };
// lavc_conv.c // lavc_conv.c
+39
View File
@@ -1017,6 +1017,44 @@ static void uninit(struct sd *sd)
talloc_free(ctx->copy_cache); 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) static int control(struct sd *sd, enum sd_ctrl cmd, void *arg)
{ {
struct sd_ass_priv *ctx = sd->priv; struct sd_ass_priv *ctx = sd->priv;
@@ -1072,6 +1110,7 @@ const struct sd_functions sd_ass = {
.get_bitmaps = get_bitmaps, .get_bitmaps = get_bitmaps,
.get_text = get_text, .get_text = get_text,
.get_times = get_times, .get_times = get_times,
.get_lines = get_lines,
.control = control, .control = control,
.reset = reset, .reset = reset,
.select = enable_output, .select = enable_output,