From 91b6d8fdd12401c5e06d9864db0ee9d502c51ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kacper=20Michaj=C5=82ow?= Date: Mon, 27 Apr 2026 02:09:34 +0200 Subject: [PATCH] demux: enrich edition titles with codec/resolution/bitrate info Extract the codec descriptor out of mp_format_track_metadata into a shared demux_append_codec_desc() helper, and use it from a new demux_compose_edition_title() composer that both demux_timeline.c and demux_lavf.c call when building editions. Codec block is only added when the program has exactly one stream of that kind, and fields already present in the prefix (e.g. WxH from yt-dlp's format label) are skipped to avoid duplication. --- demux/demux.c | 70 ++++++++++++++++++++++++++++++++++++++++++ demux/demux.h | 14 +++++++++ demux/demux_lavf.c | 59 +++++++++++++++-------------------- demux/demux_timeline.c | 19 ++++++------ player/misc.c | 24 +++------------ 5 files changed, 122 insertions(+), 64 deletions(-) diff --git a/demux/demux.c b/demux/demux.c index 8ce1a0a877..892107d0f8 100644 --- a/demux/demux.c +++ b/demux/demux.c @@ -1249,6 +1249,76 @@ const char *stream_type_name(enum stream_type type) } } +static void append_field(void *ta_ctx, bstr *dst, const char *skip_dup, + const char *value) +{ + if (skip_dup && skip_dup[0] && strstr(skip_dup, value)) + return; + bstr_xappend_asprintf(ta_ctx, dst, " %s", value); +} + +void demux_append_codec_desc(void *ta_ctx, bstr *dst, struct sh_stream *sh, + const char *skip_dup) +{ + struct mp_codec_params *c = sh->codec; + + bstr_xappend0(ta_ctx, dst, c->codec ? c->codec : ""); + + if (c->codec_profile) + bstr_xappend_asprintf(ta_ctx, dst, " [%s]", c->codec_profile); + if (c->disp_w) + append_field(ta_ctx, dst, skip_dup, + mp_tprintf(32, "%dx%d", c->disp_w, c->disp_h)); + if (c->fps && !sh->image) { + char *fps = mp_format_double(ta_ctx, c->fps, 4, false, false, true); + append_field(ta_ctx, dst, skip_dup, mp_tprintf(32, "%s fps", fps)); + } + if (c->channels.num) + bstr_xappend_asprintf(ta_ctx, dst, " %dch", c->channels.num); + if (c->samplerate) + bstr_xappend_asprintf(ta_ctx, dst, " %d Hz", c->samplerate); + int bitrate = 0; + if (c->bitrate > 0 && c->bitrate < INT_MAX - 500) { + bitrate = (c->bitrate + 500) / 1000; + } else if (sh->hls_bitrate > 0 && sh->hls_bitrate < INT_MAX - 500) { + bitrate = (sh->hls_bitrate + 500) / 1000; + } + if (bitrate) + append_field(ta_ctx, dst, skip_dup, mp_tprintf(32, "%d kbps", bitrate)); +} + +char *demux_compose_edition_title(void *ta_ctx, struct demuxer *demuxer, + int program_id, const char *prefix) +{ + struct sh_stream *vsh = NULL, *ash = NULL; + int video_count = 0, audio_count = 0; + int num = demux_get_num_stream(demuxer); + for (int i = 0; i < num; i++) { + struct sh_stream *s = demux_get_stream(demuxer, i); + if (s->program_id != program_id) + continue; + if (s->type == STREAM_VIDEO) { video_count++; vsh = s; } + else if (s->type == STREAM_AUDIO) { audio_count++; ash = s; } + } + + bstr dst = {0}; + if (prefix && prefix[0]) + bstr_xappend0(ta_ctx, &dst, prefix); + if (video_count == 1) { + bstr_xappend0(ta_ctx, &dst, dst.len ? " (" : "("); + demux_append_codec_desc(ta_ctx, &dst, vsh, prefix); + bstr_xappend0(ta_ctx, &dst, ")"); + } + if (audio_count == 1) { + bstr_xappend0(ta_ctx, &dst, dst.len ? " (" : "("); + demux_append_codec_desc(ta_ctx, &dst, ash, prefix); + bstr_xappend0(ta_ctx, &dst, ")"); + } + if (!dst.len) + return NULL; + return bstrto0(ta_ctx, dst); +} + static struct sh_stream *demuxer_get_cc_track_locked(struct sh_stream *stream) { struct sh_stream *sh = stream->ds->cc; diff --git a/demux/demux.h b/demux/demux.h index fb290e9d6b..5596261a40 100644 --- a/demux/demux.h +++ b/demux/demux.h @@ -370,4 +370,18 @@ bool demux_matroska_uid_cmp(struct matroska_segment_uid *a, const char *stream_type_name(enum stream_type type); +// Append a codec descriptor for stream `sh` to output string `dst` +// Fields that are unset are skipped. If `skip_dup` is non-NULL, fields whose +// formatted value already appears in it are skipped too. +void demux_append_codec_desc(void *ta_ctx, bstr *dst, struct sh_stream *sh, + const char *skip_dup); + +// Compose a descriptive edition title. Walks the demuxer's streams matching +// `program_id`, and when exactly one video / one audio stream is present, +// appends a parenthesized codec descriptor for it. When multiple tracks of a +// a type are present, no descriptor is appended. Returns NULL if +// nothing useful would be produced; otherwise a talloc'd string. +char *demux_compose_edition_title(void *ta_ctx, struct demuxer *demuxer, + int program_id, const char *prefix); + #endif /* MPLAYER_DEMUXER_H */ diff --git a/demux/demux_lavf.c b/demux/demux_lavf.c index 414c127f8d..d9e0a21485 100644 --- a/demux/demux_lavf.c +++ b/demux/demux_lavf.c @@ -995,57 +995,48 @@ static void build_editions(demuxer_t *demuxer) }; mp_tags_copy_from_av_dictionary(ed.metadata, prog->metadata); - // AVProgram is unlikely to have a "title" tag, but just in case, - // don't override it. - char *title = mp_tags_get_str(ed.metadata, "title"); - if (title) - goto done; - - char *service_name = mp_tags_get_str(ed.metadata, "service_name"); - if (service_name) { - mp_tags_set_str(ed.metadata, "title", service_name); - goto done; - } - - // If program/variant has a single video (or audio) stream, use the - // "comment" metadata as the name. - char *rendition_name = NULL; int video_count = 0, audio_count = 0; int video_idx = -1, audio_idx = -1; for (unsigned j = 0; j < prog->nb_stream_indexes; j++) { unsigned idx = prog->stream_index[j]; if (idx >= priv->num_streams || !priv->streams[idx]->sh) continue; - if (priv->streams[idx]->sh->type == STREAM_VIDEO) { + struct sh_stream *sh = priv->streams[idx]->sh; + if (sh->type == STREAM_VIDEO) { video_count++; video_idx = idx; - } else if (priv->streams[idx]->sh->type == STREAM_AUDIO) { + } else if (sh->type == STREAM_AUDIO) { audio_count++; audio_idx = idx; } } - int name_idx = video_count == 1 ? video_idx : audio_count == 1 ? audio_idx : -1; - if (name_idx >= 0) - rendition_name = mp_tags_get_str(priv->streams[name_idx]->sh->tags, "comment"); - if (rendition_name) { - mp_tags_set_str(ed.metadata, "title", rendition_name); - goto done; + const char *prefix = mp_tags_get_str(ed.metadata, "title"); + if (!prefix) + prefix = mp_tags_get_str(ed.metadata, "service_name"); + if (!prefix) { + int name_idx = video_count == 1 ? video_idx + : audio_count == 1 ? audio_idx : -1; + if (name_idx >= 0) + prefix = mp_tags_get_str(priv->streams[name_idx]->sh->tags, "comment"); } - - char *vb = mp_tags_get_str(ed.metadata, "variant_bitrate"); - char *end; - double rate = vb ? strtoll(vb, &end, 10) : 0; - if (rate > 0 && *end == '\0') { - rate /= 1000.0; - if (rate < 1000) { - mp_tags_set_str(ed.metadata, "title", mp_tprintf(42, "Bitrate: %.f kbps", rate)); - } else { - mp_tags_set_str(ed.metadata, "title", mp_tprintf(42, "Bitrate: %.3f Mbps", rate / 1000.0)); + if (!prefix) { + char *vb = mp_tags_get_str(ed.metadata, "variant_bitrate"); + char *end; + double rate = vb ? strtoll(vb, &end, 10) : 0; + if (rate > 0 && *end == '\0') { + rate /= 1000.0; + prefix = rate < 1000 + ? mp_tprintf(42, "Bitrate: %.f kbps", rate) + : mp_tprintf(42, "Bitrate: %.3f Mbps", rate / 1000.0); } } -done: + char *title = demux_compose_edition_title(demuxer, demuxer, + prog->id, prefix); + if (title) + mp_tags_set_str(ed.metadata, "title", title); + MP_TARRAY_APPEND(demuxer, demuxer->editions, demuxer->num_editions, ed); } diff --git a/demux/demux_timeline.c b/demux/demux_timeline.c index 4d3cb2a41d..00804c9c0c 100644 --- a/demux/demux_timeline.c +++ b/demux/demux_timeline.c @@ -667,22 +667,21 @@ static void build_editions(struct demuxer *demuxer) .metadata = talloc_zero(demuxer, struct mp_tags), }; - // Use title from the first video stream in this program as edition - // title, falling back to any stream with a title. - const char *fallback_title = NULL; + const char *prefix = NULL; + bool prefix_is_video = false; for (int i = n; i < num_streams; i++) { struct sh_stream *s = demux_get_stream(demuxer, i); if (s->program_id != sh->program_id || !s->title) continue; - if (s->type == STREAM_VIDEO) { - fallback_title = s->title; - break; + if (!prefix || (!prefix_is_video && s->type == STREAM_VIDEO)) { + prefix = s->title; + prefix_is_video = s->type == STREAM_VIDEO; } - if (!fallback_title) - fallback_title = s->title; } - if (fallback_title) - mp_tags_set_str(ed.metadata, "title", fallback_title); + char *title = demux_compose_edition_title(demuxer, demuxer, + sh->program_id, prefix); + if (title) + mp_tags_set_str(ed.metadata, "title", title); MP_TARRAY_APPEND(demuxer, demuxer->editions, demuxer->num_editions, ed); } diff --git a/player/misc.c b/player/misc.c index 6cb16938d2..e6e3fc2e89 100644 --- a/player/misc.c +++ b/player/misc.c @@ -366,31 +366,15 @@ char *mp_format_track_metadata(void *ctx, struct track *t, bool add_lang) if (t->title) bstr_xappend_asprintf(ctx, &dst, "'%s' ", t->title); - const char *codec = s ? s->codec->codec : NULL; - bstr_xappend0(ctx, &dst, "("); if (add_lang && t->lang) bstr_xappend_asprintf(ctx, &dst, "%s ", t->lang); - bstr_xappend0(ctx, &dst, codec ? codec : ""); - - if (s && s->codec->codec_profile) - bstr_xappend_asprintf(ctx, &dst, " [%s]", s->codec->codec_profile); - if (s && s->codec->disp_w) - bstr_xappend_asprintf(ctx, &dst, " %dx%d", s->codec->disp_w, s->codec->disp_h); - if (s && s->codec->fps && !t->image) { - char *fps = mp_format_double(ctx, s->codec->fps, 4, false, false, true); - bstr_xappend_asprintf(ctx, &dst, " %s fps", fps); - } - if (s && s->codec->channels.num) - bstr_xappend_asprintf(ctx, &dst, " %dch", s->codec->channels.num); - if (s && s->codec->samplerate) - bstr_xappend_asprintf(ctx, &dst, " %d Hz", s->codec->samplerate); - if (s && s->codec->bitrate > 0 && s->codec->bitrate < INT_MAX - 500) { - bstr_xappend_asprintf(ctx, &dst, " %d kbps", (s->codec->bitrate + 500) / 1000); - } else if (s && s->hls_bitrate > 0 && s->hls_bitrate < INT_MAX - 500) { - bstr_xappend_asprintf(ctx, &dst, " %d kbps", (s->hls_bitrate + 500) / 1000); + if (s) { + demux_append_codec_desc(ctx, &dst, s, NULL); + } else { + bstr_xappend0(ctx, &dst, ""); } bstr_xappend0(ctx, &dst, ")");