diff options
Diffstat (limited to 'player/command.c')
-rw-r--r-- | player/command.c | 2620 |
1 files changed, 1902 insertions, 718 deletions
diff --git a/player/command.c b/player/command.c index 37ace97ba6..b7130814d8 100644 --- a/player/command.c +++ b/player/command.c @@ -15,6 +15,7 @@ * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ +#include <float.h> #include <stdlib.h> #include <inttypes.h> #include <unistd.h> @@ -23,21 +24,23 @@ #include <assert.h> #include <time.h> #include <math.h> -#include <pthread.h> #include <sys/types.h> +#include <ass/ass.h> #include <libavutil/avstring.h> #include <libavutil/common.h> -#include "config.h" #include "mpv_talloc.h" #include "client.h" +#include "external_files.h" #include "common/av_common.h" #include "common/codecs.h" #include "common/msg.h" #include "common/msg_control.h" +#include "common/stats.h" #include "filters/f_decoder_wrapper.h" #include "command.h" +#include "osdep/threads.h" #include "osdep/timer.h" #include "common/common.h" #include "input/input.h" @@ -46,11 +49,15 @@ #include "demux/demux.h" #include "demux/stheader.h" #include "common/playlist.h" -#include "sub/osd.h" #include "sub/dec_sub.h" +#include "sub/osd.h" +#include "sub/sd.h" #include "options/m_option.h" #include "options/m_property.h" -#include "options/m_config.h" +#include "options/m_config_frontend.h" +#include "options/parse_configfile.h" +#include "osdep/getpid.h" +#include "video/out/gpu/context.h" #include "video/out/vo.h" #include "video/csputils.h" #include "video/hwdec.h" @@ -67,6 +74,7 @@ #include "osdep/io.h" #include "osdep/subprocess.h" +#include "osdep/terminal.h" #include "core.h" @@ -78,15 +86,16 @@ struct command_ctx { // All properties, terminated with a {0} item. struct m_property *properties; - bool is_idle; - double last_seek_time; double last_seek_pts; double marked_pts; + bool marked_permanent; char **warned_deprecated; int num_warned_deprecated; + bool command_opts_processed; + struct overlay *overlays; int num_overlays; // One of these is in use by the OSD; the other one exists so that the @@ -104,6 +113,8 @@ struct command_ctx { struct mp_cmd_ctx *cache_dump_cmd; // in progress cache dumping char **script_props; + mpv_node udata; + mpv_node mdata; double cached_window_scale; }; @@ -112,27 +123,51 @@ static const struct m_option script_props_type = { .type = &m_option_type_keyvalue_list }; +static const struct m_option udata_type = { + .type = CONF_TYPE_NODE +}; + +static const struct m_option mdata_type = { + .type = CONF_TYPE_NODE +}; + struct overlay { struct mp_image *source; int x, y; + int dw, dh; }; struct hook_handler { - char *client; // client API user name + char *client; // client mpv_handle name (for logging) + int64_t client_id; // client mpv_handle ID char *type; // kind of hook, e.g. "on_load" uint64_t user_id; // user-chosen ID int priority; // priority for global hook order int64_t seq; // unique ID, != 0, also for fixed order on equal priorities - bool legacy; // old cmd based hook API bool active; // hook is currently in progress (only 1 at a time for now) }; -// U+279C HEAVY ROUND-TIPPED RIGHTWARDS ARROW +enum load_action_type { + LOAD_TYPE_REPLACE, + LOAD_TYPE_INSERT_AT, + LOAD_TYPE_INSERT_NEXT, + LOAD_TYPE_APPEND, +}; + +struct load_action { + enum load_action_type type; + bool play; +}; + +// U+25CB WHITE CIRCLE +// U+25CF BLACK CIRCLE // U+00A0 NO-BREAK SPACE -#define ARROW_SP "\342\236\234\302\240" +#define WHITECIRCLE "\xe2\x97\x8b" +#define BLACKCIRCLE "\xe2\x97\x8f" +#define NBSP "\xc2\xa0" -const char list_current[] = OSD_ASS_0 ARROW_SP OSD_ASS_1; -const char list_normal[] = OSD_ASS_0 "{\\alpha&HFF}" ARROW_SP "{\\r}" OSD_ASS_1; +const char list_current[] = BLACKCIRCLE NBSP; +const char list_normal[] = WHITECIRCLE NBSP; static int edit_filters(struct MPContext *mpctx, struct mp_log *log, enum stream_type mediatype, @@ -152,7 +187,7 @@ static void hook_remove(struct MPContext *mpctx, struct hook_handler *h) return; } } - assert(0); + MP_ASSERT_UNREACHABLE(); } bool mp_hook_test_completion(struct MPContext *mpctx, char *type) @@ -161,7 +196,7 @@ bool mp_hook_test_completion(struct MPContext *mpctx, char *type) for (int n = 0; n < cmd->num_hooks; n++) { struct hook_handler *h = cmd->hooks[n]; if (h->active && strcmp(h->type, type) == 0) { - if (!mp_client_exists(mpctx, h->client)) { + if (!mp_client_id_exists(mpctx, h->client_id)) { MP_WARN(mpctx, "client removed during hook handling\n"); hook_remove(mpctx, h); break; @@ -178,29 +213,14 @@ static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h) h->active = true; uint64_t reply_id = 0; - void *data; - int msg; - if (h->legacy) { - mpv_event_client_message *m = talloc_ptrtype(NULL, m); - *m = (mpv_event_client_message){0}; - MP_TARRAY_APPEND(m, m->args, m->num_args, "hook_run"); - MP_TARRAY_APPEND(m, m->args, m->num_args, - talloc_asprintf(m, "%llu", (long long)h->user_id)); - MP_TARRAY_APPEND(m, m->args, m->num_args, - talloc_asprintf(m, "%llu", (long long)h->seq)); - data = m; - msg = MPV_EVENT_CLIENT_MESSAGE; - } else { - mpv_event_hook *m = talloc_ptrtype(NULL, m); - *m = (mpv_event_hook){ - .name = talloc_strdup(m, h->type), - .id = h->seq, - }, - reply_id = h->user_id; - data = m; - msg = MPV_EVENT_HOOK; - } - int r = mp_client_send_event(mpctx, h->client, reply_id, msg, data); + mpv_event_hook *m = talloc_ptrtype(NULL, m); + *m = (mpv_event_hook){ + .name = talloc_strdup(m, h->type), + .id = h->seq, + }, + reply_id = h->user_id; + char *name = mp_tprintf(22, "@%"PRIi64, h->client_id); + int r = mp_client_send_event(mpctx, name, reply_id, MPV_EVENT_HOOK, m); if (r < 0) { MP_WARN(mpctx, "Sending hook command failed. Removing hook.\n"); hook_remove(mpctx, h); @@ -233,13 +253,13 @@ void mp_hook_start(struct MPContext *mpctx, char *type) } } -int mp_hook_continue(struct MPContext *mpctx, char *client, uint64_t id) +int mp_hook_continue(struct MPContext *mpctx, int64_t client_id, uint64_t id) { struct command_ctx *cmd = mpctx->command_ctx; for (int n = 0; n < cmd->num_hooks; n++) { struct hook_handler *h = cmd->hooks[n]; - if (strcmp(h->client, client) == 0 && h->seq == id) { + if (h->client_id == client_id && h->seq == id) { if (!h->active) break; h->active = false; @@ -260,22 +280,19 @@ static int compare_hook(const void *pa, const void *pb) return (*h1)->seq - (*h2)->seq; } -void mp_hook_add(struct MPContext *mpctx, const char *client, const char *name, - uint64_t user_id, int pri, bool legacy) +void mp_hook_add(struct MPContext *mpctx, char *client, int64_t client_id, + const char *name, uint64_t user_id, int pri) { - if (legacy) - MP_WARN(mpctx, "The old hook API is deprecated! Use the libmpv API.\n"); - struct command_ctx *cmd = mpctx->command_ctx; struct hook_handler *h = talloc_ptrtype(cmd, h); int64_t seq = ++cmd->hook_seq; *h = (struct hook_handler){ .client = talloc_strdup(h, client), + .client_id = client_id, .type = talloc_strdup(h, name), .user_id = user_id, .priority = pri, .seq = seq, - .legacy = legacy, }; MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook); @@ -407,9 +424,9 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - if (action == M_PROPERTY_PRINT) { - double speed = mpctx->opts->playback_speed; - *(char **)arg = talloc_asprintf(NULL, "%.2f", speed); + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_speed, 2, + false, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg); @@ -424,11 +441,12 @@ static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, switch (type[0]) { case 'a': val = mpctx->speed_factor_a; break; case 'v': val = mpctx->speed_factor_v; break; - default: abort(); + default: MP_ASSERT_UNREACHABLE(); } - if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_asprintf(NULL, "%+.05f%%", (val - 1) * 100); + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, (val - 1) * 100, 2, true, + true, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } @@ -439,7 +457,14 @@ static int mp_property_display_sync_active(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_flag_ro(action, arg, mpctx->display_sync_active); + return m_property_bool_ro(action, arg, mpctx->display_sync_active); +} + +static int mp_property_pid(void *ctx, struct m_property *prop, + int action, void *arg) +{ + // 32 bit on linux/windows - which C99 `int' is not guaranteed to hold + return m_property_int64_ro(action, arg, mp_getpid()); } /// filename with path (RO) @@ -519,28 +544,34 @@ static int mp_property_file_size(void *ctx, struct m_property *prop, return m_property_int64_ro(action, arg, size); } -static int mp_property_media_title(void *ctx, struct m_property *prop, - int action, void *arg) +static const char *find_non_filename_media_title(MPContext *mpctx) { - MPContext *mpctx = ctx; - char *name = NULL; - if (mpctx->opts->media_title) - name = mpctx->opts->media_title; + const char *name = mpctx->opts->media_title; if (name && name[0]) - return m_property_strdup_ro(action, arg, name); + return name; if (mpctx->demuxer) { name = mp_tags_get_str(mpctx->demuxer->metadata, "service_name"); if (name && name[0]) - return m_property_strdup_ro(action, arg, name); + return name; name = mp_tags_get_str(mpctx->demuxer->metadata, "title"); if (name && name[0]) - return m_property_strdup_ro(action, arg, name); + return name; name = mp_tags_get_str(mpctx->demuxer->metadata, "icy-title"); if (name && name[0]) - return m_property_strdup_ro(action, arg, name); + return name; } if (mpctx->playing && mpctx->playing->title) - return m_property_strdup_ro(action, arg, mpctx->playing->title); + return mpctx->playing->title; + return NULL; +} + +static int mp_property_media_title(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + const char *name = find_non_filename_media_title(mpctx); + if (name && name[0]) + return m_property_strdup_ro(action, arg, name); return mp_property_filename(ctx, prop, action, arg); } @@ -647,8 +678,9 @@ static int mp_property_avsync(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->ao_chain || !mpctx->vo_chain) return M_PROPERTY_UNAVAILABLE; - if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_asprintf(NULL, "%7.3f", mpctx->last_av_difference); + if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { + *(char **)arg = mp_format_double(NULL, mpctx->last_av_difference, 4, + true, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return m_property_double_ro(action, arg, mpctx->last_av_difference); @@ -674,7 +706,8 @@ static int mp_property_frame_drop_dec(void *ctx, struct m_property *prop, if (!dec) return M_PROPERTY_UNAVAILABLE; - return m_property_int_ro(action, arg, dec->dropped_frames); + return m_property_int_ro(action, arg, + mp_decoder_wrapper_get_frames_dropped(dec)); } static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop, @@ -753,7 +786,6 @@ static int mp_property_percent_pos(void *ctx, struct m_property *prop, case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = (struct m_option){ .type = CONF_TYPE_DOUBLE, - .flags = M_OPT_RANGE, .min = 0, .max = 100, }; @@ -872,7 +904,6 @@ static int mp_property_chapter(void *ctx, struct m_property *prop, case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = (struct m_option){ .type = CONF_TYPE_INT, - .flags = M_OPT_MIN | M_OPT_MAX, .min = -1, .max = num - 1, }; @@ -904,8 +935,8 @@ static int mp_property_chapter(void *ctx, struct m_property *prop, } else // Absolute set step_all = *(int *)arg - chapter; chapter += step_all; - if (chapter < -1) - chapter = -1; + if (chapter < 0) // avoid using -1 if first chapter starts at 0 + chapter = (chapter_start_time(mpctx, 0) <= 0) ? 0 : -1; if (chapter >= num && step_all > 0) { if (mpctx->opts->keep_open) { seek_to_last_frame(mpctx); @@ -920,9 +951,9 @@ static int mp_property_chapter(void *ctx, struct m_property *prop, } else { double pts = chapter_start_time(mpctx, chapter); if (pts != MP_NOPTS_VALUE) { - queue_seek(mpctx, MPSEEK_ABSOLUTE, pts, MPSEEK_DEFAULT, 0); + queue_seek(mpctx, MPSEEK_CHAPTER, pts, MPSEEK_DEFAULT, 0); mpctx->last_chapter_seek = chapter; - mpctx->last_chapter_pts = pts; + mpctx->last_chapter_flag = true; } } return M_PROPERTY_OK; @@ -1002,7 +1033,7 @@ static int parse_node_chapters(struct MPContext *mpctx, } } - mp_notify(mpctx, MPV_EVENT_CHAPTER_CHANGE, NULL); + mp_notify(mpctx, MP_EVENT_CHAPTER_CHANGE, NULL); mp_notify_property(mpctx, "chapter-list"); return M_PROPERTY_OK; @@ -1060,18 +1091,39 @@ static int mp_property_edition(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; + char *name = NULL; + + if (!demuxer) + return mp_property_generic_option(mpctx, prop, action, arg); + + int ed = demuxer->edition; - if (action == M_PROPERTY_GET_CONSTRICTED_TYPE && demuxer) { + if (demuxer->num_editions <= 1) + return M_PROPERTY_UNAVAILABLE; + + switch (action) { + case M_PROPERTY_GET_CONSTRICTED_TYPE: { *(struct m_option *)arg = (struct m_option){ .type = CONF_TYPE_INT, - .flags = M_OPT_RANGE, .min = 0, .max = demuxer->num_editions - 1, }; return M_PROPERTY_OK; } - - return mp_property_generic_option(mpctx, prop, action, arg); + case M_PROPERTY_PRINT: { + if (ed < 0) + return M_PROPERTY_UNAVAILABLE; + name = mp_tags_get_str(demuxer->editions[ed].metadata, "title"); + if (name) { + *(char **) arg = talloc_strdup(NULL, name); + } else { + *(char **) arg = talloc_asprintf(NULL, "%d", ed + 1); + } + return M_PROPERTY_OK; + } + default: + return mp_property_generic_option(mpctx, prop, action, arg); + } } static int get_edition_entry(int item, int action, void *arg, void *ctx) @@ -1087,7 +1139,7 @@ static int get_edition_entry(int item, int action, void *arg, void *ctx) {"id", SUB_PROP_INT(item)}, {"title", SUB_PROP_STR(title), .unavailable = !title}, - {"default", SUB_PROP_FLAG(ed->default_edition)}, + {"default", SUB_PROP_BOOL(ed->default_edition)}, {0} }; @@ -1322,15 +1374,37 @@ static int mp_property_core_idle(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_flag_ro(action, arg, !mpctx->playback_active); + return m_property_bool_ro(action, arg, !mpctx->playback_active); +} + +static int mp_property_deinterlace(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct vo_chain *vo_c = mpctx->vo_chain; + if (!vo_c) + return M_PROPERTY_UNAVAILABLE; + + bool deinterlace_active = mp_output_chain_deinterlace_active(vo_c->filter); + return m_property_bool_ro(action, arg, deinterlace_active); } static int mp_property_idle(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - struct command_ctx *cmd = mpctx->command_ctx; - return m_property_flag_ro(action, arg, cmd->is_idle); + return m_property_bool_ro(action, arg, mpctx->stop_play == PT_STOP); +} + +static int mp_property_window_id(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct vo *vo = mpctx->video_out; + int64_t wid; + if (!vo || vo_control(vo, VOCTRL_GET_WINDOW_ID, &wid) <= 0) + return M_PROPERTY_UNAVAILABLE; + return m_property_int64_ro(action, arg, wid); } static int mp_property_eof_reached(void *ctx, struct m_property *prop, @@ -1341,7 +1415,7 @@ static int mp_property_eof_reached(void *ctx, struct m_property *prop, return M_PROPERTY_UNAVAILABLE; bool eof = mpctx->video_status == STATUS_EOF && mpctx->audio_status == STATUS_EOF; - return m_property_flag_ro(action, arg, eof); + return m_property_bool_ro(action, arg, eof); } static int mp_property_seeking(void *ctx, struct m_property *prop, @@ -1350,14 +1424,14 @@ static int mp_property_seeking(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->playback_initialized) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, !mpctx->restart_complete); + return m_property_bool_ro(action, arg, !mpctx->restart_complete); } static int mp_property_playback_abort(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_flag_ro(action, arg, !mpctx->playing || mpctx->stop_play); + return m_property_bool_ro(action, arg, !mpctx->playing || mpctx->stop_play); } static int mp_property_cache_speed(void *ctx, struct m_property *prop, @@ -1421,7 +1495,7 @@ static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop, struct demux_reader_state s; demux_get_reader_state(mpctx->demuxer, &s); - return m_property_flag_ro(action, arg, s.idle); + return m_property_bool_ro(action, arg, s.idle); } static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop, @@ -1460,6 +1534,8 @@ static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop, node_map_add_int64(r, "fw-bytes", s.fw_bytes); if (s.file_cache_bytes >= 0) node_map_add_int64(r, "file-cache-bytes", s.file_cache_bytes); + if (s.bytes_per_second > 0) + node_map_add_int64(r, "raw-input-rate", s.bytes_per_second); if (s.seeking != MP_NOPTS_VALUE) node_map_add_double(r, "debug-seeking", s.seeking); node_map_add_int64(r, "debug-low-level-seeks", s.low_level_seeks); @@ -1498,7 +1574,7 @@ static int mp_property_paused_for_cache(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->playback_initialized) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, mpctx->paused_for_cache); + return m_property_bool_ro(action, arg, mpctx->paused_for_cache); } static int mp_property_cache_buffering(void *ctx, struct m_property *prop, @@ -1518,7 +1594,7 @@ static int mp_property_demuxer_is_network(void *ctx, struct m_property *prop, if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, mpctx->demuxer->is_network); + return m_property_bool_ro(action, arg, mpctx->demuxer->is_network); } @@ -1540,7 +1616,7 @@ static int mp_property_seekable(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, mpctx->demuxer->seekable); + return m_property_bool_ro(action, arg, mpctx->demuxer->seekable); } static int mp_property_partially_seekable(void *ctx, struct m_property *prop, @@ -1549,14 +1625,14 @@ static int mp_property_partially_seekable(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; if (!mpctx->demuxer) return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, mpctx->demuxer->partially_seekable); + return m_property_bool_ro(action, arg, mpctx->demuxer->partially_seekable); } static int mp_property_mixer_active(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_flag_ro(action, arg, !!mpctx->ao); + return m_property_bool_ro(action, arg, !!mpctx->ao); } /// Volume (RW) @@ -1570,7 +1646,6 @@ static int mp_property_volume(void *ctx, struct m_property *prop, case M_PROPERTY_GET_CONSTRICTED_TYPE: *(struct m_option *)arg = (struct m_option){ .type = CONF_TYPE_FLOAT, - .flags = M_OPT_RANGE, .min = 0, .max = opts->softvol_max, }; @@ -1583,6 +1658,28 @@ static int mp_property_volume(void *ctx, struct m_property *prop, return mp_property_generic_option(mpctx, prop, action, arg); } +static int mp_property_volume_gain(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + struct MPOpts *opts = mpctx->opts; + + switch (action) { + case M_PROPERTY_GET_CONSTRICTED_TYPE: + *(struct m_option *)arg = (struct m_option){ + .type = CONF_TYPE_FLOAT, + .min = opts->softvol_gain_min, + .max = opts->softvol_gain_max, + }; + return M_PROPERTY_OK; + case M_PROPERTY_PRINT: + *(char **)arg = talloc_asprintf(NULL, "%.1f", opts->softvol_gain); + return M_PROPERTY_OK; + } + + return mp_property_generic_option(mpctx, prop, action, arg); +} + static int mp_property_ao_volume(void *ctx, struct m_property *prop, int action, void *arg) { @@ -1593,32 +1690,28 @@ static int mp_property_ao_volume(void *ctx, struct m_property *prop, switch (action) { case M_PROPERTY_SET: { - float value = *(float *)arg; - ao_control_vol_t vol = {value, value}; + float vol = *(float *)arg; if (ao_control(ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK) return M_PROPERTY_UNAVAILABLE; return M_PROPERTY_OK; } case M_PROPERTY_GET: { - ao_control_vol_t vol = {0}; - if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) + if (ao_control(ao, AOCONTROL_GET_VOLUME, arg) != CONTROL_OK) return M_PROPERTY_UNAVAILABLE; - *(float *)arg = (vol.left + vol.right) / 2.0f; return M_PROPERTY_OK; } case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = (struct m_option){ .type = CONF_TYPE_FLOAT, - .flags = M_OPT_RANGE, .min = 0, .max = 100, }; return M_PROPERTY_OK; case M_PROPERTY_PRINT: { - ao_control_vol_t vol = {0}; + float vol = 0; if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) return M_PROPERTY_UNAVAILABLE; - *(char **)arg = talloc_asprintf(NULL, "%.f", (vol.left + vol.right) / 2.0f); + *(char **)arg = talloc_asprintf(NULL, "%.f", vol); return M_PROPERTY_OK; } } @@ -1649,7 +1742,7 @@ static int mp_property_ao_mute(void *ctx, struct m_property *prop, return M_PROPERTY_OK; } case M_PROPERTY_GET_TYPE: - *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_FLAG}; + *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_BOOL}; return M_PROPERTY_OK; } return M_PROPERTY_NOT_IMPLEMENTED; @@ -1691,7 +1784,7 @@ static int mp_property_audio_device(void *ctx, struct m_property *prop, if (mp_property_generic_option(mpctx, prop, M_PROPERTY_GET, &name) < 1) name = NULL; - struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug); + struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug, mpctx->ao); for (int n = 0; n < list->num_devices; n++) { struct ao_device_desc *dev = &list->devices[n]; if (dev->name && name && strcmp(dev->name, name) == 0) { @@ -1713,7 +1806,7 @@ static int mp_property_audio_devices(void *ctx, struct m_property *prop, struct command_ctx *cmd = mpctx->command_ctx; create_hotplug(mpctx); - struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug); + struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug, mpctx->ao); return m_property_read_list(action, arg, list->num_devices, get_device_entry, list); } @@ -1721,8 +1814,7 @@ static int mp_property_audio_devices(void *ctx, struct m_property *prop, static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg) { MPContext *mpctx = ctx; - return m_property_strdup_ro(action, arg, - mpctx->ao ? ao_get_name(mpctx->ao) : NULL); + return m_property_strdup_ro(action, arg, mpctx->ao ? ao_get_name(mpctx->ao) : NULL); } /// Audio delay (RW) @@ -1737,26 +1829,6 @@ static int mp_property_audio_delay(void *ctx, struct m_property *prop, return mp_property_generic_option(mpctx, prop, action, arg); } -/// Audio codec tag (RO) -static int mp_property_audio_codec_name(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - const char *c = track && track->stream ? track->stream->codec->codec : NULL; - return m_property_strdup_ro(action, arg, c); -} - -/// Audio codec name (RO) -static int mp_property_audio_codec(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_AUDIO]; - const char *c = track && track->dec ? track->dec->decoder_desc : NULL; - return m_property_strdup_ro(action, arg, c); -} - static int property_audiofmt(struct mp_aframe *fmt, int action, void *arg) { if (!fmt || !mp_aframe_config_is_valid(fmt)) @@ -1850,8 +1922,11 @@ static int property_switch_track(void *ctx, struct m_property *prop, case M_PROPERTY_PRINT: if (track) { char *lang = track->lang; - if (!lang) + if (!lang && type != STREAM_VIDEO) { lang = "unknown"; + } else if (!lang) { + lang = ""; + } if (track->title) { *(char **)arg = talloc_asprintf(NULL, "(%d) %s (\"%s\")", @@ -1880,10 +1955,8 @@ static int property_switch_track(void *ctx, struct m_property *prop, } else { // Simply cycle between "no" and "auto". It's possible that this does // not always do what the user means, but keep the complexity low. - mpctx->opts->stream_id[order][type] = - mpctx->opts->stream_id[order][type] == -1 ? -2 : -1; - m_config_notify_change_opt_ptr(mpctx->mconfig, - &mpctx->opts->stream_id[order][type]); + mark_track_selection(mpctx, order, type, + mpctx->opts->stream_id[order][type] == -1 ? -2 : -1); } return M_PROPERTY_OK; } @@ -1904,10 +1977,6 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) struct mp_codec_params p = track->stream ? *track->stream->codec : (struct mp_codec_params){0}; - const char *decoder_desc = NULL; - if (track->dec) - decoder_desc = track->dec->decoder_desc; - bool has_rg = track->stream && track->stream->codec->replaygain_data; struct replaygain_data rg = has_rg ? *track->stream->codec->replaygain_data : (struct replaygain_data){0}; @@ -1916,6 +1985,17 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) if (p.par_h) par = p.par_w / (double) p.par_h; + int order = -1; + if (track->selected) { + for (int i = 0; i < num_ptracks[track->type]; i++) { + if (mpctx->current_track[i][track->type] == track) { + order = i; + break; + } + } + } + + bool has_crop = mp_rect_w(p.crop) > 0 && mp_rect_h(p.crop) > 0; struct m_sub_property props[] = { {"id", SUB_PROP_INT(track->user_tid)}, {"type", SUB_PROP_STR(stream_type_name(track->type)), @@ -1928,23 +2008,39 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) .unavailable = !track->lang}, {"audio-channels", SUB_PROP_INT(track_channels(track)), .unavailable = track_channels(track) <= 0}, - {"albumart", SUB_PROP_FLAG(track->attached_picture)}, - {"default", SUB_PROP_FLAG(track->default_track)}, - {"forced", SUB_PROP_FLAG(track->forced_track)}, - {"dependent", SUB_PROP_FLAG(track->dependent_track)}, - {"visual-impaired", SUB_PROP_FLAG(track->visual_impaired_track)}, - {"hearing-impaired", SUB_PROP_FLAG(track->hearing_impaired_track)}, - {"external", SUB_PROP_FLAG(track->is_external)}, - {"selected", SUB_PROP_FLAG(track->selected)}, + {"image", SUB_PROP_BOOL(track->image)}, + {"albumart", SUB_PROP_BOOL(track->attached_picture)}, + {"default", SUB_PROP_BOOL(track->default_track)}, + {"forced", SUB_PROP_BOOL(track->forced_track)}, + {"dependent", SUB_PROP_BOOL(track->dependent_track)}, + {"visual-impaired", SUB_PROP_BOOL(track->visual_impaired_track)}, + {"hearing-impaired", SUB_PROP_BOOL(track->hearing_impaired_track)}, + {"external", SUB_PROP_BOOL(track->is_external)}, + {"selected", SUB_PROP_BOOL(track->selected)}, + {"main-selection", SUB_PROP_INT(order), .unavailable = order < 0}, {"external-filename", SUB_PROP_STR(track->external_filename), .unavailable = !track->external_filename}, {"ff-index", SUB_PROP_INT(track->ff_index)}, - {"decoder-desc", SUB_PROP_STR(decoder_desc), - .unavailable = !decoder_desc}, + {"hls-bitrate", SUB_PROP_INT(track->hls_bitrate), + .unavailable = !track->hls_bitrate}, + {"program-id", SUB_PROP_INT(track->program_id), + .unavailable = track->program_id < 0}, + {"decoder", SUB_PROP_STR(p.decoder), + .unavailable = !p.decoder}, + {"decoder-desc", SUB_PROP_STR(p.decoder_desc), + .unavailable = !p.decoder_desc}, {"codec", SUB_PROP_STR(p.codec), .unavailable = !p.codec}, + {"codec-desc", SUB_PROP_STR(p.codec_desc), + .unavailable = !p.codec_desc}, + {"codec-profile", SUB_PROP_STR(p.codec_profile), + .unavailable = !p.codec_profile}, {"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w}, {"demux-h", SUB_PROP_INT(p.disp_h), .unavailable = !p.disp_h}, + {"demux-crop-x",SUB_PROP_INT(p.crop.x0), .unavailable = !has_crop}, + {"demux-crop-y",SUB_PROP_INT(p.crop.y0), .unavailable = !has_crop}, + {"demux-crop-w",SUB_PROP_INT(mp_rect_w(p.crop)), .unavailable = !has_crop}, + {"demux-crop-h",SUB_PROP_INT(mp_rect_h(p.crop)), .unavailable = !has_crop}, {"demux-channel-count", SUB_PROP_INT(p.channels.num), .unavailable = !p.channels.num}, {"demux-channels", SUB_PROP_STR(mp_chmap_to_str(&p.channels)), @@ -1955,6 +2051,7 @@ static int get_track_entry(int item, int action, void *arg, void *ctx) {"demux-bitrate", SUB_PROP_INT(p.bitrate), .unavailable = p.bitrate <= 0}, {"demux-rotation", SUB_PROP_INT(p.rotate), .unavailable = p.rotate <= 0}, {"demux-par", SUB_PROP_DOUBLE(par), .unavailable = par <= 0}, + {"format-name", SUB_PROP_STR(p.format_name), .unavailable = !p.format_name}, {"replaygain-track-peak", SUB_PROP_FLOAT(rg.track_peak), .unavailable = !has_rg}, {"replaygain-track-gain", SUB_PROP_FLOAT(rg.track_gain), @@ -2022,6 +2119,63 @@ static int property_list_tracks(void *ctx, struct m_property *prop, get_track_entry, mpctx); } +static int property_current_tracks(void *ctx, struct m_property *prop, + int action, void *arg) +{ + MPContext *mpctx = ctx; + + if (action != M_PROPERTY_KEY_ACTION) + return M_PROPERTY_UNAVAILABLE; + + int type = -1; + int order = 0; + + struct m_property_action_arg *ka = arg; + bstr key; + char *rem; + m_property_split_path(ka->key, &key, &rem); + + if (bstr_equals0(key, "video")) { + type = STREAM_VIDEO; + } else if (bstr_equals0(key, "audio")) { + type = STREAM_AUDIO; + } else if (bstr_equals0(key, "sub")) { + type = STREAM_SUB; + } else if (bstr_equals0(key, "sub2")) { + type = STREAM_SUB; + order = 1; + } + + if (type < 0) + return M_PROPERTY_UNKNOWN; + + struct track *t = mpctx->current_track[order][type]; + + if (!t && mpctx->lavfi) { + for (int n = 0; n < mpctx->num_tracks; n++) { + if (mpctx->tracks[n]->type == type && mpctx->tracks[n]->selected) { + t = mpctx->tracks[n]; + break; + } + } + } + + if (!t) + return M_PROPERTY_UNAVAILABLE; + + int index = -1; + for (int n = 0; n < mpctx->num_tracks; n++) { + if (mpctx->tracks[n] == t) { + index = n; + break; + } + } + assert(index >= 0); + + char *name = mp_tprintf(80, "track-list/%d/%s", index, rem); + return mp_property_do(name, ka->action, ka->arg, ctx); +} + static int mp_property_hwdec_current(void *ctx, struct m_property *prop, int action, void *arg) { @@ -2034,7 +2188,7 @@ static int mp_property_hwdec_current(void *ctx, struct m_property *prop, char *current = NULL; mp_decoder_wrapper_control(dec, VDCTRL_GET_HWDEC, ¤t); - if (!current) + if (!current || !current[0]) current = "no"; return m_property_strdup_ro(action, arg, current); } @@ -2090,67 +2244,129 @@ static int mp_property_frame_count(void *ctx, struct m_property *prop, return m_property_int_ro(action, arg, frames); } -/// Video codec tag (RO) -static int mp_property_video_format(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct track *track = mpctx->current_track[0][STREAM_VIDEO]; - const char *c = track && track->stream ? track->stream->codec->codec : NULL; - return m_property_strdup_ro(action, arg, c); -} +static const char *get_aspect_ratio_name(double ratio) +{ + // Depending on cropping/mastering exact ratio may differ. +#define RATIO_THRESH 0.025 +#define RATIO_CASE(ref, name) \ + if (fabs(ratio - (ref)) < RATIO_THRESH) \ + return name; \ + + // https://en.wikipedia.org/wiki/Aspect_ratio_(image) |