stream: auto-detect .iso DVD-Video, DVD-Audio and Blu-ray images

Passing a disc image directly (e.g. `mpv disc.iso`) previously fell
through to FFmpeg. Route it to the matching disc backend instead,
like the VIDEO_TS.IFO/AUDIO_TS.IFO/BDMV redirectors already do.
This commit is contained in:
Kacper Michajłow committed 2026-08-26 01:47:41 +02:00
1 parent fee9b14d46
commit 02ec593037
6 files changed
+99 -21

No files matched your search

+10
View File
@@ -1336,6 +1336,9 @@ PROTOCOLS
Play a Blu-ray disc. Since libbluray 1.0.1, you can read from ISO files
by passing them to ``--bluray-device``.
A Blu-ray ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``title`` can be: ``longest`` or ``first`` (selects the default
playlist); ``menu`` (starts in the disc menu, see ``--disc-menu`` and the
``discnav`` command); ``mpls/<number>`` (selects <number>.mpls playlist);
@@ -1352,6 +1355,9 @@ PROTOCOLS
it will probably try to open an actual optical drive, if available and
implemented for the OS.
A DVD-Video ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened.
``dvdnav://`` is an old alias for ``dvd://`` and does exactly the same
thing.
@@ -1365,6 +1371,10 @@ PROTOCOLS
with its tracks, are exposed as a video track and shown by default. Can be
disabled with ``--vid=no``.
A DVD-Audio ``.iso`` image passed directly (e.g. ``mpv disc.iso``) is also
detected and opened. For hybrid discs, the DVD-Audio zone is preferred over
the DVD-Video zone.
``dvb://[cardnumber@]channel`` ``--dvbin-...``
Digital TV via DVB. (Linux only.)
+1
View File
@@ -368,6 +368,7 @@ static int stream_create_instance(const stream_info_t *sinfo,
s->log = mp_log_new(s, s->global->log, sinfo->name);
}
s->info = sinfo;
s->autoprobed = !args->sinfo;
s->cancel = args->cancel;
s->url = talloc_strdup(s, url);
s->path = talloc_strdup(s, path);
+2
View File
@@ -255,6 +255,8 @@ typedef struct stream {
bool is_regular : 1; // regular file
bool access_references : 1; // open other streams
bool allow_partial_read : 1; // allows partial read with stream_read_file()
bool autoprobed : 1; // opened by the autoprobe loop, not explicitly
// requested, failures should stay quiet
struct mp_log *log;
struct mpv_global *global;
+21 -2
View File
@@ -119,6 +119,7 @@ struct bd_overlay_plane {
struct bluray_priv_s {
BLURAY *bd;
struct mp_log *bluray_log;
bool probing; // open is an .iso auto-detection probe
BLURAY_TITLE_INFO *title_info;
int num_titles;
int current_angle;
@@ -1047,9 +1048,11 @@ static bool check_disc_info(stream_t *s)
// check Blu-ray
if (!info->bluray_detected) {
MP_ERR(s, "Given stream is not a Blu-ray.\n");
if (!b->probing)
MP_ERR(s, "Given stream is not a Blu-ray.\n");
return false;
}
b->probing = false;
// check AACS
if (info->aacs_detected) {
@@ -1159,7 +1162,8 @@ static int bluray_stream_open_internal(stream_t *s)
BLURAY *bd = bd_open(device_tmp, NULL);
talloc_free(device_tmp);
if (!bd) {
MP_ERR(s, "Couldn't open Blu-ray device: %s\n", device);
if (!b->probing)
MP_ERR(s, "Couldn't open Blu-ray device: %s\n", device);
ret = STREAM_UNSUPPORTED;
goto err;
}
@@ -1385,6 +1389,21 @@ static int bdmv_dir_stream_open(stream_t *stream)
if (!path)
goto unsupported;
// Hand the .iso to libbluray as the device. Opening validates the disc
// info, so it doubles as the probe (see priv->probing).
if (bstr_case_endswith(bstr0(path), bstr0(".iso"))) {
priv->cfg_device = talloc_strdup(priv, path);
priv->probing = stream->autoprobed;
int r = bluray_stream_open_internal(stream);
if (r != STREAM_OK) {
if (priv->probing)
goto unsupported;
return r;
}
MP_INFO(stream, "Blu-ray ISO image detected. Redirecting to bluray://\n");
return r;
}
// We allow the path to point to a directory containing BDMV/, a
// directory containing MovieObject.bdmv, or that file itself.
if (!check_bdmv(path)) {
+34 -10
View File
@@ -101,6 +101,8 @@ struct priv {
mp_mutex still_lock; // guards the still-picture state below
int page; // forced still page (--dvda-page), -1 = time-based
char *device;
struct mp_log *lib_log; // used by libdvdread's log callback
bool probing; // open is an .iso auto-detection probe
struct dvda_opts *opts;
};
@@ -225,11 +227,13 @@ static bool read_disc_structure(stream_t *stream)
ifo_handle_t *amg = ifoOpenVMGI(priv->dvd);
if (!amg || amg->ifo_format != IFO_AUDIO || !amg->amgi_mat) {
MP_ERR(stream, "Could not read AUDIO_TS.IFO.\n");
if (!priv->probing)
MP_ERR(stream, "Could not read AUDIO_TS.IFO.\n");
if (amg)
ifoClose(amg);
return false;
}
priv->probing = false;
int num_ats = amg->amgi_mat->amg_nr_of_title_sets;
ifoClose(amg);
@@ -583,9 +587,10 @@ static void stream_dvda_close(stream_t *stream)
DVDClose(priv->dvd);
}
static void dvda_log(void *priv, dvd_logger_level_t level,
static void dvda_log(void *p, dvd_logger_level_t level,
const char *fmt, va_list va)
{
struct priv *priv = p;
int lvl;
switch (level) {
case DVD_LOGGER_LEVEL_ERROR: lvl = MSGL_ERR; break;
@@ -594,10 +599,12 @@ static void dvda_log(void *priv, dvd_logger_level_t level,
case DVD_LOGGER_LEVEL_INFO:
default: lvl = MSGL_V; break;
}
if (!mp_msg_test(priv, lvl))
if (priv->probing)
lvl = MPMAX(lvl, MSGL_V);
if (!mp_msg_test(priv->lib_log, lvl))
return;
mp_msg_va(priv, lvl, fmt, va);
mp_msg(priv, lvl, "\n");
mp_msg_va(priv->lib_log, lvl, fmt, va);
mp_msg(priv->lib_log, lvl, "\n");
}
static int open_s_internal(stream_t *stream)
@@ -622,16 +629,18 @@ static int open_s_internal(stream_t *stream)
if (!path)
goto err;
struct mp_log *log = mp_log_new(stream, stream->log, "/libdvdread");
priv->lib_log = mp_log_new(stream, stream->log, "/libdvdread");
const dvd_logger_cb logger_cb = { .pf_log = dvda_log };
priv->dvd = DVDOpenAudio(log, &logger_cb, path);
priv->dvd = DVDOpenAudio(priv, &logger_cb, path);
if (!priv->dvd) {
MP_ERR(stream, "Couldn't open DVD-Audio device: %s\n", path);
if (!priv->probing)
MP_ERR(stream, "Couldn't open DVD-Audio device: %s\n", path);
goto err;
}
if (!read_disc_structure(stream)) {
MP_ERR(stream, "No DVD-Audio titles found: %s\n", path);
if (!priv->probing)
MP_ERR(stream, "No DVD-Audio titles found: %s\n", path);
goto err;
}
@@ -666,7 +675,7 @@ static int open_s_internal(stream_t *stream)
err:
stream_dvda_close(stream);
return STREAM_ERROR;
return priv->probing ? STREAM_UNSUPPORTED : STREAM_ERROR;
}
static int open_s(stream_t *stream)
@@ -742,6 +751,21 @@ static int ifo_dvda_stream_open(stream_t *stream)
if (!path)
goto unsupported;
// Hand the .iso to libdvdread as the device. Opening validates the AMG
// IFO, so it doubles as the probe (see priv->probing).
if (bstr_case_endswith(bstr0(path), bstr0(".iso"))) {
priv->device = talloc_strdup(priv, path);
priv->probing = stream->autoprobed;
int r = open_s_internal(stream);
if (r != STREAM_OK) {
if (priv->probing)
goto unsupported;
return r;
}
MP_INFO(stream, "DVD-Audio ISO image detected. Redirecting to dvda://\n");
return r;
}
// We allow the path to point to a directory containing AUDIO_TS/, a
// directory containing AUDIO_TS.IFO, or that file itself.
if (!check_ifo(path)) {
+31 -9
View File
@@ -66,6 +66,8 @@
struct priv {
dvdnav_t *dvdnav; // handle to libdvdnav stuff
struct mp_log *log; // borrowed from the stream
struct mp_log *lib_log; // used by libdvdnav's log callback
bool probing; // open is an .iso auto-detection probe
char *filename; // path
int64_t duration; // in 90 kHz PTS ticks
int title;
@@ -1023,9 +1025,10 @@ static void stream_dvdnav_close(stream_t *s)
mp_mutex_destroy(&priv->lock);
}
static void dvdnav_log(void *priv, dvdnav_logger_level_t level,
static void dvdnav_log(void *p, dvdnav_logger_level_t level,
const char *fmt, va_list va)
{
struct priv *priv = p;
int lvl;
switch (level) {
case DVDNAV_LOGGER_LEVEL_ERROR: lvl = MSGL_ERR; break;
@@ -1034,17 +1037,20 @@ static void dvdnav_log(void *priv, dvdnav_logger_level_t level,
case DVDNAV_LOGGER_LEVEL_INFO:
default: lvl = MSGL_V; break;
}
if (!mp_msg_test(priv, lvl))
if (priv->probing)
lvl = MPMAX(lvl, MSGL_V);
if (!mp_msg_test(priv->lib_log, lvl))
return;
mp_msg_va(priv, lvl, fmt, va);
mp_msg(priv, lvl, "\n");
mp_msg_va(priv->lib_log, lvl, fmt, va);
mp_msg(priv->lib_log, lvl, "\n");
}
static dvdnav_status_t nav_open(stream_t *stream, dvdnav_t **dest, const char *path)
{
struct mp_log *log = mp_log_new(stream, stream->log, "/libdvdnav");
struct priv *priv = stream->priv;
priv->lib_log = mp_log_new(stream, stream->log, "/libdvdnav");
const dvdnav_logger_cb logger_cb = { .pf_log = dvdnav_log };
return dvdnav_open2(dest, log, &logger_cb, path);
return dvdnav_open2(dest, priv, &logger_cb, path);
}
static struct priv *new_dvdnav_stream(stream_t *stream, char *filename)
@@ -1099,11 +1105,12 @@ static int open_s_internal(stream_t *stream)
else
filename = DEFAULT_OPTICAL_DEVICE;
if (!new_dvdnav_stream(stream, filename)) {
MP_ERR(stream, "Couldn't open DVD device: %s\n",
filename);
ret = STREAM_ERROR;
if (!priv->probing)
MP_ERR(stream, "Couldn't open DVD device: %s\n", filename);
ret = priv->probing ? STREAM_UNSUPPORTED : STREAM_ERROR;
goto err;
}
priv->probing = false;
int32_t num_titles = 0;
dvdnav_get_number_of_titles(priv->dvdnav, &num_titles);
@@ -1233,6 +1240,21 @@ static int ifo_dvdnav_stream_open(stream_t *stream)
if (!path)
goto unsupported;
// Hand the .iso to libdvdnav as the device. Opening validates the VMG
// IFO, so it doubles as the probe (see priv->probing).
if (bstr_case_endswith(bstr0(path), bstr0(".iso"))) {
priv->device = talloc_strdup(priv, path);
priv->probing = stream->autoprobed;
int r = open_s_internal(stream);
if (r != STREAM_OK) {
if (priv->probing)
goto unsupported;
return r;
}
MP_INFO(stream, "DVD ISO image detected. Redirecting to dvd://\n");
return r;
}
// We allow the path to point to a directory containing VIDEO_TS/, a
// directory containing VIDEO_TS.IFO, or that file itself.
if (!check_ifo(path)) {