summaryrefslogtreecommitdiffstats
path: root/player
diff options
context:
space:
mode:
Diffstat (limited to 'player')
-rw-r--r--player/audio.c471
-rw-r--r--player/command.c3243
-rw-r--r--player/command.h50
-rw-r--r--player/configfiles.c354
-rw-r--r--player/dvdnav.c251
-rw-r--r--player/loadfile.c1423
-rw-r--r--player/lua/assdraw.lua98
-rw-r--r--player/lua/defaults.lua82
-rw-r--r--player/lua/osc.lua1288
-rw-r--r--player/main.c450
-rw-r--r--player/misc.c210
-rw-r--r--player/mp_core.h459
-rw-r--r--player/mp_lua.c683
-rw-r--r--player/mp_lua.h14
-rw-r--r--player/osd.c518
-rw-r--r--player/playloop.c1343
-rw-r--r--player/screenshot.c404
-rw-r--r--player/screenshot.h46
-rw-r--r--player/sub.c233
-rw-r--r--player/timeline/tl_cue.c417
-rw-r--r--player/timeline/tl_matroska.c591
-rw-r--r--player/timeline/tl_mpv_edl.c274
-rw-r--r--player/video.c422
23 files changed, 13324 insertions, 0 deletions
diff --git a/player/audio.c b/player/audio.c
new file mode 100644
index 0000000000..ec2f039531
--- /dev/null
+++ b/player/audio.c
@@ -0,0 +1,471 @@
+/*
+ * 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 <stddef.h>
+#include <stdbool.h>
+#include <inttypes.h>
+#include <math.h>
+#include <assert.h>
+
+#include "config.h"
+#include "talloc.h"
+
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/options.h"
+#include "mpvcore/mp_common.h"
+
+#include "audio/mixer.h"
+#include "audio/audio.h"
+#include "audio/audio_buffer.h"
+#include "audio/decode/dec_audio.h"
+#include "audio/filter/af.h"
+#include "audio/out/ao.h"
+#include "demux/demux.h"
+#include "video/decode/dec_video.h"
+
+#include "mp_core.h"
+
+static int build_afilter_chain(struct MPContext *mpctx)
+{
+ struct dec_audio *d_audio = mpctx->d_audio;
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+
+ if (!d_audio)
+ return 0;
+
+ struct mp_audio in_format;
+ mp_audio_buffer_get_format(d_audio->decode_buffer, &in_format);
+
+ int new_srate;
+ if (af_control_any_rev(d_audio->afilter, AF_CONTROL_SET_PLAYBACK_SPEED,
+ &opts->playback_speed))
+ new_srate = in_format.rate;
+ else {
+ new_srate = in_format.rate * opts->playback_speed;
+ if (new_srate != ao->samplerate) {
+ // limits are taken from libaf/af_resample.c
+ if (new_srate < 8000)
+ new_srate = 8000;
+ if (new_srate > 192000)
+ new_srate = 192000;
+ opts->playback_speed = new_srate / (double)in_format.rate;
+ }
+ }
+ return audio_init_filters(d_audio, new_srate,
+ &ao->samplerate, &ao->channels, &ao->format);
+}
+
+static int recreate_audio_filters(struct MPContext *mpctx)
+{
+ assert(mpctx->d_audio);
+
+ // init audio filters:
+ if (!build_afilter_chain(mpctx)) {
+ MP_ERR(mpctx, "Couldn't find matching filter/ao format!\n");
+ return -1;
+ }
+
+ mixer_reinit_audio(mpctx->mixer, mpctx->ao, mpctx->d_audio->afilter);
+
+ return 0;
+}
+
+int reinit_audio_filters(struct MPContext *mpctx)
+{
+ struct dec_audio *d_audio = mpctx->d_audio;
+ if (!d_audio)
+ return -2;
+
+ af_uninit(mpctx->d_audio->afilter);
+ if (af_init(mpctx->d_audio->afilter) < 0)
+ return -1;
+ if (recreate_audio_filters(mpctx) < 0)
+ return -1;
+
+ return 0;
+}
+
+void reinit_audio_chain(struct MPContext *mpctx)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct sh_stream *sh = init_demux_stream(mpctx, STREAM_AUDIO);
+ if (!sh) {
+ uninit_player(mpctx, INITIALIZED_AO);
+ goto no_audio;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_ACODEC)) {
+ mpctx->initialized_flags |= INITIALIZED_ACODEC;
+ assert(!mpctx->d_audio);
+ mpctx->d_audio = talloc_zero(NULL, struct dec_audio);
+ mpctx->d_audio->opts = opts;
+ mpctx->d_audio->header = sh;
+ if (!audio_init_best_codec(mpctx->d_audio, opts->audio_decoders))
+ goto init_error;
+ }
+ assert(mpctx->d_audio);
+
+ struct mp_audio in_format;
+ mp_audio_buffer_get_format(mpctx->d_audio->decode_buffer, &in_format);
+
+ int ao_srate = opts->force_srate;
+ int ao_format = opts->audio_output_format;
+ struct mp_chmap ao_channels = {0};
+ if (mpctx->initialized_flags & INITIALIZED_AO) {
+ ao_srate = mpctx->ao->samplerate;
+ ao_format = mpctx->ao->format;
+ ao_channels = mpctx->ao->channels;
+ } else {
+ // Automatic downmix
+ if (mp_chmap_is_stereo(&opts->audio_output_channels) &&
+ !mp_chmap_is_stereo(&in_format.channels))
+ {
+ mp_chmap_from_channels(&ao_channels, 2);
+ }
+ }
+
+ // Determine what the filter chain outputs. build_afilter_chain() also
+ // needs this for testing whether playback speed is changed by resampling
+ // or using a special filter.
+ if (!audio_init_filters(mpctx->d_audio, // preliminary init
+ // input:
+ in_format.rate,
+ // output:
+ &ao_srate, &ao_channels, &ao_format)) {
+ MP_ERR(mpctx, "Error at audio filter chain pre-init!\n");
+ goto init_error;
+ }
+
+ if (!(mpctx->initialized_flags & INITIALIZED_AO)) {
+ mpctx->initialized_flags |= INITIALIZED_AO;
+ mp_chmap_remove_useless_channels(&ao_channels,
+ &opts->audio_output_channels);
+ mpctx->ao = ao_init_best(mpctx->global, mpctx->input,
+ mpctx->encode_lavc_ctx, ao_srate, ao_format,
+ ao_channels);
+ struct ao *ao = mpctx->ao;
+ if (!ao) {
+ MP_ERR(mpctx, "Could not open/initialize audio device -> no sound.\n");
+ goto init_error;
+ }
+
+ ao->buffer = mp_audio_buffer_create(ao);
+ mp_audio_buffer_reinit_fmt(ao->buffer, ao->format, &ao->channels,
+ ao->samplerate);
+
+ char *s = mp_audio_fmt_to_str(ao->samplerate, &ao->channels, ao->format);
+ MP_INFO(mpctx, "AO: [%s] %s\n", ao->driver->name, s);
+ talloc_free(s);
+ MP_VERBOSE(mpctx, "AO: Description: %s\n", ao->driver->description);
+ update_window_title(mpctx, true);
+ }
+
+ if (recreate_audio_filters(mpctx) < 0)
+ goto init_error;
+
+ mpctx->syncing_audio = true;
+ return;
+
+init_error:
+ uninit_player(mpctx, INITIALIZED_ACODEC | INITIALIZED_AO);
+ cleanup_demux_stream(mpctx, STREAM_AUDIO);
+no_audio:
+ mpctx->current_track[STREAM_AUDIO] = NULL;
+ MP_INFO(mpctx, "Audio: no audio\n");
+}
+
+// Return pts value corresponding to the end point of audio written to the
+// ao so far.
+double written_audio_pts(struct MPContext *mpctx)
+{
+ struct dec_audio *d_audio = mpctx->d_audio;
+ if (!d_audio)
+ return MP_NOPTS_VALUE;
+
+ struct mp_audio in_format;
+ mp_audio_buffer_get_format(d_audio->decode_buffer, &in_format);
+
+ // first calculate the end pts of audio that has been output by decoder
+ double a_pts = d_audio->pts;
+ if (a_pts == MP_NOPTS_VALUE)
+ return MP_NOPTS_VALUE;
+
+ // d_audio->pts is the timestamp of the latest input packet with
+ // known pts that the decoder has decoded. d_audio->pts_bytes is
+ // the amount of bytes the decoder has written after that timestamp.
+ a_pts += d_audio->pts_offset / (double)in_format.rate;
+
+ // Now a_pts hopefully holds the pts for end of audio from decoder.
+ // Subtract data in buffers between decoder and audio out.
+
+ // Decoded but not filtered
+ a_pts -= mp_audio_buffer_seconds(d_audio->decode_buffer);
+
+ // Data buffered in audio filters, measured in seconds of "missing" output
+ double buffered_output = af_calc_delay(d_audio->afilter);
+
+ // Data that was ready for ao but was buffered because ao didn't fully
+ // accept everything to internal buffers yet
+ buffered_output += mp_audio_buffer_seconds(mpctx->ao->buffer);
+
+ // Filters divide audio length by playback_speed, so multiply by it
+ // to get the length in original units without speedup or slowdown
+ a_pts -= buffered_output * mpctx->opts->playback_speed;
+
+ return a_pts + mpctx->video_offset;
+}
+
+// Return pts value corresponding to currently playing audio.
+double playing_audio_pts(struct MPContext *mpctx)
+{
+ double pts = written_audio_pts(mpctx);
+ if (pts == MP_NOPTS_VALUE)
+ return pts;
+ return pts - mpctx->opts->playback_speed * ao_get_delay(mpctx->ao);
+}
+
+static int write_to_ao(struct MPContext *mpctx, struct mp_audio *data, int flags,
+ double pts)
+{
+ if (mpctx->paused)
+ return 0;
+ struct ao *ao = mpctx->ao;
+ ao->pts = pts;
+ double real_samplerate = ao->samplerate / mpctx->opts->playback_speed;
+ int played = ao_play(mpctx->ao, data->planes, data->samples, flags);
+ assert(played <= data->samples);
+ if (played > 0) {
+ mpctx->shown_aframes += played;
+ mpctx->delay += played / real_samplerate;
+ // Keep correct pts for remaining data - could be used to flush
+ // remaining buffer when closing ao.
+ ao->pts += played / real_samplerate;
+ return played;
+ }
+ return 0;
+}
+
+static int write_silence_to_ao(struct MPContext *mpctx, int samples, int flags,
+ double pts)
+{
+ struct mp_audio tmp = {0};
+ mp_audio_buffer_get_format(mpctx->ao->buffer, &tmp);
+ tmp.samples = samples;
+ char *p = talloc_size(NULL, tmp.samples * tmp.sstride);
+ for (int n = 0; n < tmp.num_planes; n++)
+ tmp.planes[n] = p;
+ mp_audio_fill_silence(&tmp, 0, tmp.samples);
+ int r = write_to_ao(mpctx, &tmp, 0, pts);
+ talloc_free(p);
+ return r;
+}
+
+#define ASYNC_PLAY_DONE -3
+static int audio_start_sync(struct MPContext *mpctx, int playsize)
+{
+ struct ao *ao = mpctx->ao;
+ struct MPOpts *opts = mpctx->opts;
+ struct dec_audio *d_audio = mpctx->d_audio;
+ int res;
+
+ assert(d_audio);
+
+ // Timing info may not be set without
+ res = audio_decode(d_audio, ao->buffer, 1);
+ if (res < 0)
+ return res;
+
+ int samples;
+ bool did_retry = false;
+ double written_pts;
+ double real_samplerate = ao->samplerate / opts->playback_speed;
+ bool hrseek = mpctx->hrseek_active; // audio only hrseek
+ mpctx->hrseek_active = false;
+ while (1) {
+ written_pts = written_audio_pts(mpctx);
+ double ptsdiff;
+ if (hrseek)
+ ptsdiff = written_pts - mpctx->hrseek_pts;
+ else
+ ptsdiff = written_pts - mpctx->video_next_pts - mpctx->delay
+ - mpctx->audio_delay;
+ samples = ptsdiff * real_samplerate;
+
+ // ogg demuxers give packets without timing
+ if (written_pts <= 1 && d_audio->pts == MP_NOPTS_VALUE) {
+ if (!did_retry) {
+ // Try to read more data to see packets that have pts
+ res = audio_decode(d_audio, ao->buffer, ao->samplerate);
+ if (res < 0)
+ return res;
+ did_retry = true;
+ continue;
+ }
+ samples = 0;
+ }
+
+ if (fabs(ptsdiff) > 300 || isnan(ptsdiff)) // pts reset or just broken?
+ samples = 0;
+
+ if (samples > 0)
+ break;
+
+ mpctx->syncing_audio = false;
+ int skip_samples = -samples;
+ int a = MPMIN(skip_samples, MPMAX(playsize, 2500));
+ res = audio_decode(d_audio, ao->buffer, a);
+ if (skip_samples <= mp_audio_buffer_samples(ao->buffer)) {
+ mp_audio_buffer_skip(ao->buffer, skip_samples);
+ if (res < 0)
+ return res;
+ return audio_decode(d_audio, ao->buffer, playsize);
+ }
+ mp_audio_buffer_clear(ao->buffer);
+ if (res < 0)
+ return res;
+ }
+ if (hrseek)
+ // Don't add silence in audio-only case even if position is too late
+ return 0;
+ if (samples >= playsize) {
+ /* This case could fall back to the one below with
+ * samples = playsize, but then silence would keep accumulating
+ * in ao->buffer if the AO accepts less data than it asks for
+ * in playsize. */
+ write_silence_to_ao(mpctx, playsize, 0,
+ written_pts - samples / real_samplerate);
+ return ASYNC_PLAY_DONE;
+ }
+ mpctx->syncing_audio = false;
+ mp_audio_buffer_prepend_silence(ao->buffer, samples);
+ return audio_decode(d_audio, ao->buffer, playsize);
+}
+
+int fill_audio_out_buffers(struct MPContext *mpctx, double endpts)
+{
+ struct MPOpts *opts = mpctx->opts;
+ struct ao *ao = mpctx->ao;
+ int playsize;
+ int playflags = 0;
+ bool audio_eof = false;
+ bool signal_eof = false;
+ bool partial_fill = false;
+ struct dec_audio *d_audio = mpctx->d_audio;
+ // Can't adjust the start of audio with spdif pass-through.
+ bool modifiable_audio_format = !(ao->format & AF_FORMAT_SPECIAL_MASK);
+
+ assert(d_audio);
+
+ if (mpctx->paused)
+ playsize = 1; // just initialize things (audio pts at least)
+ else
+ playsize = ao_get_space(ao);
+
+ // Coming here with hrseek_active still set means audio-only
+ if (!mpctx->d_video || !mpctx->sync_audio_to_video)
+ mpctx->syncing_audio = false;
+ if (!opts->initial_audio_sync || !modifiable_audio_format) {
+ mpctx->syncing_audio = false;
+ mpctx->hrseek_active = false;
+ }
+
+ int res;
+ if (mpctx->syncing_audio || mpctx->hrseek_active)
+ res = audio_start_sync(mpctx, playsize);
+ else
+ res = audio_decode(d_audio, ao->buffer, playsize);
+
+ if (res < 0) { // EOF, error or format change
+ if (res == -2) {
+ /* The format change isn't handled too gracefully. A more precise
+ * implementation would require draining buffered old-format audio
+ * while displaying video, then doing the output format switch.
+ */
+ if (!mpctx->opts->gapless_audio)
+ uninit_player(mpctx, INITIALIZED_AO);
+ reinit_audio_chain(mpctx);
+ return -1;
+ } else if (res == ASYNC_PLAY_DONE)
+ return 0;
+ else if (demux_stream_eof(d_audio->header))
+ audio_eof = true;
+ }
+
+ if (endpts != MP_NOPTS_VALUE) {
+ double samples = (endpts - written_audio_pts(mpctx) + mpctx->audio_delay)
+ * ao->samplerate / opts->playback_speed;
+ if (playsize > samples) {
+ playsize = MPMAX(samples, 0);
+ audio_eof = true;
+ partial_fill = true;
+ }
+ }
+
+ if (playsize > mp_audio_buffer_samples(ao->buffer)) {
+ playsize = mp_audio_buffer_samples(ao->buffer);
+ partial_fill = true;
+ }
+ if (!playsize)
+ return partial_fill && audio_eof ? -2 : -partial_fill;
+
+ if (audio_eof && partial_fill) {
+ if (opts->gapless_audio) {
+ // With gapless audio, delay this to ao_uninit. There must be only
+ // 1 final chunk, and that is handled when calling ao_uninit().
+ signal_eof = true;
+ } else {
+ playflags |= AOPLAY_FINAL_CHUNK;
+ }
+ }
+
+ assert(ao->buffer_playable_samples <= mp_audio_buffer_samples(ao->buffer));
+
+ struct mp_audio data;
+ mp_audio_buffer_peek(ao->buffer, &data);
+ data.samples = MPMIN(data.samples, playsize);
+ int played = write_to_ao(mpctx, &data, playflags, written_audio_pts(mpctx));
+ ao->buffer_playable_samples = playsize - played;
+
+ if (played > 0) {
+ mp_audio_buffer_skip(ao->buffer, played);
+ } else if (!mpctx->paused && audio_eof && ao_get_delay(ao) < .04) {
+ // Sanity check to avoid hanging in case current ao doesn't output
+ // partial chunks and doesn't check for AOPLAY_FINAL_CHUNK
+ signal_eof = true;
+ }
+
+ return signal_eof ? -2 : -partial_fill;
+}
+
+// Drop data queued for output, or which the AO is currently outputting.
+void clear_audio_output_buffers(struct MPContext *mpctx)
+{
+ if (mpctx->ao) {
+ ao_reset(mpctx->ao);
+ mp_audio_buffer_clear(mpctx->ao->buffer);
+ mpctx->ao->buffer_playable_samples = 0;
+ }
+}
+
+// Drop decoded data queued for filtering.
+void clear_audio_decode_buffers(struct MPContext *mpctx)
+{
+ if (mpctx->d_audio)
+ mp_audio_buffer_clear(mpctx->d_audio->decode_buffer);
+}
diff --git a/player/command.c b/player/command.c
new file mode 100644
index 0000000000..10dfb4c261
--- /dev/null
+++ b/player/command.c
@@ -0,0 +1,3243 @@
+/*
+ * 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 <sys/types.h>
+
+#include <libavutil/avstring.h>
+#include <libavutil/common.h>
+
+#include "config.h"
+#include "talloc.h"
+#include "command.h"
+#include "osdep/timer.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/osd.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 "video/out/vo.h"
+#include "video/csputils.h"
+#include "audio/mixer.h"
+#include "audio/audio_buffer.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"
+#if HAVE_DVBIN
+#include "stream/dvbin.h"
+#endif
+#include "screenshot.h"
+#if HAVE_SYS_MMAN_H
+#include <sys/mman.h>
+#endif
+#ifndef __MINGW32__
+#include <sys/wait.h>
+#endif
+
+#include "osdep/io.h"
+
+#include "mp_core.h"
+#include "mp_lua.h"
+
+struct command_ctx {
+ int events;
+
+ double last_seek_time;
+ double last_seek_pts;
+
+ struct cycle_counter *cycle_counters;
+ int num_cycle_counters;
+
+#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);
+
+// Call before a seek, in order to allow revert_seek to undo the seek.
+static void mark_seek(struct MPContext *mpctx)
+{
+ struct command_ctx *cmd = mpctx->command_ctx;
+ double now = mp_time_sec();
+ if (now > cmd->last_seek_time + 2.0 || cmd->last_seek_pts == MP_NOPTS_VALUE)
+ cmd->last_seek_pts = get_current_time(mpctx);
+ cmd->last_seek_time = now;
+}
+
+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->d_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->d_audio || !mpctx->d_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 bool time_remaining(MPContext *mpctx, double *remaining)
+{
+ double len = get_time_length(mpctx);
+ double pos = get_current_time(mpctx);
+ double start = get_start_time(mpctx);
+
+ *remaining = len - (pos - start);
+
+ return !!(int)len;
+}
+
+static int mp_property_remaining(m_option_t *prop, int action,
+