summaryrefslogtreecommitdiffstats
path: root/mpvcore/player/command.c
diff options
context:
space:
mode:
Diffstat (limited to 'mpvcore/player/command.c')
-rw-r--r--mpvcore/player/command.c3019
1 files changed, 3019 insertions, 0 deletions
diff --git a/mpvcore/player/command.c b/mpvcore/player/command.c
new file mode 100644
index 0000000000..f1a2453ca9
--- /dev/null
+++ b/mpvcore/player/command.c
@@ -0,0 +1,3019 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer 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.
+ *
+ * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdlib.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <time.h>
+
+#include <libavutil/avstring.h>
+#include <libavutil/common.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "command.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/input/input.h"
+#include "stream/stream.h"
+#include "demux/demux.h"
+#include "demux/stheader.h"
+#include "mpvcore/resolve.h"
+#include "mpvcore/playlist.h"
+#include "mpvcore/playlist_parser.h"
+#include "sub/sub.h"
+#include "sub/dec_sub.h"
+#include "mpvcore/m_option.h"
+#include "mpvcore/m_property.h"
+#include "mpvcore/m_config.h"
+#include "video/filter/vf.h"
+#include "video/decode/vd.h"
+#include "mp_osd.h"
+#include "video/out/vo.h"
+#include "video/csputils.h"
+#include "audio/mixer.h"
+#include "audio/out/ao.h"
+#include "audio/filter/af.h"
+#include "video/decode/dec_video.h"
+#include "audio/decode/dec_audio.h"
+#include "mpvcore/path.h"
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#include "stream/pvr.h"
+#ifdef CONFIG_DVBIN
+#include "stream/dvbin.h"
+#endif
+#include "screenshot.h"
+#ifdef HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+
+#include "mp_core.h"
+#include "mp_lua.h"
+
+struct command_ctx {
+ int events;
+
+#define OVERLAY_MAX_ID 64
+ void *overlay_map[OVERLAY_MAX_ID];
+};
+
+static int edit_filters(struct MPContext *mpctx, enum stream_type mediatype,
+ const char *cmd, const char *arg);
+static int set_filters(struct MPContext *mpctx, enum stream_type mediatype,
+ struct m_obj_settings *new_chain);
+
+static char *format_bitrate(int rate)
+{
+ return talloc_asprintf(NULL, "%d kbps", rate * 8 / 1000);
+}
+
+static char *format_delay(double time)
+{
+ return talloc_asprintf(NULL, "%d ms", ROUND(time * 1000));
+}
+
+// Property-option bridge.
+static int mp_property_generic_option(struct m_option *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ char *optname = prop->priv;
+ struct m_config_option *opt = m_config_get_co(mpctx->mconfig,
+ bstr0(optname));
+ void *valptr = opt->data;
+
+ switch (action) {
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)arg = *(opt->opt);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ m_option_copy(opt->opt, arg, valptr);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ m_option_copy(opt->opt, valptr, arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Playback speed (RW)
+static int mp_property_playback_speed(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ double orig_speed = opts->playback_speed;
+ switch (action) {
+ case M_PROPERTY_SET: {
+ opts->playback_speed = *(double *) arg;
+ // Adjust time until next frame flip for nosound mode
+ mpctx->time_frame *= orig_speed / opts->playback_speed;
+ if (mpctx->sh_audio)
+ reinit_audio_chain(mpctx);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "x %6.2f", orig_speed);
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// filename with path (RO)
+static int mp_property_path(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->filename)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, mpctx->filename);
+}
+
+static int mp_property_filename(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->filename)
+ return M_PROPERTY_UNAVAILABLE;
+ char *filename = talloc_strdup(NULL, mpctx->filename);
+ if (mp_is_url(bstr0(filename)))
+ mp_url_unescape_inplace(filename);
+ char *f = (char *)mp_basename(filename);
+ int r = m_property_strdup_ro(prop, action, arg, f[0] ? f : filename);
+ talloc_free(filename);
+ return r;
+}
+
+static int mp_property_media_title(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ char *name = NULL;
+ if (mpctx->resolve_result)
+ name = mpctx->resolve_result->title;
+ if (name && name[0])
+ return m_property_strdup_ro(prop, action, arg, name);
+ if (mpctx->master_demuxer) {
+ name = demux_info_get(mpctx->master_demuxer, "title");
+ if (name && name[0])
+ return m_property_strdup_ro(prop, action, arg, name);
+ }
+ return mp_property_filename(prop, action, arg, mpctx);
+}
+
+static int mp_property_stream_path(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream || !stream->url)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, stream->url);
+}
+
+static int mp_property_stream_capture(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->stream)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (action == M_PROPERTY_SET) {
+ char *filename = *(char **)arg;
+ stream_set_capture_file(mpctx->stream, filename);
+ // fall through to mp_property_generic_option
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Demuxer name (RO)
+static int mp_property_demuxer(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_strdup_ro(prop, action, arg, demuxer->desc->name);
+}
+
+/// Position in the stream (RW)
+static int mp_property_stream_pos(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int64_t *) arg = stream_tell(stream);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ stream_seek(stream, *(int64_t *) arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Stream start offset (RO)
+static int mp_property_stream_start(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg, stream->start_pos);
+}
+
+/// Stream end offset (RO)
+static int mp_property_stream_end(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg, stream->end_pos);
+}
+
+/// Stream length (RO)
+static int mp_property_stream_length(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct stream *stream = mpctx->stream;
+ if (!stream)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int64_ro(prop, action, arg,
+ stream->end_pos - stream->start_pos);
+}
+
+// Does some magic to handle "<name>/full" as time formatted with milliseconds.
+// Assumes prop is the type of the actual property.
+static int property_time(m_option_t *prop, int action, void *arg, double time)
+{
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(double *)arg = time;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_KEY_ACTION: {
+ struct m_property_action_arg *ka = arg;
+
+ if (strcmp(ka->key, "full") != 0)
+ return M_PROPERTY_UNKNOWN;
+
+ switch (ka->action) {
+ case M_PROPERTY_GET:
+ *(double *)ka->arg = time;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ *(char **)ka->arg = mp_format_time(time, true);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)ka->arg = *prop;
+ return M_PROPERTY_OK;
+ }
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Current stream position in seconds (RO)
+static int mp_property_stream_time_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ double pts = demuxer->stream_pts;
+ if (pts == MP_NOPTS_VALUE)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, pts);
+}
+
+
+/// Media length in seconds (RO)
+static int mp_property_length(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ double len;
+
+ if (!(int) (len = get_time_length(mpctx)))
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, len);
+}
+
+static int mp_property_avsync(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio || !mpctx->sh_video)
+ return M_PROPERTY_UNAVAILABLE;
+ if (mpctx->last_av_difference == MP_NOPTS_VALUE)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_double_ro(prop, action, arg, mpctx->last_av_difference);
+}
+
+/// Current position in percent (RW)
+static int mp_property_percent_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_SET: ;
+ double pos = *(double *)arg;
+ queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, 0);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(double *)arg = get_current_pos_ratio(mpctx, false) * 100.0;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "%d", get_percent_pos(mpctx));
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Current position in seconds (RW)
+static int mp_property_time_pos(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+
+ if (action == M_PROPERTY_SET) {
+ queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, 0);
+ return M_PROPERTY_OK;
+ }
+ return property_time(prop, action, arg, get_current_time(mpctx));
+}
+
+static int mp_property_remaining(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ double len = get_time_length(mpctx);
+ double pos = get_current_time(mpctx);
+ double start = get_start_time(mpctx);
+
+ if (!(int)len)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return property_time(prop, action, arg, len - (pos - start));
+}
+
+/// Current chapter (RW)
+static int mp_property_chapter(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ int chapter = get_current_chapter(mpctx);
+ if (chapter < -1)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = chapter;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ *(char **) arg = chapter_display_name(mpctx, chapter);
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SWITCH:
+ case M_PROPERTY_SET: ;
+ int step_all;
+ if (action == M_PROPERTY_SWITCH) {
+ struct m_property_switch_arg *sarg = arg;
+ step_all = ROUND(sarg->inc);
+ // Check threshold for relative backward seeks
+ if (mpctx->opts->chapter_seek_threshold >= 0 && step_all < 0) {
+ double current_chapter_start =
+ chapter_start_time(mpctx, chapter);
+ // If we are far enough into a chapter, seek back to the
+ // beginning of current chapter instead of previous one
+ if (current_chapter_start >= 0 &&
+ get_current_time(mpctx) - current_chapter_start >
+ mpctx->opts->chapter_seek_threshold)
+ step_all++;
+ }
+ } else // Absolute set
+ step_all = *(int *)arg - chapter;
+ chapter += step_all;
+ if (chapter < -1)
+ chapter = -1;
+ if (chapter >= get_chapter_count(mpctx) && step_all > 0) {
+ mpctx->stop_play = PT_NEXT_ENTRY;
+ } else {
+ mp_seek_chapter(mpctx, chapter);
+ }
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_list_chapters(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ int count = get_chapter_count(mpctx);
+ int cur = mpctx->num_sources ? get_current_chapter(mpctx) : -1;
+ char *res = NULL;
+ int n;
+
+ if (count < 1) {
+ res = talloc_asprintf_append(res, "No chapters.");
+ }
+
+ for (n = 0; n < count; n++) {
+ char *name = chapter_display_name(mpctx, n);
+ double t = chapter_start_time(mpctx, n);
+ char* time = mp_format_time(t, false);
+ res = talloc_asprintf_append(res, "%s", time);
+ talloc_free(time);
+ char *m1 = "> ", *m2 = " <";
+ if (n != cur)
+ m1 = m2 = "";
+ res = talloc_asprintf_append(res, " %s%s%s\n", m1, name, m2);
+ talloc_free(name);
+ }
+
+ *(char **)arg = res;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_edition(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ if (demuxer->num_editions <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+
+ int edition = demuxer->edition;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *)arg = edition;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET: {
+ edition = *(int *)arg;
+ if (edition != demuxer->edition) {
+ opts->edition_id = edition;
+ mpctx->stop_play = PT_RESTART;
+ }
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .name = prop->name,
+ .type = CONF_TYPE_INT,
+ .flags = CONF_RANGE,
+ .min = 0,
+ .max = demuxer->num_editions - 1,
+ };
+ *(struct m_option *)arg = opt;
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static struct mp_resolve_src *find_source(struct mp_resolve_result *res,
+ char *encid, char *url)
+{
+ if (res->num_srcs == 0)
+ return NULL;
+
+ int src = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ char *s_url = res->srcs[n]->url;
+ char *s_encid = res->srcs[n]->encid;
+ if (url && s_url && strcmp(url, s_url) == 0) {
+ src = n;
+ break;
+ }
+ // Prefer source URL if possible; so continue in case encid isn't unique
+ if (encid && s_encid && strcmp(encid, s_encid) == 0)
+ src = n;
+ }
+ return res->srcs[src];
+}
+
+static int mp_property_quvi_format(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct mp_resolve_result *res = mpctx->resolve_result;
+ if (!res || !res->num_srcs)
+ return M_PROPERTY_UNAVAILABLE;
+
+ struct mp_resolve_src *cur = find_source(res, opts->quvi_format, res->url);
+ if (!cur)
+ return M_PROPERTY_UNAVAILABLE;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(char **)arg = talloc_strdup(NULL, cur->encid);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET: {
+ mpctx->stop_play = PT_RESTART;
+ // Make it restart at the same position. This will have disastrous
+ // consequences if the stream is not arbitrarily seekable, but whatever.
+ m_config_backup_opt(mpctx->mconfig, "start");
+ opts->play_start = (struct m_rel_time) {
+ .type = REL_TIME_ABSOLUTE,
+ .pos = get_current_time(mpctx),
+ };
+ break;
+ }
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ int pos = 0;
+ for (int n = 0; n < res->num_srcs; n++) {
+ if (res->srcs[n] == cur) {
+ pos = n;
+ break;
+ }
+ }
+ pos += sarg->inc;
+ if (pos < 0 || pos >= res->num_srcs) {
+ if (sarg->wrap) {
+ pos = (res->num_srcs + pos) % res->num_srcs;
+ } else {
+ pos = av_clip(pos, 0, res->num_srcs);
+ }
+ }
+ char *fmt = res->srcs[pos]->encid;
+ return mp_property_quvi_format(prop, M_PROPERTY_SET, &fmt, mpctx);
+ }
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Number of titles in file
+static int mp_property_titles(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ 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(prop, action, arg, num_titles);
+}
+
+/// Number of chapters in file
+static int mp_property_chapters(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+ int count = get_chapter_count(mpctx);
+ return m_property_int_ro(prop, action, arg, count);
+}
+
+static int mp_property_editions(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+ if (demuxer->num_editions <= 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, demuxer->num_editions);
+}
+
+/// Current dvd angle (RW)
+static int mp_property_angle(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ int angle = -1;
+ int angles;
+
+ if (demuxer)
+ angle = demuxer_get_current_angle(demuxer);
+ if (angle < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ angles = demuxer_angles_count(demuxer);
+ if (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 = demuxer_set_angle(demuxer, *(int *)arg);
+ if (angle >= 0) {
+ if (mpctx->sh_video)
+ resync_video_stream(mpctx->sh_video);
+
+ if (mpctx->sh_audio)
+ resync_audio_stream(mpctx->sh_audio);
+ }
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE: {
+ struct m_option opt = {
+ .name = prop->name,
+ .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 tag_property(m_option_t *prop, int action, void *arg,
+ struct mp_tags *tags)
+{
+ static const m_option_t key_type =
+ {
+ "tags", NULL, CONF_TYPE_STRING, 0, 0, 0, NULL
+ };
+
+ switch (action) {
+ case M_PROPERTY_GET: {
+ char **slist = NULL;
+ int num = 0;
+ for (int n = 0; n < tags->num_keys; n++) {
+ MP_TARRAY_APPEND(NULL, slist, num, tags->keys[n]);
+ MP_TARRAY_APPEND(NULL, slist, num, tags->values[n]);
+ }
+ MP_TARRAY_APPEND(NULL, slist, num, NULL);
+ *(char ***)arg = slist;
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_PRINT: {
+ char *res = NULL;
+ for (int n = 0; n < tags->num_keys; n++) {
+ res = talloc_asprintf_append_buffer(res, "%s: %s\n",
+ tags->keys[n], tags->values[n]);
+ }
+ *(char **)arg = res;
+ return res ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
+ }
+ case M_PROPERTY_KEY_ACTION: {
+ struct m_property_action_arg *ka = arg;
+ char *meta = mp_tags_get_str(tags, ka->key);
+ if (!meta)
+ return M_PROPERTY_UNKNOWN;
+ switch (ka->action) {
+ case M_PROPERTY_GET:
+ *(char **)ka->arg = talloc_strdup(NULL, meta);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET_TYPE:
+ *(struct m_option *)ka->arg = key_type;
+ return M_PROPERTY_OK;
+ }
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Demuxer meta data
+static int mp_property_metadata(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (!demuxer)
+ return M_PROPERTY_UNAVAILABLE;
+
+ return tag_property(prop, action, arg, demuxer->metadata);
+}
+
+static int mp_property_chapter_metadata(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ int chapter = get_current_chapter(mpctx);
+ if (!demuxer || chapter < 0)
+ return M_PROPERTY_UNAVAILABLE;
+
+ assert(chapter < demuxer->num_chapters);
+
+ return tag_property(prop, action, arg, demuxer->chapters[chapter].metadata);
+}
+
+static int mp_property_pause(m_option_t *prop, int action, void *arg,
+ void *ctx)
+{
+ MPContext *mpctx = ctx;
+
+ if (action == M_PROPERTY_SET) {
+ if (*(int *)arg) {
+ pause_player(mpctx);
+ } else {
+ unpause_player(mpctx);
+ }
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, ctx);
+}
+
+static int mp_property_cache(m_option_t *prop, int action, void *arg,
+ void *ctx)
+{
+ MPContext *mpctx = ctx;
+ int cache = mp_get_cache_percent(mpctx);
+ if (cache < 0)
+ return M_PROPERTY_UNAVAILABLE;
+ return m_property_int_ro(prop, action, arg, cache);
+}
+
+static int mp_property_clock(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ char outstr[6];
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+
+ if ((tmp != NULL) && (strftime(outstr, sizeof(outstr), "%H:%M", tmp) == 5))
+ return m_property_strdup_ro(prop, action, arg, outstr);
+ return M_PROPERTY_UNAVAILABLE;
+}
+
+/// Volume (RW)
+static int mp_property_volume(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ switch (action) {
+ case M_PROPERTY_GET:
+ mixer_getbothvolume(mpctx->mixer, arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ if (!mixer_audio_initialized(mpctx->mixer))
+ return M_PROPERTY_ERROR;
+ mixer_setvolume(mpctx->mixer, *(float *) arg, *(float *) arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SWITCH: {
+ if (!mixer_audio_initialized(mpctx->mixer))
+ return M_PROPERTY_ERROR;
+ struct m_property_switch_arg *sarg = arg;
+ if (sarg->inc <= 0)
+ mixer_decvolume(mpctx->mixer);
+ else
+ mixer_incvolume(mpctx->mixer);
+ return M_PROPERTY_OK;
+ }
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Mute (RW)
+static int mp_property_mute(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ switch (action) {
+ case M_PROPERTY_SET:
+ if (!mixer_audio_initialized(mpctx->mixer))
+ return M_PROPERTY_ERROR;
+ mixer_setmute(mpctx->mixer, *(int *) arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mixer_getmute(mpctx->mixer);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static int mp_property_volrestore(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ switch (action) {
+ case M_PROPERTY_GET: {
+ char *s = mixer_get_volume_restore_data(mpctx->mixer);
+ *(char **)arg = s;
+ return s ? M_PROPERTY_OK : M_PROPERTY_UNAVAILABLE;
+ }
+ case M_PROPERTY_SET:
+ return M_PROPERTY_NOT_IMPLEMENTED;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Audio delay (RW)
+static int mp_property_audio_delay(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!(mpctx->sh_audio && mpctx->sh_video))
+ return M_PROPERTY_UNAVAILABLE;
+ float delay = mpctx->opts->audio_delay;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = format_delay(delay);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_SET:
+ mpctx->audio_delay = mpctx->opts->audio_delay = *(float *)arg;
+ mpctx->delay -= mpctx->audio_delay - delay;
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+/// Audio codec tag (RO)
+static int mp_property_audio_format(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_audio ? mpctx->sh_audio->gsh->codec : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+/// Audio codec name (RO)
+static int mp_property_audio_codec(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ const char *c = mpctx->sh_audio ? mpctx->sh_audio->gsh->decoder_desc : NULL;
+ return m_property_strdup_ro(prop, action, arg, c);
+}
+
+/// Audio bitrate (RO)
+static int mp_property_audio_bitrate(m_option_t *prop, int action,
+ void *arg, MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = format_bitrate(mpctx->sh_audio->i_bps);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->i_bps;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Samplerate (RO)
+static int mp_property_samplerate(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **)arg = talloc_asprintf(NULL, "%d kHz",
+ mpctx->sh_audio->samplerate / 1000);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->samplerate;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Number of channels (RO)
+static int mp_property_channels(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (!mpctx->sh_audio)
+ return M_PROPERTY_UNAVAILABLE;
+ switch (action) {
+ case M_PROPERTY_PRINT:
+ *(char **) arg = mp_chmap_to_str(&mpctx->sh_audio->channels);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_GET:
+ *(int *)arg = mpctx->sh_audio->channels.num;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+/// Balance (RW)
+static int mp_property_balance(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ float bal;
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ mixer_getbalance(mpctx->mixer, arg);
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT: {
+ char **str = arg;
+ mixer_getbalance(mpctx->mixer, &bal);
+ if (bal == 0.f)
+ *str = talloc_strdup(NULL, "center");
+ else if (bal == -1.f)
+ *str = talloc_strdup(NULL, "left only");
+ else if (bal == 1.f)
+ *str = talloc_strdup(NULL, "right only");
+ else {
+ unsigned right = (bal + 1.f) / 2.f * 100.f;
+ *str = talloc_asprintf(NULL, "left %d%%, right %d%%",
+ 100 - right, right);
+ }
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ mixer_setbalance(mpctx->mixer, *(float *)arg);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+static struct track* track_next(struct MPContext *mpctx, enum stream_type type,
+ int direction, struct track *track)
+{
+ assert(direction == -1 || direction == +1);
+ struct track *prev = NULL, *next = NULL;
+ bool seen = track == NULL;
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *cur = mpctx->tracks[n];
+ if (cur->type == type) {
+ if (cur == track) {
+ seen = true;
+ } else {
+ if (seen && !next) {
+ next = cur;
+ }
+ if (!seen || !track) {
+ prev = cur;
+ }
+ }
+ }
+ }
+ return direction > 0 ? next : prev;
+}
+
+static int property_switch_track(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx, enum stream_type type)
+{
+ if (!mpctx->num_sources)
+ return M_PROPERTY_UNAVAILABLE;
+ struct track *track = mpctx->current_track[type];
+
+ switch (action) {
+ case M_PROPERTY_GET:
+ *(int *) arg = track ? track->user_tid : -2;
+ return M_PROPERTY_OK;
+ case M_PROPERTY_PRINT:
+ if (!track)
+ *(char **) arg = talloc_strdup(NULL, "no");
+ else {
+ char *lang = track->lang;
+ if (!lang)
+ lang = mp_gtext("unknown");
+
+ if (track->title)
+ *(char **)arg = talloc_asprintf(NULL, "(%d) %s (\"%s\")",
+ track->user_tid, lang, track->title);
+ else
+ *(char **)arg = talloc_asprintf(NULL, "(%d) %s",
+ track->user_tid, lang);
+ }
+ return M_PROPERTY_OK;
+
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ mp_switch_track(mpctx, type,
+ track_next(mpctx, type, sarg->inc >= 0 ? +1 : -1, track));
+ return M_PROPERTY_OK;
+ }
+ case M_PROPERTY_SET:
+ mp_switch_track(mpctx, type, mp_track_by_tid(mpctx, type, *(int *)arg));
+ return M_PROPERTY_OK;
+ }
+ return mp_property_generic_option(prop, action, arg, mpctx);
+}
+
+static const char *track_type_name(enum stream_type t)
+{
+ switch (t) {
+ case STREAM_VIDEO: return "Video";
+ case STREAM_AUDIO: return "Audio";
+ case STREAM_SUB: return "Sub";
+ }
+ return NULL;
+}
+
+static int property_list_tracks(m_option_t *prop, int action, void *arg,
+ MPContext *mpctx)
+{
+ if (action == M_PROPERTY_GET) {
+ char *res = NULL;
+
+ for (int type = 0; type < STREAM_TYPE_COUNT; type++) {
+ for (int n = 0; n < mpctx->num_tracks; n++) {
+ struct track *track = mpctx->tracks[n];
+ if (track->type != type)
+ continue;
+
+ bool selected = mpctx->current_track[track->type] == track;
+ res = talloc_asprintf_append(res, "%s: ",
+ track_type_name(track->type));
+ if (selected)
+ res = talloc_asprintf_append(res, "> ");
+ res = talloc_asprintf_append(res, "(%d) ", track->user_tid);
+ if (track->title)
+ res = talloc_asprintf_append(res, "'%s' ", track->title);
+ if (track->lang)
+ res = talloc_asprintf_append(res, "(%s) ", track->lang);
+ if (track->is_external)
+ res = talloc_asprintf_append(res, "(external) ");
+ if (selected)
+ res = talloc_asprintf_append(res, "<");
+ res = talloc_asprintf_append(res, "\n");
+ }
+
+ res = talloc_asprintf_append(res, "\n");
+ }
+
+ struct demuxer *demuxer = mpctx->master_demuxer;
+ if (demuxer && d