summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--demux/demux.c2
-rw-r--r--demux/demux_disc.c347
-rwxr-xr-xold-configure10
-rw-r--r--options/options.c11
-rw-r--r--options/options.h6
-rw-r--r--player/command.c133
-rw-r--r--player/configfiles.c6
-rw-r--r--player/core.h9
-rw-r--r--player/discnav.c334
-rw-r--r--player/loadfile.c7
-rw-r--r--player/misc.c4
-rw-r--r--player/playloop.c2
-rw-r--r--stream/cache.c30
-rw-r--r--stream/stream.c15
-rw-r--r--stream/stream.h9
-rw-r--r--stream/stream_bluray.c831
-rw-r--r--stream/stream_dvd.c968
-rw-r--r--stream/stream_dvdnav.c755
-rw-r--r--sub/osd.c11
-rw-r--r--sub/osd.h8
-rw-r--r--wscript13
-rw-r--r--wscript_build.py6
22 files changed, 3517 insertions, 0 deletions
diff --git a/demux/demux.c b/demux/demux.c
index b7f184c161..e716194614 100644
--- a/demux/demux.c
+++ b/demux/demux.c
@@ -52,12 +52,14 @@ extern const demuxer_desc_t demuxer_desc_lavf;
extern const demuxer_desc_t demuxer_desc_libass;
extern const demuxer_desc_t demuxer_desc_subreader;
extern const demuxer_desc_t demuxer_desc_playlist;
+extern const demuxer_desc_t demuxer_desc_disc;
/* Please do not add any new demuxers here. If you want to implement a new
* demuxer, add it to libavformat, except for wrappers around external
* libraries and demuxers requiring binary support. */
const demuxer_desc_t *const demuxer_list[] = {
+ &demuxer_desc_disc,
&demuxer_desc_edl,
&demuxer_desc_cue,
&demuxer_desc_rawaudio,
diff --git a/demux/demux_disc.c b/demux/demux_disc.c
new file mode 100644
index 0000000000..06cea65d1a
--- /dev/null
+++ b/demux/demux_disc.c
@@ -0,0 +1,347 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+#include <assert.h>
+
+#include "common/common.h"
+#include "common/msg.h"
+
+#include "stream/stream.h"
+#include "demux.h"
+#include "stheader.h"
+
+#include "video/csputils.h"
+
+struct priv {
+ struct demuxer *slave;
+ // streams[slave_stream_index] == our_stream
+ struct sh_stream **streams;
+ int num_streams;
+ // This contains each DVD sub stream, or NULL. Needed because DVD packets
+ // can come arbitrarily late in the MPEG stream, so the slave demuxer
+ // might add the streams only later.
+ struct sh_stream *dvd_subs[32];
+ // Used to rewrite the raw MPEG timestamps to playback time.
+ struct {
+ double base_time; // playback display start time of current segment
+ double base_dts; // packet DTS that maps to base_time
+ double last_dts; // DTS of previously demuxed packet
+ } pts[STREAM_TYPE_COUNT];
+ double seek_pts;
+ bool seek_reinit; // needs reinit after seek
+};
+
+static void reselect_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ for (int n = 0; n < MPMIN(p->slave->num_streams, p->num_streams); n++) {
+ if (p->streams[n]) {
+ demuxer_select_track(p->slave, p->slave->streams[n],
+ demux_stream_is_selected(p->streams[n]));
+ }
+ }
+}
+
+static void get_disc_lang(struct stream *stream, struct sh_stream *sh)
+{
+ struct stream_lang_req req = {.type = sh->type, .id = sh->demuxer_id};
+ if (stream->uncached_type == STREAMTYPE_DVD && sh->type == STREAM_SUB)
+ req.id = req.id & 0x1F; // mpeg ID to index
+ stream_control(stream, STREAM_CTRL_GET_LANG, &req);
+ if (req.name[0])
+ sh->lang = talloc_strdup(sh, req.name);
+}
+
+static void add_dvd_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ struct stream *stream = demuxer->stream;
+ if (stream->uncached_type != STREAMTYPE_DVD)
+ return;
+ struct stream_dvd_info_req info;
+ if (stream_control(stream, STREAM_CTRL_GET_DVD_INFO, &info) > 0) {
+ for (int n = 0; n < MPMIN(32, info.num_subs); n++) {
+ struct sh_stream *sh = new_sh_stream(demuxer, STREAM_SUB);
+ if (!sh)
+ break;
+ sh->demuxer_id = n + 0x20;
+ sh->codec = "dvd_subtitle";
+ get_disc_lang(stream, sh);
+ // p->streams _must_ match with p->slave->streams, so we can't add
+ // it yet - it has to be done when the real stream appears, which
+ // could be right on start, or any time later.
+ p->dvd_subs[n] = sh;
+
+ // emulate the extradata
+ struct mp_csp_params csp = MP_CSP_PARAMS_DEFAULTS;
+ csp.int_bits_in = 8;
+ csp.int_bits_out = 8;
+ float cmatrix[3][4];
+ mp_get_yuv2rgb_coeffs(&csp, cmatrix);
+
+ char *s = talloc_strdup(sh, "");
+ s = talloc_asprintf_append(s, "palette: ");
+ for (int i = 0; i < 16; i++) {
+ int color = info.palette[i];
+ int c[3] = {(color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff};
+ mp_map_int_color(cmatrix, 8, c);
+ color = (c[2] << 16) | (c[1] << 8) | c[0];
+
+ if (i != 0)
+ s = talloc_asprintf_append(s, ", ");
+ s = talloc_asprintf_append(s, "%06x", color);
+ }
+ s = talloc_asprintf_append(s, "\n");
+
+ sh->sub->extradata = s;
+ sh->sub->extradata_len = strlen(s);
+ }
+ }
+}
+
+static void add_streams(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ for (int n = p->num_streams; n < p->slave->num_streams; n++) {
+ struct sh_stream *src = p->slave->streams[n];
+ if (src->sub) {
+ struct sh_stream *sub = NULL;
+ if (src->demuxer_id >= 0x20 && src->demuxer_id <= 0x3F)
+ sub = p->dvd_subs[src->demuxer_id - 0x20];
+ if (sub) {
+ assert(p->num_streams == n); // directly mapped
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, sub);
+ continue;
+ }
+ }
+ struct sh_stream *sh = new_sh_stream(demuxer, src->type);
+ assert(p->num_streams == n); // directly mapped
+ MP_TARRAY_APPEND(p, p->streams, p->num_streams, sh);
+ // Copy all stream fields that might be relevant
+ sh->codec = talloc_strdup(sh, src->codec);
+ sh->format = src->format;
+ sh->lav_headers = src->lav_headers;
+ if (sh && src->video) {
+ double ar;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_ASPECT_RATIO, &ar)
+ == STREAM_OK)
+ sh->video->aspect = ar;
+ }
+ if (sh && src->audio)
+ sh->audio = src->audio;
+ }
+ reselect_streams(demuxer);
+}
+
+static void d_seek(demuxer_t *demuxer, float rel_seek_secs, int flags)
+{
+ struct priv *p = demuxer->priv;
+
+ if (demuxer->stream->uncached_type == STREAMTYPE_CDDA) {
+ demux_seek(p->slave, rel_seek_secs, flags);
+ return;
+ }
+
+ double pts = p->seek_pts;
+ if (flags & SEEK_ABSOLUTE)
+ pts = 0.0f;
+
+ if (flags & SEEK_FACTOR) {
+ double tmp = 0;
+ stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &tmp);
+ pts += tmp * rel_seek_secs;
+ } else {
+ pts += rel_seek_secs;
+ }
+
+ MP_VERBOSE(demuxer, "seek to: %f\n", pts);
+
+ stream_control(demuxer->stream, STREAM_CTRL_SEEK_TO_TIME, &pts);
+ demux_control(p->slave, DEMUXER_CTRL_RESYNC, NULL);
+
+ p->seek_pts = pts;
+ p->seek_reinit = true;
+}
+
+static void reset_pts(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ double base;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_CURRENT_TIME, &base) < 1)
+ base = 0;
+
+ MP_VERBOSE(demuxer, "reset to time: %f\n", base);
+
+ for (int n = 0; n < STREAM_TYPE_COUNT; n++) {
+ p->pts[n].base_dts = p->pts[n].last_dts = MP_NOPTS_VALUE;
+ p->pts[n].base_time = base;
+ }
+
+ p->seek_reinit = false;
+}
+
+static int d_fill_buffer(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+
+ struct demux_packet *pkt = demux_read_any_packet(p->slave);
+ if (!pkt)
+ return 0;
+
+ if (p->seek_reinit)
+ reset_pts(demuxer);
+
+ add_streams(demuxer);
+ if (pkt->stream >= p->num_streams) { // out of memory?
+ talloc_free(pkt);
+ return 0;
+ }
+
+ struct sh_stream *sh = p->streams[pkt->stream];
+ if (!demux_stream_is_selected(sh)) {
+ talloc_free(pkt);
+ return 1;
+ }
+
+ int t = sh->type;
+
+ // Subtitle timestamps are not continuous, so the heuristic below can't be
+ // applied. Instead, use the video stream as reference.
+ if (t == STREAM_SUB)
+ t = STREAM_VIDEO;
+
+ MP_TRACE(demuxer, "ipts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
+
+ if (sh->type == STREAM_SUB) {
+ if (p->pts[t].base_dts == MP_NOPTS_VALUE)
+ MP_WARN(demuxer, "subtitle packet along PTS reset, report a bug\n");
+ } else if (pkt->dts != MP_NOPTS_VALUE) {
+ if (p->pts[t].base_dts == MP_NOPTS_VALUE)
+ p->pts[t].base_dts = p->pts[t].last_dts = pkt->dts;
+
+ if (pkt->dts < p->pts[t].last_dts || pkt->dts > p->pts[t].last_dts + 0.5)
+ {
+ MP_VERBOSE(demuxer, "PTS discontinuity on stream %d\n", sh->type);
+ p->pts[t].base_time += p->pts[t].last_dts - p->pts[t].base_dts;
+ p->pts[t].base_dts = p->pts[t].last_dts = pkt->dts - pkt->duration;
+ }
+ p->pts[t].last_dts = pkt->dts;
+ }
+ if (p->pts[t].base_dts != MP_NOPTS_VALUE) {
+ double delta = -p->pts[t].base_dts + p->pts[t].base_time;
+ if (pkt->pts != MP_NOPTS_VALUE)
+ pkt->pts += delta;
+ if (pkt->dts != MP_NOPTS_VALUE)
+ pkt->dts += delta;
+ }
+
+ MP_TRACE(demuxer, "opts: %d %f %f\n", sh->type, pkt->pts, pkt->dts);
+
+ if (pkt->pts != MP_NOPTS_VALUE)
+ p->seek_pts = pkt->pts;
+
+ demux_add_packet(sh, pkt);
+ return 1;
+}
+
+static void add_stream_chapters(struct demuxer *demuxer)
+{
+ int num = 0;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_CHAPTERS, &num) < 1)
+ return;
+ for (int n = 0; n < num; n++) {
+ double p = n;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_CHAPTER_TIME, &p) < 1)
+ continue;
+ demuxer_add_chapter(demuxer, bstr0(""), p * 1e9, 0, 0);
+ }
+}
+
+static int d_open(demuxer_t *demuxer, enum demux_check check)
+{
+ struct priv *p = demuxer->priv = talloc_zero(demuxer, struct priv);
+
+ if (check != DEMUX_CHECK_FORCE)
+ return -1;
+
+ char *demux = "+lavf";
+ if (demuxer->stream->uncached_type == STREAMTYPE_CDDA)
+ demux = "+rawaudio";
+
+ // Initialize the playback time. We need to read _some_ data to get the
+ // correct stream-layer time (at least with libdvdnav).
+ stream_peek(demuxer->stream, 1);
+ reset_pts(demuxer);
+
+ p->slave = demux_open(demuxer->stream, demux, NULL, demuxer->global);
+ if (!p->slave)
+ return -1;
+
+ // So that we don't miss initial packets of delayed subtitle streams.
+ p->slave->stream_select_default = true;
+
+ // Can be seekable even if the stream isn't.
+ demuxer->seekable = true;
+
+ add_dvd_streams(demuxer);
+ add_streams(demuxer);
+ add_stream_chapters(demuxer);
+
+ return 0;
+}
+
+static void d_close(demuxer_t *demuxer)
+{
+ struct priv *p = demuxer->priv;
+ free_demuxer(p->slave);
+}
+
+static int d_control(demuxer_t *demuxer, int cmd, void *arg)
+{
+ struct priv *p = demuxer->priv;
+
+ switch (cmd) {
+ case DEMUXER_CTRL_GET_TIME_LENGTH: {
+ double len;
+ if (stream_control(demuxer->stream, STREAM_CTRL_GET_TIME_LENGTH, &len) < 1)
+ break;
+ *(double *)arg = len;
+ return DEMUXER_CTRL_OK;
+ }
+ case DEMUXER_CTRL_RESYNC:
+ demux_flush(p->slave);
+ break; // relay to slave demuxer
+ case DEMUXER_CTRL_SWITCHED_TRACKS:
+ reselect_streams(demuxer);
+ return DEMUXER_CTRL_OK;
+ }
+ return demux_control(p->slave, cmd, arg);
+}
+
+const demuxer_desc_t demuxer_desc_disc = {
+ .name = "disc",
+ .desc = "CD/DVD/BD wrapper",
+ .fill_buffer = d_fill_buffer,
+ .open = d_open,
+ .close = d_close,
+ .seek = d_seek,
+ .control = d_control,
+ .type = DEMUXER_TYPE_DISC,
+};
diff --git a/old-configure b/old-configure
index 8de6ac7eda..a57be03a56 100755
--- a/old-configure
+++ b/old-configure
@@ -179,6 +179,9 @@ options_state_machine() {
opt_yes_no _libquvi4 "libquvi 0.4.x"
opt_yes_no _libquvi9 "libquvi 0.9.x"
opt_yes_no _lcms2 "LCMS2 support"
+ opt_yes_no _bluray "Blu-ray support"
+ opt_yes_no _dvdread "libdvdread"
+ opt_yes_no _dvdnav "libdvdnav"
opt_yes_no _enca "ENCA charset oracle library"
opt_yes_no _libass "subtitle rendering with libass"
opt_yes_no _libpostproc "postprocess filter (vf_pp)"
@@ -727,6 +730,12 @@ check_pkg_config "OpenAL" $_openal OPENAL 'openal >= 1.13'
check_pkg_config "ALSA audio" $_alsa ALSA 'alsa >= 1.0.9'
+check_pkg_config "Blu-ray support" $_bluray LIBBLURAY 'libbluray >= 0.2.1'
+
+check_pkg_config "dvdread" $_dvdread DVDREAD 'dvdread >= 4.1.0'
+
+check_pkg_config "dvdnav" $_dvdnav DVDNAV 'dvdnav >= 4.2.0'
+
check_pkg_config "libcdio" $_libcdio CDDA 'libcdio_paranoia'
_oldass=$_libass
@@ -952,6 +961,7 @@ cat > $TMPC << EOF
#define HAVE_SDL1 0
#define DEFAULT_CDROM_DEVICE "/dev/cdrom"
+#define DEFAULT_DVD_DEVICE "/dev/dvd"
#define PATH_DEV_DSP "/dev/dsp"
#define PATH_DEV_MIXER "/dev/mixer"
diff --git a/options/options.c b/options/options.c
index 21adaf76c4..84eda8db64 100644
--- a/options/options.c
+++ b/options/options.c
@@ -147,9 +147,18 @@ const m_option_t mp_opts[] = {
({"no", 0})),
OPT_INTRANGE("cache-pause-restart", stream_cache_unpause, 0, 0, 0x7fffffff),
+#if HAVE_DVDREAD || HAVE_DVDNAV
+ OPT_STRING("dvd-device", dvd_device, 0),
+ OPT_INT("dvd-speed", dvd_speed, 0),
+ OPT_INTRANGE("dvd-angle", dvd_angle, 0, 1, 99),
+#endif /* HAVE_DVDREAD */
OPT_INTPAIR("chapter", chapterrange, 0),
OPT_CHOICE_OR_INT("edition", edition_id, 0, 0, 8190,
({"auto", -1})),
+#if HAVE_LIBBLURAY
+ OPT_STRING("bluray-device", bluray_device, 0),
+ OPT_INTRANGE("bluray-angle", bluray_angle, 0, 0, 999),
+#endif /* HAVE_LIBBLURAY */
OPT_STRINGLIST("http-header-fields", network_http_header_fields, 0),
OPT_STRING("user-agent", network_useragent, 0),
@@ -631,6 +640,8 @@ const struct MPOpts mp_default_opts = {
.index_mode = 1,
+ .dvd_angle = 1,
+
.mf_fps = 1.0,
};
diff --git a/options/options.h b/options/options.h
index 9cd5f800d9..100fded4a3 100644
--- a/options/options.h
+++ b/options/options.h
@@ -251,6 +251,12 @@ typedef struct MPOpts {
struct dvb_params *stream_dvb_opts;
char *cdrom_device;
+ int dvd_title;
+ int dvd_angle;
+ int dvd_speed;
+ char *dvd_device;
+ int bluray_angle;
+ char *bluray_device;
double mf_fps;
char *mf_type;
diff --git a/player/command.c b/player/command.c
index facc890e8a..fffa63bb70 100644
--- a/player/command.c
+++ b/player/command.c
@@ -232,6 +232,13 @@ static int mp_property_media_title(void *ctx, struct m_property *prop,
name = demux_info_get(mpctx->master_demuxer, "title");
if (name && name[0])
return m_property_strdup_ro(action, arg, name);
+ struct stream *stream = mpctx->master_demuxer->stream;
+ if (stream_control(stream, STREAM_CTRL_GET_DISC_NAME, &name) > 0
+ && name) {
+ int r = m_property_strdup_ro(action, arg, name);
+ talloc_free(name);
+ return r;
+ }
}
return mp_property_filename(ctx, prop, action, arg);
}
@@ -478,6 +485,48 @@ static int mp_property_playback_time(void *ctx, struct m_property *prop,
return property_time(action, arg, get_playback_time(mpctx));
}
+/// Current BD/DVD title (RW)
+static int mp_property_disc_title(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer || !demuxer->stream)
+ return M_PROPERTY_UNAVAILABLE;
+ struct stream *stream = demuxer->stream;
+ unsigned int title = -1;
+ switch (action) {
+ case M_PROPERTY_GET:
+ if (stream_control(stream, STREAM_CTRL_GET_CURRENT_TITLE, &title) <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+ *(int*)arg = title;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = (struct m_option){
+ .type = CONF_TYPE_INT,
+ .flags = M_OPT_MIN,
+ .min = -1,
+ };
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ title = *(int*)arg;
+ if (stream_control(stream, STREAM_CTRL_SET_CURRENT_TITLE, &title) <= 0)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_disc_menu(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ int state = mp_nav_in_menu(mpctx);
+ if (state < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_flag_ro(action, arg, !!state);
+}
+
/// Current chapter (RW)
static int mp_property_chapter(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -757,6 +806,19 @@ static int mp_property_quvi_format(void *ctx, struct m_property *prop,
return mp_property_generic_option(mpctx, prop, action, arg);
}
+/// Number of titles in BD/DVD
+static int mp_property_disc_titles(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ unsigned int num_titles;
+ if (!demuxer || stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_TITLES,
+ &num_titles) < 1)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(action, arg, num_titles);
+}
+
/// Number of chapters in file
static int mp_property_chapters(void *ctx, struct m_property *prop,
int action, void *arg)
@@ -780,6 +842,69 @@ static int mp_property_editions(void *ctx, struct m_property *prop,
return m_property_int_ro(action, arg, demuxer->num_editions);
}
+/// Current dvd angle (RW)
+static int mp_property_angle(void *ctx, struct m_property *prop,
+ int action, void *arg)
+{
+ MPContext *mpctx = ctx;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ int ris, angles = -1, angle = 1;
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_GET_NUM_ANGLES, &angles);
+ if (ris == STREAM_UNSUPPORTED)
+ return M_PROPERTY_UNAVAILABLE;
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_GET_ANGLE, &angle);
+ if (ris == STREAM_UNSUPPORTED)
+ return -1;
+
+ if (angle < 0 || angles <= 1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = angle;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ *(char **) arg = talloc_asprintf(NULL, "%d/%d", angle, angles);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ angle = *(int *)arg;
+ if (angle < 0 || angle > angles)
+ return M_PROPERTY_ERROR;
+ demux_flush(demuxer);
+
+ ris = stream_control(demuxer->stream, STREAM_CTRL_SET_ANGLE, &angle);
+ if (ris != STREAM_OK)
+ return M_PROPERTY_ERROR;
+
+ demux_control(demuxer, DEMUXER_CTRL_RESYNC, NULL);
+
+ if (mpctx->d_video)
+ video_reset_decoding(mpctx->d_video);
+
+ if (mpctx->d_audio)
+ audio_reset_decoding(mpctx->d_audio);
+
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 1,
+ .max = angles,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
static int get_tag_entry(int item, int action, void *arg, void *ctx)
{
struct mp_tags *tags = ctx;
@@ -2525,11 +2650,15 @@ static const struct m_property mp_properties[] = {
{"time-remaining", mp_property_remaining},
{"playtime-remaining", mp_property_playtime_remaining},
{"playback-time", mp_property_playback_time},
+ {"disc-title", mp_property_disc_title},
+ {"disc-menu-active", mp_property_disc_menu},
{"chapter", mp_property_chapter},
{"edition", mp_property_edition},
{"quvi-format", mp_property_quvi_format},
+ {"disc-titles", mp_property_disc_titles},
{"chapters", mp_property_chapters},
{"editions", mp_property_editions},
+ {"angle", mp_property_angle},
{"metadata", mp_property_metadata},
{"chapter-metadata", mp_property_chapter_metadata},
{"vf-metadata", mp_property_vf_metadata},
@@ -3651,6 +3780,10 @@ int run_command(MPContext *mpctx, mp_cmd_t *cmd)
mp_input_disable_section(mpctx->input, cmd->args[0].v.s);
break;
+ case MP_CMD_DISCNAV:
+ mp_nav_user_input(mpctx, cmd->args[0].v.s);
+ break;
+
case MP_CMD_VO_CMDLINE:
if (mpctx->video_out) {
char *s = cmd->args[0].v.s;
diff --git a/player/configfiles.c b/player/configfiles.c
index b05184c5fb..d1c79c9c9d 100644
--- a/player/configfiles.c
+++ b/player/configfiles.c
@@ -173,6 +173,7 @@ void mp_load_auto_profiles(struct MPContext *mpctx)
static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
const char *fname)
{
+ struct MPOpts *opts = global->opts;
char *res = NULL;
void *tmp = talloc_new(NULL);
const char *realpath = fname;
@@ -183,6 +184,11 @@ static char *mp_get_playback_resume_config_filename(struct mpv_global *global,
goto exit;
realpath = mp_path_join(tmp, bstr0(cwd), bstr0(fname));
}
+ if (bstr_startswith0(bfname, "dvd://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->dvd_device);
+ if (bstr_startswith0(bfname, "br://") || bstr_startswith0(bfname, "bd://") ||
+ bstr_startswith0(bfname, "bluray://"))
+ realpath = talloc_asprintf(tmp, "%s - %s", realpath, opts->bluray_device);
uint8_t md5[16];
av_md5_sum(md5, realpath, strlen(realpath));
char *conf = talloc_strdup(tmp, "");
diff --git a/player/core.h b/player/core.h
index 856e970945..84b6123c1c 100644
--- a/player/core.h
+++ b/player/core.h
@@ -350,6 +350,7 @@ typedef struct MPContext {
struct screenshot_ctx *screenshot_ctx;
struct command_ctx *command_ctx;
struct encode_lavc_context *encode_lavc_ctx;
+ struct mp_nav_state *nav_state;
} MPContext;
// audio.c
@@ -370,6 +371,14 @@ void mp_write_watch_later_conf(struct MPContext *mpctx);
struct playlist_entry *mp_check_playlist_resume(struct MPContext *mpctx,
struct playlist *playlist);
+// discnav.c
+void mp_nav_init(struct MPContext *mpctx);
+void mp_nav_reset(struct MPContext *mpctx);
+void mp_nav_destroy(struct MPContext *mpctx);
+void mp_nav_user_input(struct MPContext *mpctx, char *command);
+void mp_handle_nav(struct MPContext *mpctx);
+int mp_nav_in_menu(struct MPContext *mpctx);
+
// loadfile.c
void uninit_player(struct MPContext *mpctx, unsigned int mask);
struct track *mp_add_subtitles(struct MPContext *mpctx, char *filename);
diff --git a/player/discnav.c b/player/discnav.c
new file mode 100644
index 0000000000..0b52fed479
--- /dev/null
+++ b/player/discnav.c
@@ -0,0 +1,334 @@
+/*
+ * This file is part of mpv.
+ *
+ * mpv is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with mpv. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <limits.h>
+#include <pthread.h>
+#include <assert.h>
+
+#include "core.h"
+#include "command.h"
+
+#include "common/msg.h"
+#include "common/common.h"
+#include "input/input.h"
+
+#include "stream/discnav.h"
+
+#include "sub/dec_sub.h"
+#include "sub/osd.h"
+
+#include "video/mp_image.h"
+#include "video/decode/dec_video.h"
+
+struct mp_nav_state {
+ struct mp_log *log;
+
+ int nav_still_frame;
+ bool nav_eof;
+ bool nav_menu;
+ bool nav_draining;
+
+ // Accessed by OSD (possibly separate thread)
+ // Protected by the given lock
+ pthread_mutex_t osd_lock;
+ int hi_visible;
+ int highlight[4]; // x0 y0 x1 y1
+ int vidsize[2];
+ int subsize[2];
+ struct sub_bitmap *hi_elem;
+ struct sub_bitmap *overlays[2];
+ struct sub_bitmap outputs[3];
+};
+
+static inline bool is_valid_size(int size[2])
+{
+ return size[0] >= 1 && size[1] >= 1;
+}
+
+static void update_resolution(struct MPContext *mpctx)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ int size[2] = {0};
+ if (mpctx->d_sub[0])
+ sub_control(mpctx->d_sub[0], SD_CTRL_GET_RESOLUTION, size);
+ if (!is_valid_size(size)) {
+ struct mp_image_params vid = {0};
+ if (mpctx->d_video)
+ vid = mpctx->d_video->decoder_output;
+ size[0] = vid.w;
+ size[1] = vid.h;
+ }
+ pthread_mutex_lock(&nav->osd_lock);
+ nav->vidsize[0] = size[0];
+ nav->vidsize[1] = size[1];
+ pthread_mutex_unlock(&nav->osd_lock);
+}
+
+// Send update events and such.
+static void update_state(struct MPContext *mpctx)
+{
+ mp_notify_property(mpctx, "disc-menu-active");
+}
+
+// Return 1 if in menu, 0 if in video, or <0 if no navigation possible.
+int mp_nav_in_menu(struct MPContext *mpctx)
+{
+ return mpctx->nav_state ? mpctx->nav_state->nav_menu : -1;
+}
+
+// Allocate state and enable navigation features. Must happen before
+// initializing cache, because the cache would read data. Since stream_dvdnav is
+// in a mode which skips all transitions on reading data (before enabling
+// navigation), this would skip some menu screens.
+void mp_nav_init(struct MPContext *mpctx)
+{
+ assert(!mpctx->nav_state);
+
+ // dvdnav is interactive
+ if (mpctx->encode_lavc_ctx)
+ return;
+
+ struct mp_nav_cmd inp = {MP_NAV_CMD_ENABLE};
+ if (stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp) < 1)
+ return;
+
+ mpctx->nav_state = talloc_zero(NULL, struct mp_nav_state);
+ mpctx->nav_state->log = mp_log_new(mpctx->nav_state, mpctx->log, "discnav");
+ pthread_mutex_init(&mpctx->nav_state->osd_lock, NULL);
+
+ MP_VERBOSE(mpctx->nav_state, "enabling\n");
+
+ mp_input_enable_section(mpctx->input, "discnav", 0);
+ mp_input_set_section_mouse_area(mpctx->input, "discnav-menu",
+ INT_MIN, INT_MIN, INT_MAX, INT_MAX);
+
+ update_state(mpctx);
+}
+
+void mp_nav_reset(struct MPContext *mpctx)
+{
+ struct mp_nav_state *nav = mpctx->nav_state;
+ if (!nav)
+ return;
+ struct mp_nav_cmd inp = {MP_NAV_CMD_RESUME};
+ stream_control(mpctx->stream, STREAM_CTRL_NAV_CMD, &inp);
+ osd_set_nav_highlight(mpctx->osd, NULL);
+ nav->hi_visible = 0;
+ nav->nav_menu = false;
+ nav->nav_draining = false;
+ nav->n