/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <float.h>#include <stdlib.h>#include <inttypes.h>#include <string.h>#include <stdbool.h>#include <assert.h>#include <time.h>#include <math.h>#include <sys/types.h> #include <ass/ass.h>#include <libavutil/avstring.h>#include <libavutil/common.h>#include <libavutil/timecode.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"#include "input/keycodes.h"#include "stream/stream.h"#include "demux/demux.h"#include "demux/stheader.h"#include "common/playlist.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_frontend.h"#include "options/parse_configfile.h"#include "osdep/getpid.h"#include "video/out/vo.h"#include "video/csputils.h"#include "video/hwdec.h"#include "audio/aframe.h"#include "audio/format.h"#include "audio/out/ao.h"#include "video/out/bitmap_packer.h"#include "options/path.h"#include "screenshot.h"#include "misc/dispatch.h"#include "misc/language.h"#include "misc/node.h"#include "misc/thread_pool.h"#include "misc/thread_tools.h" #include "osdep/io.h"#include "osdep/subprocess.h"#include "osdep/terminal.h" #ifdef _WIN32#include <windows.h>#endif struct command_ctx { // All properties, terminated with a {0} item. struct m_property *properties; 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 // bitmap list can be manipulated without additional synchronization. struct sub_bitmaps overlay_osd[2]; int overlay_osd_current; struct bitmap_packer *overlay_packer; struct hook_handler **hooks; int num_hooks; int64_t hook_seq; // for hook_handler.seq struct ao_hotplug *hotplug; struct mp_cmd_ctx *cache_dump_cmd; // in progress cache dumping char **script_props; mpv_node udata; mpv_node mdata; double cached_window_scale;}; 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 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 active; // hook is currently in progress (only 1 at a time for now)}; 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+00A0 NO-BREAK SPACE#define NBSP "\xc2\xa0" const char list_current[] = BLACK_CIRCLE NBSP;const char list_normal[] = WHITE_CIRCLE NBSP; static int edit_filters(struct MPContext *mpctx, struct mp_log *log, 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 bool is_property_set(int action, void *val); static void hook_remove(struct MPContext *mpctx, struct hook_handler *h){ struct command_ctx *cmd = mpctx->command_ctx; for (int n = 0; n < cmd->num_hooks; n++) { if (cmd->hooks[n] == h) { talloc_free(cmd->hooks[n]); MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, n); return; } } MP_ASSERT_UNREACHABLE();} bool mp_hook_test_completion(struct MPContext *mpctx, char *type){ struct command_ctx *cmd = mpctx->command_ctx; 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_id_exists(mpctx, h->client_id)) { MP_WARN(mpctx, "client removed during hook handling\n"); hook_remove(mpctx, h); break; } return false; } } return true;} static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h){ MP_VERBOSE(mpctx, "Running hook: %s/%s\n", h->client, h->type); h->active = true; uint64_t reply_id = 0; 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_MSG(mpctx, mp_client_id_exists(mpctx, h->client_id) ? MSGL_WARN : MSGL_V, "Failed sending hook command %s/%s. Removing hook.\n", h->client, h->type); hook_remove(mpctx, h); mp_wakeup_core(mpctx); // repeat next iteration to finish } return r;} static int run_next_hook_handler(struct MPContext *mpctx, char *type, int index){ struct command_ctx *cmd = mpctx->command_ctx; for (int n = index; n < cmd->num_hooks; n++) { struct hook_handler *h = cmd->hooks[n]; if (strcmp(h->type, type) == 0) return invoke_hook_handler(mpctx, h); } mp_wakeup_core(mpctx); // finished hook return 0;} // Start processing script/client API hooks. This is asynchronous, and the// caller needs to use mp_hook_test_completion() to check whether they're done.void mp_hook_start(struct MPContext *mpctx, char *type){ while (run_next_hook_handler(mpctx, type, 0) < 0) { // We can repeat this until all broken clients have been removed, and // hook processing is successfully started. }} 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 (h->client_id == client_id && h->seq == id) { if (!h->active) break; h->active = false; return run_next_hook_handler(mpctx, h->type, n + 1); } } MP_ERR(mpctx, "invalid hook API usage\n"); return MPV_ERROR_INVALID_PARAMETER;} static int compare_hook(const void *pa, const void *pb){ struct hook_handler **h1 = (void *)pa; struct hook_handler **h2 = (void *)pb; if ((*h1)->priority != (*h2)->priority) return (*h1)->priority - (*h2)->priority; return (*h1)->seq - (*h2)->seq;} void mp_hook_add(struct MPContext *mpctx, char *client, int64_t client_id, const char *name, uint64_t user_id, int pri){ 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, }; MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook);} // Call before a seek, in order to allow revert-seek to undo the seek.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 *skip_n_lines(char *text, int lines){ while (text && lines > 0) { char *next = strchr(text, '\n'); text = next ? next + 1 : NULL; lines--; } return text;} static int count_lines(char *text){ if (!text[0]) return 0; int count = 0; while (text) { count++; char *next = strchr(text, '\n'); if (!next || (next[0] == '\n' && !next[1])) break; text = next + 1; } return count;} // Given a huge string separated by new lines, attempts to cut off text above// the current line to keep the line visible, and below to keep rendering// performance up. pos gives the current line (0 for the first line).// "text" might be returned as is, or it can be freed and a new allocation is// returned.// This is only a heuristic - we can't deal with line breaking.static char *cut_osd_list(struct MPContext *mpctx, char *header, char *text, int pos){ int count = count_lines(text); if (!count) return text; int max_lines; if (mpctx->video_out && mpctx->opts->video_osd) { int screen_h, font_h; osd_get_text_size(mpctx->osd, &screen_h, &font_h); max_lines = screen_h / MPMAX(font_h, 1); } else { int w = -1; max_lines = 24; terminal_get_size(&w, &max_lines); char *msg = mp_property_expand_escaped_string(mpctx, mpctx->opts->status_msg); max_lines -= msg[0] ? count_lines(msg) : 1; talloc_free(msg); } // Subtract 1 for the header. max_lines--; char *new = talloc_asprintf(NULL, "%s [%d/%d]:\n", header, pos + 1, count); int start = MPMIN(MPMAX(pos - max_lines / 2, 0), count - max_lines); char *head = skip_n_lines(text, start); char *tail = skip_n_lines(head, max_lines); new = talloc_asprintf_append_buffer(new, "%.*s", (int)(tail ? tail - head : strlen(head)), head); // Strip the final newline to not print it in the terminal. new[strlen(new) - 1] = '\0'; talloc_free(text); return new;} static char *format_delay(double time){ return talloc_asprintf(NULL, "%.f ms", time * 1000);} // Property-option bridge. (Maps the property to the option with the same name.)static int mp_property_generic_option(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; struct m_config_option *opt = m_config_get_co(mpctx->mconfig, bstr0(prop->name)); if (!opt) return M_PROPERTY_UNKNOWN; switch (action) { case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = *(opt->opt); return M_PROPERTY_OK; case M_PROPERTY_GET: if (!opt->data) return M_PROPERTY_NOT_IMPLEMENTED; m_option_copy(opt->opt, arg, opt->data); return M_PROPERTY_OK; case M_PROPERTY_SET: if (m_config_set_option_raw(mpctx->mconfig, opt, arg, 0) < 0) return M_PROPERTY_ERROR; return M_PROPERTY_OK; } return M_PROPERTY_NOT_IMPLEMENTED;} /// Playback speed (RW)static int mp_property_playback_speed(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; 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);} /// Playback pitch (RW)static int mp_property_playback_pitch(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_pitch, 2, false, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg);} static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; char *type = prop->priv; double val = 0; switch (type[0]) { case 'a': val = mpctx->speed_factor_a; break; case 'v': val = mpctx->speed_factor_v; break; default: MP_ASSERT_UNREACHABLE(); } 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; } return m_property_double_ro(action, arg, val);} static int mp_property_display_sync_active(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; 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)static int mp_property_path(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; if (!mpctx->filename) return M_PROPERTY_UNAVAILABLE; return m_property_strdup_ro(action, arg, mpctx->filename);} static int mp_property_filename(void *ctx, struct m_property *prop, int action, void *arg){ MPContext *mpctx = ctx; 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); if (!f[0]) f = filename; if (action == M_PROPERTY_KEY_ACTION) { struct m_property_action_arg *ka = arg; if (strcmp(ka->key, "no-ext") == 0) { action = ka->action; arg = ka->arg; bstr root; if (mp_splitext(f, &root)) f = bstrto0(filename, root); } } int r = m_property_strdup_ro(action, arg, f);Lines 1–500 of 7710. Load more
/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */ #include <float.h> #include <stdlib.h> #include <inttypes.h> #include <string.h> #include <stdbool.h> #include <assert.h> #include <time.h> #include <math.h> #include <sys/types.h> #include <ass/ass.h> #include <libavutil/avstring.h> #include <libavutil/common.h> #include <libavutil/timecode.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" #include "input/keycodes.h" #include "stream/stream.h" #include "demux/demux.h" #include "demux/stheader.h" #include "common/playlist.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_frontend.h" #include "options/parse_configfile.h" #include "osdep/getpid.h" #include "video/out/vo.h" #include "video/csputils.h" #include "video/hwdec.h" #include "audio/aframe.h" #include "audio/format.h" #include "audio/out/ao.h" #include "video/out/bitmap_packer.h" #include "options/path.h" #include "screenshot.h" #include "misc/dispatch.h" #include "misc/language.h" #include "misc/node.h" #include "misc/thread_pool.h" #include "misc/thread_tools.h" #include "osdep/io.h" #include "osdep/subprocess.h" #include "osdep/terminal.h" #ifdef _WIN32 #include <windows.h> #endif struct command_ctx { // All properties, terminated with a {0} item. struct m_property *properties; 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 // bitmap list can be manipulated without additional synchronization. struct sub_bitmaps overlay_osd[2]; int overlay_osd_current; struct bitmap_packer *overlay_packer; struct hook_handler **hooks; int num_hooks; int64_t hook_seq; // for hook_handler.seq struct ao_hotplug *hotplug; struct mp_cmd_ctx *cache_dump_cmd; // in progress cache dumping char **script_props; mpv_node udata; mpv_node mdata; double cached_window_scale; }; 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 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 active; // hook is currently in progress (only 1 at a time for now) }; 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+00A0 NO-BREAK SPACE #define NBSP "\xc2\xa0" const char list_current[] = BLACK_CIRCLE NBSP; const char list_normal[] = WHITE_CIRCLE NBSP; static int edit_filters(struct MPContext *mpctx, struct mp_log *log, 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 bool is_property_set(int action, void *val); static void hook_remove(struct MPContext *mpctx, struct hook_handler *h) { struct command_ctx *cmd = mpctx->command_ctx; for (int n = 0; n < cmd->num_hooks; n++) { if (cmd->hooks[n] == h) { talloc_free(cmd->hooks[n]); MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, n); return; } } MP_ASSERT_UNREACHABLE(); } bool mp_hook_test_completion(struct MPContext *mpctx, char *type) { struct command_ctx *cmd = mpctx->command_ctx; 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_id_exists(mpctx, h->client_id)) { MP_WARN(mpctx, "client removed during hook handling\n"); hook_remove(mpctx, h); break; } return false; } } return true; } static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h) { MP_VERBOSE(mpctx, "Running hook: %s/%s\n", h->client, h->type); h->active = true; uint64_t reply_id = 0; 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_MSG(mpctx, mp_client_id_exists(mpctx, h->client_id) ? MSGL_WARN : MSGL_V, "Failed sending hook command %s/%s. Removing hook.\n", h->client, h->type); hook_remove(mpctx, h); mp_wakeup_core(mpctx); // repeat next iteration to finish } return r; } static int run_next_hook_handler(struct MPContext *mpctx, char *type, int index) { struct command_ctx *cmd = mpctx->command_ctx; for (int n = index; n < cmd->num_hooks; n++) { struct hook_handler *h = cmd->hooks[n]; if (strcmp(h->type, type) == 0) return invoke_hook_handler(mpctx, h); } mp_wakeup_core(mpctx); // finished hook return 0; } // Start processing script/client API hooks. This is asynchronous, and the // caller needs to use mp_hook_test_completion() to check whether they're done. void mp_hook_start(struct MPContext *mpctx, char *type) { while (run_next_hook_handler(mpctx, type, 0) < 0) { // We can repeat this until all broken clients have been removed, and // hook processing is successfully started. } } 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 (h->client_id == client_id && h->seq == id) { if (!h->active) break; h->active = false; return run_next_hook_handler(mpctx, h->type, n + 1); } } MP_ERR(mpctx, "invalid hook API usage\n"); return MPV_ERROR_INVALID_PARAMETER; } static int compare_hook(const void *pa, const void *pb) { struct hook_handler **h1 = (void *)pa; struct hook_handler **h2 = (void *)pb; if ((*h1)->priority != (*h2)->priority) return (*h1)->priority - (*h2)->priority; return (*h1)->seq - (*h2)->seq; } void mp_hook_add(struct MPContext *mpctx, char *client, int64_t client_id, const char *name, uint64_t user_id, int pri) { 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, }; MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook); } // Call before a seek, in order to allow revert-seek to undo the seek. 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 *skip_n_lines(char *text, int lines) { while (text && lines > 0) { char *next = strchr(text, '\n'); text = next ? next + 1 : NULL; lines--; } return text; } static int count_lines(char *text) { if (!text[0]) return 0; int count = 0; while (text) { count++; char *next = strchr(text, '\n'); if (!next || (next[0] == '\n' && !next[1])) break; text = next + 1; } return count; } // Given a huge string separated by new lines, attempts to cut off text above // the current line to keep the line visible, and below to keep rendering // performance up. pos gives the current line (0 for the first line). // "text" might be returned as is, or it can be freed and a new allocation is // returned. // This is only a heuristic - we can't deal with line breaking. static char *cut_osd_list(struct MPContext *mpctx, char *header, char *text, int pos) { int count = count_lines(text); if (!count) return text; int max_lines; if (mpctx->video_out && mpctx->opts->video_osd) { int screen_h, font_h; osd_get_text_size(mpctx->osd, &screen_h, &font_h); max_lines = screen_h / MPMAX(font_h, 1); } else { int w = -1; max_lines = 24; terminal_get_size(&w, &max_lines); char *msg = mp_property_expand_escaped_string(mpctx, mpctx->opts->status_msg); max_lines -= msg[0] ? count_lines(msg) : 1; talloc_free(msg); } // Subtract 1 for the header. max_lines--; char *new = talloc_asprintf(NULL, "%s [%d/%d]:\n", header, pos + 1, count); int start = MPMIN(MPMAX(pos - max_lines / 2, 0), count - max_lines); char *head = skip_n_lines(text, start); char *tail = skip_n_lines(head, max_lines); new = talloc_asprintf_append_buffer(new, "%.*s", (int)(tail ? tail - head : strlen(head)), head); // Strip the final newline to not print it in the terminal. new[strlen(new) - 1] = '\0'; talloc_free(text); return new; } static char *format_delay(double time) { return talloc_asprintf(NULL, "%.f ms", time * 1000); } // Property-option bridge. (Maps the property to the option with the same name.) static int mp_property_generic_option(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; struct m_config_option *opt = m_config_get_co(mpctx->mconfig, bstr0(prop->name)); if (!opt) return M_PROPERTY_UNKNOWN; switch (action) { case M_PROPERTY_GET_TYPE: *(struct m_option *)arg = *(opt->opt); return M_PROPERTY_OK; case M_PROPERTY_GET: if (!opt->data) return M_PROPERTY_NOT_IMPLEMENTED; m_option_copy(opt->opt, arg, opt->data); return M_PROPERTY_OK; case M_PROPERTY_SET: if (m_config_set_option_raw(mpctx->mconfig, opt, arg, 0) < 0) return M_PROPERTY_ERROR; return M_PROPERTY_OK; } return M_PROPERTY_NOT_IMPLEMENTED; } /// Playback speed (RW) static int mp_property_playback_speed(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; 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); } /// Playback pitch (RW) static int mp_property_playback_pitch(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_pitch, 2, false, false, action != M_PROPERTY_FIXED_LEN_PRINT); return M_PROPERTY_OK; } return mp_property_generic_option(mpctx, prop, action, arg); } static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; char *type = prop->priv; double val = 0; switch (type[0]) { case 'a': val = mpctx->speed_factor_a; break; case 'v': val = mpctx->speed_factor_v; break; default: MP_ASSERT_UNREACHABLE(); } 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; } return m_property_double_ro(action, arg, val); } static int mp_property_display_sync_active(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; 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) static int mp_property_path(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; if (!mpctx->filename) return M_PROPERTY_UNAVAILABLE; return m_property_strdup_ro(action, arg, mpctx->filename); } static int mp_property_filename(void *ctx, struct m_property *prop, int action, void *arg) { MPContext *mpctx = ctx; 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); if (!f[0]) f = filename; if (action == M_PROPERTY_KEY_ACTION) { struct m_property_action_arg *ka = arg; if (strcmp(ka->key, "no-ext") == 0) { action = ka->action; arg = ka->arg; bstr root; if (mp_splitext(f, &root)) f = bstrto0(filename, root); } } int r = m_property_strdup_ro(action, arg, f);