diff options
author | Anton Kindestam <antonki@kth.se> | 2018-12-05 19:02:03 +0100 |
---|---|---|
committer | Anton Kindestam <antonki@kth.se> | 2018-12-05 19:19:24 +0100 |
commit | 8b83c8996686072bc743b112ae5cb3bf93aa33ed (patch) | |
tree | b09ce6a7ff470b05006622f19914b3d39d2f7d9f /player/command.c | |
parent | 5bcac8580df6fc62323136f756a3a6d1e754fe9c (diff) | |
parent | 559a400ac36e75a8d73ba263fd7fa6736df1c2da (diff) | |
download | mpv-8b83c8996686072bc743b112ae5cb3bf93aa33ed.tar.bz2 mpv-8b83c8996686072bc743b112ae5cb3bf93aa33ed.tar.xz |
Merge commit '559a400ac36e75a8d73ba263fd7fa6736df1c2da' into wm4-commits--merge-edition
This bumps libmpv version to 1.103
Diffstat (limited to 'player/command.c')
-rw-r--r-- | player/command.c | 949 |
1 files changed, 557 insertions, 392 deletions
diff --git a/player/command.c b/player/command.c index 7237b08820..30629eae54 100644 --- a/player/command.c +++ b/player/command.c @@ -60,7 +60,9 @@ #include "video/out/bitmap_packer.h" #include "options/path.h" #include "screenshot.h" +#include "misc/dispatch.h" #include "misc/node.h" +#include "misc/thread_pool.h" #include "osdep/io.h" #include "osdep/subprocess.h" @@ -451,10 +453,10 @@ static int mp_property_playback_speed(void *ctx, struct m_property *prop, double speed = mpctx->opts->playback_speed; switch (action) { case M_PROPERTY_SET: { - mpctx->opts->playback_speed = *(double *)arg; + int r = mp_property_generic_option(mpctx, prop, action, arg); update_playback_speed(mpctx); mp_wakeup_core(mpctx); - return M_PROPERTY_OK; + return r; } case M_PROPERTY_PRINT: *(char **)arg = talloc_asprintf(NULL, "%.2f", speed); @@ -905,7 +907,7 @@ static int mp_property_disc_title(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct demuxer *d = mpctx->demuxer; - if (!d) + if (!d || !d->extended_ctrls) return M_PROPERTY_UNAVAILABLE; unsigned int title = -1; switch (action) { @@ -1141,11 +1143,10 @@ static int mp_property_edition(void *ctx, struct m_property *prop, case M_PROPERTY_SET: { edition = *(int *)arg; if (edition != demuxer->edition) { - mpctx->opts->edition_id = edition; if (!mpctx->stop_play) mpctx->stop_play = PT_CURRENT_ENTRY; mp_wakeup_core(mpctx); - break; // make it accessible to the demuxer via option change notify + break; // write value, trigger option change notify } return M_PROPERTY_OK; } @@ -1222,8 +1223,9 @@ static int mp_property_disc_titles(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; unsigned int num_titles; - if (!demuxer || demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, - &num_titles) < 1) + if (!demuxer || !demuxer->extended_ctrls || + demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, + &num_titles) < 1) return M_PROPERTY_UNAVAILABLE; return m_property_int_ro(action, arg, num_titles); } @@ -1253,8 +1255,9 @@ static int mp_property_list_disc_titles(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; unsigned int num_titles; - if (!demuxer || demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, - &num_titles) < 1) + if (!demuxer || !demuxer->extended_ctrls || + demux_stream_control(demuxer, STREAM_CTRL_GET_NUM_TITLES, + &num_titles) < 1) return M_PROPERTY_UNAVAILABLE; return m_property_read_list(action, arg, num_titles, get_disc_title_entry, mpctx); @@ -1289,7 +1292,7 @@ static int mp_property_angle(void *ctx, struct m_property *prop, { MPContext *mpctx = ctx; struct demuxer *demuxer = mpctx->demuxer; - if (!demuxer) + if (!demuxer || !demuxer->extended_ctrls) return M_PROPERTY_UNAVAILABLE; int ris, angles = -1, angle = 1; @@ -1556,134 +1559,6 @@ static int mp_property_playback_abort(void *ctx, struct m_property *prop, return m_property_flag_ro(action, arg, !mpctx->playing || mpctx->stop_play); } -static int mp_property_cache(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - float cache = mp_get_cache_percent(mpctx); - if (cache < 0) - return M_PROPERTY_UNAVAILABLE; - - if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_asprintf(NULL, "%d", (int)cache); - return M_PROPERTY_OK; - } - - return m_property_float_ro(action, arg, cache); -} - -static int property_int_kb_size(int kb_size, int action, void *arg) -{ - switch (action) { - case M_PROPERTY_GET: - *(int *)arg = kb_size; - return M_PROPERTY_OK; - case M_PROPERTY_PRINT: - *(char **)arg = format_file_size(kb_size * 1024LL); - return M_PROPERTY_OK; - case M_PROPERTY_GET_TYPE: - *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT}; - return M_PROPERTY_OK; - } - return M_PROPERTY_NOT_IMPLEMENTED; -} - -static int mp_property_cache_size(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct demuxer *demuxer = mpctx->demuxer; - if (!demuxer) - return M_PROPERTY_UNAVAILABLE; - switch (action) { - case M_PROPERTY_GET: - case M_PROPERTY_PRINT: { - struct stream_cache_info info = {0}; - demux_stream_control(demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); - if (info.size <= 0) - break; - return property_int_kb_size(info.size / 1024, action, arg); - } - case M_PROPERTY_GET_TYPE: - *(struct m_option *)arg = (struct m_option){ - .type = CONF_TYPE_INT, - .flags = M_OPT_MIN, - .min = 0, - }; - return M_PROPERTY_OK; - case M_PROPERTY_SET: { - int64_t size = *(int *)arg * 1024LL; - int r = demux_stream_control(demuxer, STREAM_CTRL_SET_CACHE_SIZE, &size); - if (r == STREAM_UNSUPPORTED) - break; - if (r == STREAM_OK) - return M_PROPERTY_OK; - return M_PROPERTY_ERROR; - } - } - return M_PROPERTY_NOT_IMPLEMENTED; -} - -static int mp_property_cache_used(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - if (!mpctx->demuxer) - return M_PROPERTY_UNAVAILABLE; - - struct stream_cache_info info = {0}; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); - if (info.size <= 0) - return M_PROPERTY_UNAVAILABLE; - return property_int_kb_size(info.fill / 1024, action, arg); -} - -static int mp_property_cache_free(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - if (!mpctx->demuxer) - return M_PROPERTY_UNAVAILABLE; - - struct stream_cache_info info = {0}; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); - if (info.size <= 0) - return M_PROPERTY_UNAVAILABLE; - - return property_int_kb_size((info.size - info.fill) / 1024, action, arg); -} - -static int mp_property_cache_speed(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - if (!mpctx->demuxer) - return M_PROPERTY_UNAVAILABLE; - - struct stream_cache_info info = {0}; - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); - if (info.size <= 0) - return M_PROPERTY_UNAVAILABLE; - - if (action == M_PROPERTY_PRINT) { - *(char **)arg = talloc_strdup_append(format_file_size(info.speed), "/s"); - return M_PROPERTY_OK; - } - return m_property_int64_ro(action, arg, info.speed); -} - -static int mp_property_cache_idle(void *ctx, struct m_property *prop, - int action, void *arg) -{ - MPContext *mpctx = ctx; - struct stream_cache_info info = {0}; - if (mpctx->demuxer) - demux_stream_control(mpctx->demuxer, STREAM_CTRL_GET_CACHE_INFO, &info); - if (info.size <= 0) - return M_PROPERTY_UNAVAILABLE; - return m_property_flag_ro(action, arg, info.idle); -} - static int mp_property_demuxer_cache_duration(void *ctx, struct m_property *prop, int action, void *arg) { @@ -2004,14 +1879,21 @@ static int mp_property_audio_device(void *ctx, struct m_property *prop, if (action == M_PROPERTY_PRINT) { create_hotplug(mpctx); + char *name = NULL; + 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); for (int n = 0; n < list->num_devices; n++) { struct ao_device_desc *dev = &list->devices[n]; - if (dev->name && strcmp(dev->name, mpctx->opts->audio_device) == 0) { + if (dev->name && name && strcmp(dev->name, name) == 0) { *(char **)arg = talloc_strdup(NULL, dev->desc ? dev->desc : "?"); + talloc_free(name); return M_PROPERTY_OK; } } + + talloc_free(name); } return mp_property_generic_option(mpctx, prop, action, arg); } @@ -2045,12 +1927,13 @@ static int mp_property_audio_delay(void *ctx, struct m_property *prop, case M_PROPERTY_PRINT: *(char **)arg = format_delay(delay); return M_PROPERTY_OK; - case M_PROPERTY_SET: - mpctx->opts->audio_delay = *(float *)arg; + case M_PROPERTY_SET: { + int r = mp_property_generic_option(mpctx, prop, action, arg); if (mpctx->ao_chain && mpctx->vo_chain) mpctx->delay += mpctx->opts->audio_delay - delay; mp_wakeup_core(mpctx); - return M_PROPERTY_OK; + return r; + } } return mp_property_generic_option(mpctx, prop, action, arg); } @@ -2414,18 +2297,20 @@ static int mp_property_hwdec(void *ctx, struct m_property *prop, MPContext *mpctx = ctx; struct track *track = mpctx->current_track[0][STREAM_VIDEO]; struct mp_decoder_wrapper *dec = track ? track->dec : NULL; - struct MPOpts *opts = mpctx->opts; if (action == M_PROPERTY_SET) { char *new = *(char **)arg; + char *old = NULL; + if (mp_property_generic_option(mpctx, prop, M_PROPERTY_GET, &old) < 1) + old = NULL; - if (strcmp(opts->hwdec_api, new) == 0) - return M_PROPERTY_OK; + bool same = bstr_equals(bstr0(old), bstr0(new)); + + mp_property_generic_option(mpctx, prop, M_PROPERTY_SET, &new); - talloc_free(opts->hwdec_api); - opts->hwdec_api = talloc_strdup(NULL, new); + talloc_free(old); - if (!dec) + if (!dec || same) return M_PROPERTY_OK; mp_decoder_wrapper_control(dec, VDCTRL_REINIT, NULL); @@ -3117,7 +3002,7 @@ static int mp_property_cursor_autohide(void *ctx, struct m_property *prop, static int prop_stream_ctrl(struct MPContext *mpctx, int ctrl, void *arg) { - if (!mpctx->demuxer) + if (!mpctx->demuxer || !mpctx->demuxer->extended_ctrls) return M_PROPERTY_UNAVAILABLE; int r = demux_stream_control(mpctx->demuxer, ctrl, arg); switch (r) { @@ -3730,12 +3615,13 @@ static int mp_property_option_info(void *ctx, struct m_property *prop, struct m_config_option *co = m_config_get_co(mpctx->mconfig, key); if (!co) return M_PROPERTY_UNKNOWN; + const struct m_option *opt = co->opt; union m_option_value def = {0}; - if (co->default_data) - memcpy(&def, co->default_data, co->opt->type->size); + const void *def_ptr = m_config_get_co_default(mpctx->mconfig, co); + if (def_ptr && opt->type->size > 0) + memcpy(&def, def_ptr, opt->type->size); - const struct m_option *opt = co->opt; bool has_minmax = opt->type == &m_option_type_int || opt->type == &m_option_type_int64 || @@ -3885,12 +3771,6 @@ static const struct m_property mp_properties_base[] = { {"eof-reached", mp_property_eof_reached}, {"seeking", mp_property_seeking}, {"playback-abort", mp_property_playback_abort}, - {"cache-percent", mp_property_cache}, - {"cache-free", mp_property_cache_free}, - {"cache-used", mp_property_cache_used}, - {"cache-size", mp_property_cache_size}, - {"cache-idle", mp_property_cache_idle}, - {"cache-speed", mp_property_cache_speed}, {"demuxer-cache-duration", mp_property_demuxer_cache_duration}, {"demuxer-cache-time", mp_property_demuxer_cache_time}, {"demuxer-cache-idle", mp_property_demuxer_cache_idle}, @@ -4827,19 +4707,165 @@ static void cmd_cycle_values(void *p) change_property_cmd(cmd, name, M_PROPERTY_SET_STRING, cmd->args[current].v.s); } -int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *res) +struct cmd_list_ctx { + struct MPContext *mpctx; + + // actual list command + struct mp_cmd_ctx *parent; + + bool current_valid; + pthread_t current; + bool completed_recursive; + + // list of sub commands yet to run + struct mp_cmd **sub; + int num_sub; +}; + +static void continue_cmd_list(struct cmd_list_ctx *list); + +static void on_cmd_list_sub_completion(struct mp_cmd_ctx *cmd) +{ + struct cmd_list_ctx *list = cmd->on_completion_priv; + + if (list->current_valid && pthread_equal(list->current, pthread_self())) { + list->completed_recursive = true; + } else { + continue_cmd_list(list); + } +} + +static void continue_cmd_list(struct cmd_list_ctx *list) +{ + while (list->parent->args[0].v.p) { + struct mp_cmd *sub = list->parent->args[0].v.p; + list->parent->args[0].v.p = sub->queue_next; + + ta_xset_parent(sub, NULL); + + if (sub->flags & MP_ASYNC_CMD) { + // We run it "detached" (fire & forget) + run_command(list->mpctx, sub, NULL, NULL, NULL); + } else { + // Run the next command once this one completes. + + list->completed_recursive = false; + list->current_valid = true; + list->current = pthread_self(); + + run_command(list->mpctx, sub, NULL, on_cmd_list_sub_completion, list); + + list->current_valid = false; + + // run_command() either recursively calls the completion function, + // or lets the command continue run in the background. If it was + // completed recursively, we can just continue our loop. Otherwise + // the completion handler will invoke this loop again elsewhere. + // We could unconditionally call continue_cmd_list() in the handler + // instead, but then stack depth would grow with list length. + if (!list->completed_recursive) + return; + } + } + + mp_cmd_ctx_complete(list->parent); + talloc_free(list); +} + +static void cmd_list(void *p) { - struct mpv_node dummy_node = {0}; - struct mp_cmd_ctx *ctx = &(struct mp_cmd_ctx){ + struct mp_cmd_ctx *cmd = p; + + cmd->completed = false; + + struct cmd_list_ctx *list = talloc_zero(NULL, struct cmd_list_ctx); + list->mpctx = cmd->mpctx; + list->parent = p; + + continue_cmd_list(list); +} + +const struct mp_cmd_def mp_cmd_list = { "list", cmd_list, .exec_async = true }; + +// Signal that the command is complete now. This also deallocates cmd. +// You must call this function in a state where the core is locked for the +// current thread (e.g. from the main thread, or from within mp_dispatch_lock()). +// Completion means the command is finished, even if it errored or never ran. +// Keep in mind that calling this can execute further user command that can +// change arbitrary state (due to cmd_list). +void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd) +{ + cmd->completed = true; + if (!cmd->success) + mpv_free_node_contents(&cmd->result); + if (cmd->on_completion) + cmd->on_completion(cmd); + if (cmd->abort) + mp_abort_remove(cmd->mpctx, cmd->abort); + mpv_free_node_contents(&cmd->result); + talloc_free(cmd); +} + +static void run_command_on_worker_thread(void *p) +{ + struct mp_cmd_ctx *ctx = p; + struct MPContext *mpctx = ctx->mpctx; + + mp_core_lock(mpctx); + + bool exec_async = ctx->cmd->def->exec_async; + ctx->cmd->def->handler(ctx); + if (!exec_async) + mp_cmd_ctx_complete(ctx); + + mpctx->outstanding_async -= 1; + if (!mpctx->outstanding_async && mp_is_shutting_down(mpctx)) + mp_wakeup_core(mpctx); + + mp_core_unlock(mpctx); +} + +// Run the given command. Upon command completion, on_completion is called. This +// can happen within the function, or for async commands, some time after the +// function returns (the caller is supposed to be able to handle both cases). In +// both cases, the callback will be called while the core is locked (i.e. you +// can access the core freely). +// If abort is non-NULL, then the caller creates the abort object. It must have +// been allocated with talloc. run_command() will register/unregister/destroy +// it. Must not be set if cmd->def->can_abort==false. +// on_completion_priv is copied to mp_cmd_ctx.on_completion_priv and can be +// accessed from the completion callback. +// The completion callback is invoked exactly once. If it's NULL, it's ignored. +// Ownership of cmd goes to the caller. +void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, + struct mp_abort_entry *abort, + void (*on_completion)(struct mp_cmd_ctx *cmd), + void *on_completion_priv) +{ + struct mp_cmd_ctx *ctx = talloc(NULL, struct mp_cmd_ctx); + *ctx = (struct mp_cmd_ctx){ .mpctx = mpctx, - .cmd = cmd, + .cmd = talloc_steal(ctx, cmd), .args = cmd->args, .num_args = cmd->nargs, .priv = cmd->def->priv, + .abort = talloc_steal(ctx, abort), .success = true, - .result = res ? res : &dummy_node, + .completed = true, + .on_completion = on_completion, + .on_completion_priv = on_completion_priv, }; + if (!ctx->abort && cmd->def->can_abort) + ctx->abort = talloc_zero(ctx, struct mp_abort_entry); + + assert(cmd->def->can_abort == !!ctx->abort); + + if (ctx->abort) { + ctx->abort->coupled_to_playback |= cmd->def->abort_on_playback_end; + mp_abort_add(mpctx, ctx->abort); + } + struct MPOpts *opts = mpctx->opts; ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS; bool auto_osd = ctx->on_osd == MP_ON_OSD_AUTO; @@ -4855,28 +4881,32 @@ int run_command(struct MPContext *mpctx, struct mp_cmd *cmd, struct mpv_node *re for (int n = 0; n < cmd->nargs; n++) { if (cmd->args[n].type->type == CONF_TYPE_STRING) { char *s = mp_property_expand_string(mpctx, cmd->args[n].v.s); - if (!s) - return -1; + if (!s) { + ctx->success = false; + mp_cmd_ctx_complete(ctx); + return; + } talloc_free(cmd->args[n].v.s); cmd->args[n].v.s = s; } } } - if (cmd->def == &mp_cmd_list) { - for (struct mp_cmd *sub = cmd->args[0].v.p; sub; sub = sub->queue_next) - run_command(mpctx, sub, NULL); + if (cmd->def->spawn_thread) { + mpctx->outstanding_async += 1; // prevent that core disappears + if (!mp_thread_pool_queue(mpctx->thread_pool, + run_command_on_worker_thread, ctx)) + { + mpctx->outstanding_async -= 1; + ctx->success = false; + mp_cmd_ctx_complete(ctx); + } } else { - assert(cmd->def->handler); + bool exec_async = cmd->def->exec_async; cmd->def->handler(ctx); + if (!exec_async) + mp_cmd_ctx_complete(ctx); } - - if (!ctx->success) - mpv_free_node_contents(ctx->result); - - mpv_free_node_contents(&dummy_node); - - return ctx->success ? 0 : -1; } static void cmd_seek(void *p) @@ -5197,7 +5227,7 @@ static void cmd_expand_text(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - *cmd->result = (mpv_node){ + cmd->result = (mpv_node){ .format = MPV_FORMAT_STRING, .u.string = mp_property_expand_string(mpctx, cmd->args[0].v.s) }; @@ -5237,7 +5267,8 @@ static void cmd_loadlist(void *p) char *filename = cmd->args[0].v.s; bool append = cmd->args[1].v.i; - struct playlist *pl = playlist_parse_file(filename, mpctx->global); + struct playlist *pl = playlist_parse_file(filename, cmd->abort->cancel, + mpctx->global); if (pl) { prepare_playlist(mpctx, pl); struct playlist_entry *new = pl->current; @@ -5355,7 +5386,7 @@ static void cmd_tv_last_channel(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - if (!mpctx->demuxer) { + if (!mpctx->demuxer || !mpctx->demuxer->extended_ctrls) { cmd->success = false; return; } @@ -5369,7 +5400,7 @@ static void cmd_track_add(void *p) struct MPContext *mpctx = cmd->mpctx; int type = *(int *)cmd->priv; - if (!mpctx->playing) { + if (mpctx->stop_play) { cmd->success = false; return; } @@ -5386,7 +5417,8 @@ static void cmd_track_add(void *p) return; } } - int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type); + int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, + cmd->abort->cancel); if (first < 0) { cmd->success = false; return; @@ -5450,7 +5482,7 @@ static void cmd_track_reload(void *p) if (t && t->is_external && t->external_filename) { char *filename = talloc_strdup(NULL, t->external_filename); mp_remove_track(mpctx, t); - nt_num = mp_add_external_file(mpctx, filename, type); + nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel); talloc_free(filename); } @@ -5469,13 +5501,13 @@ static void cmd_rescan_external_files(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - if (!mpctx->playing) { + if (mpctx->stop_play) { cmd->success = false; return; } - autoload_external_files(mpctx); - if (cmd->args[0].v.i && mpctx->playback_initialized) { + autoload_external_files(mpctx, cmd->abort->cancel); + if (!cmd->args[0].v.i && mpctx->playback_initialized) { // somewhat fuzzy and not ideal struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); if (a && a->is_external) @@ -5488,60 +5520,101 @@ static void cmd_rescan_external_files(void *p) } } -static void cmd_screenshot(void *p) +static void cmd_run(void *p) { struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - bool async = cmd->cmd->flags & MP_ASYNC_CMD; - int mode = cmd->args[0].v.i & 3; - int freq = (cmd->args[0].v.i | cmd->args[1].v.i) >> 3; - screenshot_request(mpctx, mode, freq, cmd->msg_osd, async); + char **args = talloc_zero_array(NULL, char *, cmd->num_args + 1); + for (int n = 0; n < cmd->num_args; n++) + args[n] = cmd->args[n].v.s; + mp_subprocess_detached(mpctx->log, args); + talloc_free(args); +} + +struct subprocess_cb_ctx { + struct mp_log *log; + void* talloc_ctx; + int64_t max_size; + bool capture[3]; + bstr output[3]; +}; + +static void subprocess_output(struct subprocess_cb_ctx *ctx, int fd, + char *data, size_t size) +{ + if (ctx->capture[fd]) { + if (ctx->output[fd].len < ctx->max_size) + bstr_xappend(ctx->talloc_ctx, &ctx->output[fd], (bstr){data, size}); + } else { + int msgl = fd == 2 ? MSGL_ERR : MSGL_INFO; + mp_msg(ctx->log, msgl, "%.*s", (int)size, data); + } } -static void cmd_screenshot_to_file(void *p) +static void subprocess_stdout(void *p, char *data, size_t size) { - struct mp_cmd_ctx *cmd = p; - struct MPContext *mpctx = cmd->mpctx; - bool async = cmd->cmd->flags & MP_ASYNC_CMD; - screenshot_to_file(mpctx, cmd->args[0].v.s, cmd->args[1].v.i, cmd->msg_osd, - async); + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 1, data, size); } -static void cmd_screenshot_raw(void *p) +static void subprocess_stderr(void *p, char *data, size_t size) +{ + struct subprocess_cb_ctx *ctx = p; + subprocess_output(ctx, 2, data, size); +} + +static void cmd_subprocess(void *p) { struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; - struct mpv_node *res = cmd->result; + char **args = cmd->args[0].v.str_list; + bool playback_only = cmd->args[1].v.i; - struct mp_image *img = screenshot_get_rgb(mpctx, cmd->args[0].v.i); - if (!img) { + if (!args || !args[0]) { + MP_ERR(mpctx, "program name missing\n"); cmd->success = false; return; } - node_init(res, MPV_FORMAT_NODE_MAP, NULL); - node_map_add_int64(res, "w", img->w); - node_map_add_int64(res, "h", img->h); - node_map_add_int64(res, "stride", img->stride[0]); - node_map_add_string(res, "format", "bgr0"); - struct mpv_byte_array *ba = - node_map_add(res, "data", MPV_FORMAT_BYTE_ARRAY)->u.ba; - *ba = (struct mpv_byte_array){ - .data = img->planes[0], - .size = img->stride[0] * img->h, + void *tmp = talloc_new(NULL); + struct subprocess_cb_ctx ctx = { + .log = mp_log_new(tmp, mpctx->log, cmd->cmd->sender), + .talloc_ctx = tmp, + .max_size = cmd->args[2].v.i, + .capture = {0, cmd->args[3].v.i, cmd->args[4].v.i}, }; - talloc_steal(ba, img); -} -static void cmd_run(void *p) -{ - struct mp_cmd_ctx *cmd = p; - struct MPContext *mpctx = cmd->mpctx; - char **args = talloc_zero_array(NULL, char *, cmd->num_args + 1); - for (int n = 0; n < cmd->num_args; n++) - args[n] = cmd->args[n].v.s; - mp_subprocess_detached(mpctx->log, args); - talloc_free(args); + pthread_mutex_lock(&mpctx->abort_lock); + cmd->abort->coupled_to_playback = playback_only; + mp_abort_recheck_locked(mpctx, cmd->abort); + pthread_mutex_unlock(&mpctx->abort_lock); + + mp_core_unlock(mpctx); + + char *error = NULL; + int status = mp_subprocess(args, cmd->abort->cancel, &ctx, + subprocess_stdout, subprocess_stderr, &error); + + mp_core_lock(mpctx); + + struct mpv_node *res = &cmd->result; + node_init(res, MPV_FORMAT_NODE_MAP, NULL); + node_map_add_int64(res, "status", status); + node_map_add_flag(res, "killed_by_us", status == MP_SUBPROCESS_EKILLED_BY_US); + node_map_add_string(res, "error_string", error ? error : ""); + const char *sname[] = {NULL, "stdout", "stderr"}; + for (int n = 1; n < 3; n++) { + if (!ctx.capture[n]) + continue; + struct mpv_byte_array *ba = + node_map_add(res, sname[n], MPV_FORMAT_BYTE_ARRAY)->u.ba; + *ba = (struct mpv_byte_array){ + .data = talloc_steal(ba, ctx.output[n].start), + .size = ctx.output[n].len, + }; + } + + talloc_free(tmp); } static void cmd_enable_input_section(void *p) @@ -5563,7 +5636,7 @@ static void cmd_define_input_section(void *p) struct mp_cmd_ctx *cmd = p; struct MPContext *mpctx = cmd->mpctx; mp_input_define_section(mpctx->input, cmd->args[0].v.s, "<api>", - cmd->args[1].v.s, !!cmd->args[2].v.i, + cmd->args[1].v.s, !cmd->args[2].v.i, cmd->cmd->sender); } @@ -5816,239 +5889,331 @@ static void cmd_load_script(void *p) cmd->success = false; } -// This does not specify the real destination of the command parameter values, -// it just provides a dummy for the OPT_ macros. -#define OPT_BASE_STRUCT struct mp_cmd_arg -#define ARG(t) "", v. t - /* This array defines all known commands. - * The first field is an id used to recognize the command. - * The second is the command name used in slave mode and input.conf. - * Then comes the definition of each argument, first mandatory arguments - * (ARG_INT, ARG_FLOAT, ARG_STRING) if any, then optional arguments - * (OARG_INT(default), etc) if any. The command will be given the default - * argument value if the user didn't give enough arguments to specify it. - * A command can take a maximum of MP_CMD_DEF_MAX_ARGS arguments, or more - * if the command uses varargs. + * The first field the command name used in libmpv and input.conf. + * The second field is the handler function (see mp_cmd_def.handler and + * run_command()). + * Then comes the definition of each argument. They are defined like options, + * except that the result is parsed into mp_cmd.args[] (thus the option variable + * is a field in the mp_cmd_arg union field). Arguments are optional if either + * defval is set (usually via OPTDEF_ macros), or the MP_CMD_OPT_ARG flag is + * set, or if it's the last argument and .vararg is set. If .vararg is set, the + * command has an arbitrary number of arguments, all using the type indicated by + * the last argument (they are appended to mp_cmd.args[] starting at the last + * argument's index). + * Arguments have named, which can be used by named argument functions, e.g. in + * Lua with mp.command_native(). */ -#define ARG_INT OPT_INT(ARG(i), 0) -#define ARG_FLOAT OPT_FLOAT(ARG(f), 0) -#define ARG_DOUBLE OPT_DOUBLE(ARG(d), 0) -#define ARG_STRING OPT_STRING(ARG(s), 0) -#define ARG_CHOICE(c) OPT_CHOICE(ARG(i), 0, c) -#define ARG_CHOICE_OR_INT(...) OPT_CHOICE_OR_INT(ARG(i), 0, __VA_ARGS__) -#define ARG_TIME OPT_TIME(ARG(d), 0) -#define OARG_DOUBLE(def) OPT_DOUBLE(ARG(d), 0, OPTDEF_DOUBLE(def)) -#define OARG_INT(def) OPT_INT(ARG(i), 0, OPTDEF_INT(def)) -#define OARG_CHOICE(def, c) OPT_CHOICE(ARG(i), 0, c, OPTDEF_INT(def)) -#define OARG_FLAGS(def, c) OPT_FLAGS(ARG(i), 0, c, OPTDEF_INT(def)) -#define OARG_STRING(def) OPT_STRING(ARG(s), 0, OPTDEF_STR(def)) - -#define OARG_CYCLEDIR(def) OPT_CYCLEDIR(ARG(d), 0, OPTDEF_DOUBLE(def)) +// This does not specify the real destination of the command parameter values, +// it just provides a dummy for the OPT_ macros. The real destination is an +// array item in mp_cmd.args[], using the index of the option definition. +#define OPT_BASE_STRUCT struct mp_cmd_arg const struct mp_cmd_def mp_cmds[] = { { "ignore", cmd_ignore, .is_ignore = true }, - { "seek", cmd_seek, { - ARG_TIME, - OARG_FLAGS(4|0, ({"relative", 4|0}, {"-", 4|0}, - {"absolute-percent", 4|1}, - {"absolute", 4|2}, - {"relative-percent", 4|3}, - {"keyframes", 32|8}, - {"exact", 32|16})), - // backwards compatibility only - OARG_CHOICE(0, ({"unused", 0}, {"default-precise", 0}, + { "seek", cmd_seek, + { + OPT_TIME("target", v.d, 0), + OPT_FLAGS("flags", v.i, 0, + ({"relative", 4|0}, {"-", 4|0}, + {"absolute-percent", 4|1}, + {"absolute", 4|2}, + {"relative-percent", 4|3}, + {"keyframes", 32|8}, + {"exact", 32|16}), + OPTDEF_INT(4|0)), + // backwards compatibility only + OPT_CHOICE("legacy", v.i, MP_CMD_OPT_ARG, + ({"unused", 0}, {"default-precise", 0}, {"keyframes", 32|8}, {"exact", 32|16})), }, .allow_auto_repeat = true, .scalable = true, }, - { "revert-seek", cmd_revert_seek, { - OARG_FLAGS(0, ({"mark", 1})), - }}, - { "quit", cmd_quit, { OARG_INT(0) }, - .priv = &(const bool){0}, .is_abort = true }, - { "quit-watch-later", cmd_quit, { OARG_INT(0) }, - .priv = &(const bool){1}, .is_abort = true }, - { "stop", cmd_stop, .is_abort = true }, + { "revert-seek", cmd_revert_seek, + {OPT_FLAGS("flags", v.i, MP_CMD_OPT_ARG, ({"mark", 1}))}, + }, + { "quit", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) }, + .priv = &(const bool){0} }, + { "quit-watch-later", cmd_quit, { OPT_INT("code", v.i, MP_CMD_OPT_ARG) }, + .priv = &(const bool){1} }, + { "stop", cmd_stop, }, { "frame-step", cmd_frame_step, .allow_auto_repeat = true, .on_updown = true }, { "frame-back-step", cmd_frame_back_step, .allow_auto_repeat = true }, - { "playlist-next", cmd_playlist_next_prev, { - OARG_CHOICE(0, ({"weak", 0}, - {"force", 1})), + { "playlist-next", cmd_playlist_next_prev, + { + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0}, + {"force", 1})), }, - .is_soft_abort = true, .priv = &(const int){1}, + .priv = &(const int){1}, }, - { "playlist-prev", cmd_playlist_next_prev, { - OARG_CHOICE(0, ({"weak", 0}, - {"force", 1})), + { "playlist-prev", cmd_playlist_next_prev, + { + OPT_CHOICE("flags", v.i, MP_CMD_OPT_ARG, ({"weak", 0}, + {"force", 1})), }, - .is_soft_abort = true, .priv = &(const int){-1}, + .priv = &(const int){-1}, }, { "playlist-shuffle", cmd_playlist_shuffle, }, - { "sub-step", cmd_sub_step_seek, { ARG_INT }, .allow_auto_repeat = true, - .priv = &(const bool){true} }, - { "sub-seek", cmd_sub_step_seek, { ARG_INT }, .allow_auto_repeat = true, - .priv = &(const bool){false} }, - { "print-text", cmd_print_text, { ARG_STRING }, .allow_auto_repeat = true }, - { "show-text", cmd_show_text, { ARG_STRING, OARG_INT(-1), OARG_INT(0) }, + { "sub-step", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) }, + .allow_auto_repeat = true, .priv = &(const bool){true} }, + { "sub-seek", cmd_sub_step_seek, { OPT_INT("skip", v.i, 0) }, + .allow_auto_repeat = true, .priv = &(const bool){false} }, + { "print-text", cmd_print_text, { OPT_STRING("text", v.s, 0) }, + .allow_auto_repeat = true }, + { "show-text", cmd_show_text, { OPT_STRING("text", v.s, 0), + OPT_INT("duration", v.i, 0, OPTDEF_INT(-1)), + OPT_INT("level", v.i, MP_CMD_OPT_ARG), }, .allow_auto_repeat = true}, - { "expand-text", cmd_expand_text, { ARG_STRING } }, + { "expand-text", cmd_expand_text, { OPT_STRING("text", v.s, 0) } }, { "show-progress", cmd_show_progress, .allow_auto_r |