Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions DOCS/man/input.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3951,6 +3951,12 @@ Property list
somewhat weird form (apparently "hex BCD"), indicating the release version
of the libass library linked to mpv.

``subrandr-version``
The value of ``sbr_library_version()`` as a string in the format
``<major>.<minor>.<patch>``, indicating the release version of the subrandr
library at runtime. This property is unavailable if mpv is not compiled
with subrandr enabled.

``platform``
Returns a string describing what target platform mpv was built for. The value
of this is dependent on what the underlying build system detects. Some of the
Expand Down
10 changes: 10 additions & 0 deletions ci/build-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,14 @@ common_args="--werror \
-Dtests=true \
"

build_subrandr() {
local target="$2"
local prefix="$1"

git clone --depth=1 https://github.com/afishhh/subrandr.git
pushd subrandr
cargo xtask install ${target:+--target} $target --prefix "$prefix"
popd
}

export CFLAGS="$CFLAGS -Wno-error=deprecated -Wno-error=deprecated-declarations -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3"
10 changes: 9 additions & 1 deletion ci/build-mingw64.sh
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ _luajit () {
}
_luajit_mark=lib/libluajit-5.1.a

_subrandr () {
RUSTFLAGS="-L$prefix_dir/lib" build_subrandr "$prefix_dir" "$RUST_TARGET"
}
_subrandr_mark=lib/libsubrandr.dll.a

for x in iconv zlib shaderc spirv-cross nv-headers dav1d lcms2; do
build_if_missing $x
done
Expand All @@ -296,6 +301,9 @@ fi
for x in ffmpeg libplacebo freetype fribidi harfbuzz libass luajit; do
build_if_missing $x
done
if [[ "$TARGET" != "i686-"* ]]; then
build_if_missing subrandr
fi

## mpv

Expand Down Expand Up @@ -330,7 +338,7 @@ if [ "$2" = pack ]; then
pushd artifact/tmp
dlls=(
libgcc_*.dll lib{ssp,stdc++,winpthread}-[0-9]*.dll # compiler runtime
av*.dll sw*.dll postproc-[0-9]*.dll lib{ass,freetype,fribidi,harfbuzz,iconv,placebo}-[0-9]*.dll
av*.dll sw*.dll {postproc,subrandr}-[0-9]*.dll lib{ass,freetype,fribidi,harfbuzz,iconv,placebo}-[0-9]*.dll
lib{shaderc_shared,spirv-cross-c-shared,dav1d,lcms2}.dll zlib1.dll
)
if [[ -f vulkan-1.dll ]]; then
Expand Down
15 changes: 12 additions & 3 deletions ci/build-msys2.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,18 @@ args=(
-D{egl-angle-lib,egl-angle-win32,pdf-build,rubberband,win32-smtc}=enabled
)

[[ "$SYS" == "clang64" ]] && args+=(
-Db_sanitize=address,undefined
)
if [[ "$SYS" == "clang64" ]]; then
args+=(
-Db_sanitize=address,undefined
)
else
# currently building with subrandr on clang64+asan
# causes a weird crash (https://github.com/msys2/MINGW-packages/issues/25267)
echo "::group::Building subrandr"
build_subrandr "/$SYS"
echo "::endgroup::"
args+=(-Dsubrandr=enabled)
fi

meson setup build $common_args "${args[@]}"
meson compile -C build
Expand Down
1 change: 1 addition & 0 deletions ci/build-tumbleweed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ meson setup build $common_args $@ \
-Dlibarchive=enabled \
-Dmanpage-build=enabled \
-Dpipewire=enabled \
-Dsubrandr=enabled \
-Dvulkan=enabled
meson compile -C build
./build/mpv -v --no-config
6 changes: 6 additions & 0 deletions demux/demux.c
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ extern const demuxer_desc_t demuxer_desc_directory;
extern const demuxer_desc_t demuxer_desc_disc;
extern const demuxer_desc_t demuxer_desc_rar;
extern const demuxer_desc_t demuxer_desc_libarchive;
#if HAVE_SUBRANDR
extern const demuxer_desc_t demuxer_desc_sbr;
#endif
extern const demuxer_desc_t demuxer_desc_null;
extern const demuxer_desc_t demuxer_desc_timeline;

Expand All @@ -76,6 +79,9 @@ static const demuxer_desc_t *const demuxer_list[] = {
&demuxer_desc_matroska,
#if HAVE_LIBARCHIVE
&demuxer_desc_libarchive,
#endif
#if HAVE_SUBRANDR
&demuxer_desc_sbr,
#endif
&demuxer_desc_lavf,
&demuxer_desc_mf,
Expand Down
173 changes: 173 additions & 0 deletions demux/demux_sbr.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* This file is part of mpv.
*
* mpv is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* mpv is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with mpv. If not, see <http://www.gnu.org/licenses/>.
*/

// This demuxer is specific to the `sd_sbr` subtitle driver and exists
// so that we can take subtitle files like .srv3 or .vtt in their
// full text form and pass them to subrandr in the subtitle driver.
//
// There are two reasons for this:
// - subrandr doesn't currently support parsing packetized streams like
// what is output by ffmpeg for WebVTT.
// - Demuxing .srv3 is not supported by ffmpeg, it also doesn't really have
// a standard packetized representation that ffmpeg could demux it to
// (that I know of).
//
// Note that for now this demuxer only recognizes srv3, this is to avoid
// regressing the behavior of other formats that were previously rendered
// with libass via ffmpeg conversion.

#include <limits.h>
#include <math.h>

#include <subrandr/subrandr.h>

#include "common/common.h"
#include "demux/packet.h"
#include "misc/bstr.h"
#include "options/m_config.h"
#include "options/m_option.h"
#include "stream/stream.h"
#include "demux.h"

#define OPT_BASE_STRUCT struct demux_sbr_opts
struct demux_sbr_opts {
int probesize;
};

const struct m_sub_options demux_sbr_conf = {
.opts = (const m_option_t[]) {
{"probesize", OPT_INT(probesize), M_RANGE(32, INT_MAX)},
{0}
},
.size = sizeof(struct demux_sbr_opts),
.defaults = &(const struct demux_sbr_opts){
.probesize = 128,
},
.change_flags = UPDATE_DEMUXER,
};

struct format_codec_info {
const char *codec;
const char *codec_desc;
};

static const struct format_codec_info fmt_to_codec[] = {
[SBR_SUBTITLE_FORMAT_UNKNOWN] = {NULL, NULL},
[SBR_SUBTITLE_FORMAT_SRV3 ] = {"subrandr/srv3", "srv3"},
};

struct sub_codec_ext {
const char *ext;
struct format_codec_info codec_info;
};

static const struct sub_codec_ext codec_exts[] = {
{".srv3", fmt_to_codec[SBR_SUBTITLE_FORMAT_SRV3]},
{".ytt", fmt_to_codec[SBR_SUBTITLE_FORMAT_SRV3]},
{NULL}
};

struct demux_sbr_priv {
bstr content;
bool exhausted;
};

static int demux_open_sbr(struct demuxer *demuxer, enum demux_check check)
{
bstr filename = bstr0(demuxer->filename);
struct format_codec_info codec_info = {0};
struct demux_sbr_opts *opts = mp_get_config_group(demuxer, demuxer->global, &demux_sbr_conf);

for (const struct sub_codec_ext *ext = codec_exts; ext->ext; ++ext) {
if (bstr_endswith0(filename, ext->ext))
codec_info = ext->codec_info;
}

if (!codec_info.codec) {
int probe_size = stream_peek(demuxer->stream, opts->probesize);
uint8_t *probe_buffer = demuxer->stream->buffer;

sbr_subtitle_format fmt = sbr_probe_text((const char *)probe_buffer, (size_t)probe_size);
if (fmt < MP_ARRAY_SIZE(fmt_to_codec))
codec_info = fmt_to_codec[fmt];
}

if (check != DEMUX_CHECK_REQUEST && !codec_info.codec)
return -1;

struct demux_sbr_priv *priv = talloc_zero(demuxer, struct demux_sbr_priv);

priv->content = stream_read_complete(demuxer->stream, priv, 64 * 1024 * 1024);
if (priv->content.start == NULL)
return -1;
demuxer->priv = priv;

struct sh_stream *stream = demux_alloc_sh_stream(STREAM_SUB);
stream->codec->codec = codec_info.codec;
stream->codec->codec_desc = codec_info.codec_desc;
demux_add_sh_stream(demuxer, stream);

// Note that while in practice seeking on this stream is not possible,
// if `demuxer->seekable` is `false` then a warning is emitted when one
// tries to seek in the player which is undesirable. Therefore we mark
// it as seekable and make seeking a no-op instead.
demuxer->seekable = true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so this should be false according to the comment in demux_seek_sbr?

Copy link
Author

@afishhh afishhh Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #16271 (comment). Should I add a comment here explaining why this is done this way or is there a way to avoid that warning?

Specifically this warning:

mpv/demux/demux.c

Lines 3842 to 3845 in b9ceaf2

if (!in->d_thread->seekable) {
MP_WARN(in, "Cannot seek in this file.\n");
return false;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a short comment please.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already extended the comment in demux_seek_sbr to mention this, but I can move it here if that's what you want.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should be next to the relevant thing IMO

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved and slightly changed the comment
can you chime in on #16271 (comment) and let me know what the preferred approach would be?

demuxer->fully_read = true;
demux_close_stream(demuxer);

return 0;
}

static bool demux_read_packet_sbr(struct demuxer *demuxer, struct demux_packet **packet)
{
struct demux_sbr_priv *priv = demuxer->priv;

if (priv->exhausted)
return false;

*packet = new_demux_packet_from(demuxer->packet_pool, priv->content.start, priv->content.len);
if (!*packet)
return true;

(*packet)->stream = 0;
(*packet)->pts = 0.0;
(*packet)->sub_duration = INFINITY;
priv->exhausted = true;

return true;
}

static void demux_seek_sbr(struct demuxer *demuxer, double seek_pts, int flags)
{
// We only ever emit one packet, no seeking needed or possible.
}

static void demux_switched_tracks_sbr(struct demuxer *demuxer)
{
struct demux_sbr_priv *ctx = demuxer->priv;
ctx->exhausted = false;
}


const struct demuxer_desc demuxer_desc_sbr = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nice to document the reason why this demuxer exists in a short comment in this file or so.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note that I did this with the earlier push at the top of the file, was that fine or do I need to change anything?

.name = "subrandr",
.desc = "subrandr text subtitle demuxer",
.open = demux_open_sbr,
.read_packet = demux_read_packet_sbr,
.seek = demux_seek_sbr,
.switched_tracks = demux_switched_tracks_sbr
};
7 changes: 7 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,13 @@ if features['libdl']
dependencies += libdl
endif

subrandr = dependency('subrandr', version: '>= 0.1.1', required: get_option('subrandr'))
features += {'subrandr': subrandr.found()}
if features['subrandr']
sources += files('demux/demux_sbr.c', 'sub/sd_sbr.c')
dependencies += subrandr
endif

# C11 atomics are mandatory but linking to the library is not always required.
dependencies += cc.find_library('atomic', required: false)

Expand Down
1 change: 1 addition & 0 deletions meson.options
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ option('pthread-debug', type: 'feature', value: 'disabled', description: 'pthrea
option('rubberband', type: 'feature', value: 'auto', description: 'librubberband support')
option('sdl2', type: 'feature', value: 'disabled', description: 'SDL2')
option('sdl2-gamepad', type: 'feature', value: 'auto', description: 'SDL2 gamepad input')
option('subrandr', type: 'feature', value: 'auto', description: 'subrandr support (SRV3 and WebVTT subtitle renderer)')
option('uchardet', type: 'feature', value: 'auto', description: 'uchardet support')
option('uwp', type: 'feature', value: 'disabled', description: 'Universal Windows Platform')
option('vapoursynth', type: 'feature', value: 'auto', description: 'VapourSynth filter bridge')
Expand Down
8 changes: 8 additions & 0 deletions options/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ extern const struct m_sub_options demux_rawvideo_conf;
extern const struct m_sub_options demux_playlist_conf;
extern const struct m_sub_options demux_lavf_conf;
extern const struct m_sub_options demux_mkv_conf;
#if HAVE_SUBRANDR
extern const struct m_sub_options demux_sbr_conf;
#endif
extern const struct m_sub_options vd_lavc_conf;
extern const struct m_sub_options ad_lavc_conf;
extern const struct m_sub_options hwdec_conf;
Expand Down Expand Up @@ -693,6 +696,9 @@ static const m_option_t mp_opts[] = {
{"demuxer-rawvideo", OPT_SUBSTRUCT(demux_rawvideo, demux_rawvideo_conf)},
{"", OPT_SUBSTRUCT(demux_playlist, demux_playlist_conf)},
{"demuxer-mkv", OPT_SUBSTRUCT(demux_mkv, demux_mkv_conf)},
#if HAVE_SUBRANDR
{"demuxer-sbr", OPT_SUBSTRUCT(demux_sbr, demux_sbr_conf)},
#endif

// ------------------------- subtitles options --------------------

Expand Down Expand Up @@ -1085,13 +1091,15 @@ static const struct MPOpts mp_default_opts = {
"scc",
"smi",
"srt",
"srv3",
"ssa",
"sub",
"sup",
"utf",
"utf-8",
"utf8",
"vtt",
"ytt",
NULL
},

Expand Down
3 changes: 3 additions & 0 deletions options/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ typedef struct MPOpts {
struct demux_playlist_opts *demux_playlist;
struct demux_lavf_opts *demux_lavf;
struct demux_mkv_opts *demux_mkv;
#if HAVE_SUBRANDR
struct demux_sbr_opts *demux_sbr;
#endif

struct demux_opts *demux_opts;
struct demux_cache_opts *demux_cache_opts;
Expand Down
19 changes: 19 additions & 0 deletions player/command.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@
#include <math.h>
#include <sys/types.h>

#include "config.h" // for HAVE_SUBRANDR

#include <ass/ass.h>
#if HAVE_SUBRANDR
#include <subrandr/subrandr.h>
#endif
#include <libavutil/avstring.h>
#include <libavutil/common.h>
#include <libavutil/timecode.h>
Expand Down Expand Up @@ -3748,6 +3753,19 @@ static int mp_property_libass_version(void *ctx, struct m_property *prop,
return m_property_int64_ro(action, arg, ass_library_version());
}

static int mp_property_subrandr_version(void *ctx, struct m_property *prop,
int action, void *arg)
{
#if HAVE_SUBRANDR
uint32_t major, minor, patch;
sbr_library_version(&major, &minor, &patch);
const char *result = mp_tprintf(33, "%" PRIu32 ".%" PRIu32 ".%" PRIu32, major, minor, patch);
return m_property_strdup_ro(action, arg, result);
#else
return M_PROPERTY_UNAVAILABLE;
#endif
}

static int mp_property_platform(void *ctx, struct m_property *prop,
int action, void *arg)
{
Expand Down Expand Up @@ -4531,6 +4549,7 @@ static const struct m_property mp_properties_base[] = {
{"mpv-configuration", mp_property_configuration},
{"ffmpeg-version", mp_property_ffmpeg},
{"libass-version", mp_property_libass_version},
{"subrandr-version", mp_property_subrandr_version},
{"platform", mp_property_platform},

{"options", mp_property_options},
Expand Down
Loading
Loading