/src/mpv/player/command.c
Line | Count | Source |
1 | | /* |
2 | | * This file is part of mpv. |
3 | | * |
4 | | * mpv is free software; you can redistribute it and/or |
5 | | * modify it under the terms of the GNU Lesser General Public |
6 | | * License as published by the Free Software Foundation; either |
7 | | * version 2.1 of the License, or (at your option) any later version. |
8 | | * |
9 | | * mpv is distributed in the hope that it will be useful, |
10 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
12 | | * GNU Lesser General Public License for more details. |
13 | | * |
14 | | * You should have received a copy of the GNU Lesser General Public |
15 | | * License along with mpv. If not, see <http://www.gnu.org/licenses/>. |
16 | | */ |
17 | | |
18 | | #include <float.h> |
19 | | #include <stdlib.h> |
20 | | #include <inttypes.h> |
21 | | #include <string.h> |
22 | | #include <stdbool.h> |
23 | | #include <assert.h> |
24 | | #include <time.h> |
25 | | #include <math.h> |
26 | | #include <sys/types.h> |
27 | | |
28 | | #include "config.h" // for HAVE_SUBRANDR |
29 | | |
30 | | #include <ass/ass.h> |
31 | | #if HAVE_SUBRANDR |
32 | | #include <subrandr/subrandr.h> |
33 | | #endif |
34 | | #include <libavutil/avstring.h> |
35 | | #include <libavutil/common.h> |
36 | | #include <libavutil/timecode.h> |
37 | | |
38 | | #include "mpv_talloc.h" |
39 | | #include "client.h" |
40 | | #include "clipboard/clipboard.h" |
41 | | #include "external_files.h" |
42 | | #include "common/av_common.h" |
43 | | #include "common/codecs.h" |
44 | | #include "common/msg.h" |
45 | | #include "common/msg_control.h" |
46 | | #include "common/stats.h" |
47 | | #include "filters/f_decoder_wrapper.h" |
48 | | #include "command.h" |
49 | | #include "osdep/als.h" |
50 | | #include "osdep/threads.h" |
51 | | #include "osdep/timer.h" |
52 | | #include "common/common.h" |
53 | | #include "input/input.h" |
54 | | #include "input/keycodes.h" |
55 | | #include "sub/osd_state.h" |
56 | | #include "stream/stream.h" |
57 | | #include "demux/demux.h" |
58 | | #include "demux/stheader.h" |
59 | | #include "common/playlist.h" |
60 | | #include "sub/dec_sub.h" |
61 | | #include "sub/osd.h" |
62 | | #include "sub/sd.h" |
63 | | #include "options/m_option.h" |
64 | | #include "options/m_property.h" |
65 | | #include "options/m_config_frontend.h" |
66 | | #include "options/parse_configfile.h" |
67 | | #include "osdep/getpid.h" |
68 | | #include "video/out/vo.h" |
69 | | #include "video/csputils.h" |
70 | | #include "video/hwdec.h" |
71 | | #include "audio/aframe.h" |
72 | | #include "audio/format.h" |
73 | | #include "audio/out/ao.h" |
74 | | #include "video/out/bitmap_packer.h" |
75 | | #include "options/path.h" |
76 | | #include "screenshot.h" |
77 | | #include "misc/dispatch.h" |
78 | | #include "misc/language.h" |
79 | | #include "misc/node.h" |
80 | | #include "misc/thread_pool.h" |
81 | | #include "misc/thread_tools.h" |
82 | | |
83 | | #include "osdep/io.h" |
84 | | #include "osdep/subprocess.h" |
85 | | #include "osdep/terminal.h" |
86 | | |
87 | | #include "core.h" |
88 | | |
89 | | #ifdef _WIN32 |
90 | | #include <windows.h> |
91 | | #endif |
92 | | |
93 | | struct command_ctx { |
94 | | // All properties, sorted by name. |
95 | | struct m_property_list properties; |
96 | | |
97 | | double last_seek_time; |
98 | | double last_seek_pts; |
99 | | double marked_pts; |
100 | | bool marked_permanent; |
101 | | |
102 | | char **warned_deprecated; |
103 | | int num_warned_deprecated; |
104 | | |
105 | | bool command_opts_processed; |
106 | | |
107 | | struct overlay *overlays; |
108 | | int num_overlays; |
109 | | // One of these is in use by the OSD; the other one exists so that the |
110 | | // bitmap list can be manipulated without additional synchronization. |
111 | | struct sub_bitmaps overlay_osd[2]; |
112 | | int overlay_osd_current; |
113 | | struct bitmap_packer *overlay_packer; |
114 | | |
115 | | struct hook_handler **hooks; |
116 | | int num_hooks; |
117 | | int64_t hook_seq; // for hook_handler.seq |
118 | | |
119 | | struct ao_hotplug *hotplug; |
120 | | |
121 | | struct mp_cmd_ctx *cache_dump_cmd; // in progress cache dumping |
122 | | |
123 | | char **script_props; |
124 | | mpv_node udata; |
125 | | mpv_node mdata; |
126 | | |
127 | | int hwdec_osd_mode; |
128 | | |
129 | | double cached_window_scale; |
130 | | }; |
131 | | |
132 | | static const struct m_option script_props_type = { |
133 | | .type = &m_option_type_keyvalue_list |
134 | | }; |
135 | | |
136 | | static const struct m_option udata_type = { |
137 | | .type = CONF_TYPE_NODE |
138 | | }; |
139 | | |
140 | | static const struct m_option mdata_type = { |
141 | | .type = CONF_TYPE_NODE |
142 | | }; |
143 | | |
144 | | struct overlay { |
145 | | struct mp_image *source; |
146 | | int x, y; |
147 | | int dw, dh; |
148 | | enum pl_color_primaries primaries; |
149 | | enum pl_color_transfer transfer; |
150 | | float max_luma; |
151 | | bool video_colorspace; |
152 | | }; |
153 | | |
154 | | struct hook_handler { |
155 | | char *client; // client mpv_handle name (for logging) |
156 | | int64_t client_id; // client mpv_handle ID |
157 | | char *type; // kind of hook, e.g. "on_load" |
158 | | uint64_t user_id; // user-chosen ID |
159 | | int priority; // priority for global hook order |
160 | | int64_t seq; // unique ID, != 0, also for fixed order on equal priorities |
161 | | bool active; // hook is currently in progress (only 1 at a time for now) |
162 | | }; |
163 | | |
164 | | enum load_action_type { |
165 | | LOAD_TYPE_REPLACE, |
166 | | LOAD_TYPE_INSERT_AT, |
167 | | LOAD_TYPE_INSERT_NEXT, |
168 | | LOAD_TYPE_APPEND, |
169 | | }; |
170 | | |
171 | | struct load_action { |
172 | | enum load_action_type type; |
173 | | bool play; |
174 | | }; |
175 | | |
176 | | // U+00A0 NO-BREAK SPACE |
177 | | #define NBSP "\xc2\xa0" |
178 | | |
179 | | const char list_current[] = BLACK_CIRCLE NBSP; |
180 | | const char list_normal[] = WHITE_CIRCLE NBSP; |
181 | | |
182 | | static int edit_filters(struct MPContext *mpctx, struct mp_log *log, |
183 | | enum stream_type mediatype, |
184 | | const char *cmd, const char *arg); |
185 | | static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, |
186 | | struct m_obj_settings *new_chain); |
187 | | |
188 | | static bool is_property_set(int action, void *val); |
189 | | |
190 | | static void hook_remove(struct MPContext *mpctx, struct hook_handler *h) |
191 | 0 | { |
192 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
193 | 0 | for (int n = 0; n < cmd->num_hooks; n++) { |
194 | 0 | if (cmd->hooks[n] == h) { |
195 | 0 | talloc_free(cmd->hooks[n]); |
196 | 0 | MP_TARRAY_REMOVE_AT(cmd->hooks, cmd->num_hooks, n); |
197 | 0 | return; |
198 | 0 | } |
199 | 0 | } |
200 | 0 | MP_ASSERT_UNREACHABLE(); |
201 | 0 | } |
202 | | |
203 | | bool mp_hook_test_completion(struct MPContext *mpctx, char *type) |
204 | 2.15M | { |
205 | 2.15M | struct command_ctx *cmd = mpctx->command_ctx; |
206 | 2.15M | for (int n = 0; n < cmd->num_hooks; n++) { |
207 | 0 | struct hook_handler *h = cmd->hooks[n]; |
208 | 0 | if (h->active && strcmp(h->type, type) == 0) { |
209 | 0 | if (!mp_client_id_exists(mpctx, h->client_id)) { |
210 | 0 | MP_WARN(mpctx, "client removed during hook handling\n"); |
211 | | // Trigger completion of this hook and continue with the next one. |
212 | 0 | mp_hook_continue(mpctx, h->client_id, h->seq); |
213 | 0 | hook_remove(mpctx, h); |
214 | 0 | } |
215 | 0 | return false; |
216 | 0 | } |
217 | 0 | } |
218 | 2.15M | return true; |
219 | 2.15M | } |
220 | | |
221 | | static int invoke_hook_handler(struct MPContext *mpctx, struct hook_handler *h) |
222 | 0 | { |
223 | 0 | MP_VERBOSE(mpctx, "Running hook: %s/%s\n", h->client, h->type); |
224 | 0 | h->active = true; |
225 | |
|
226 | 0 | uint64_t reply_id = 0; |
227 | 0 | mpv_event_hook *m = talloc_ptrtype(NULL, m); |
228 | 0 | *m = (mpv_event_hook){ |
229 | 0 | .name = talloc_strdup(m, h->type), |
230 | 0 | .id = h->seq, |
231 | 0 | }, |
232 | 0 | reply_id = h->user_id; |
233 | 0 | char *name = mp_tprintf(22, "@%"PRIi64, h->client_id); |
234 | 0 | int r = mp_client_send_event(mpctx, name, reply_id, MPV_EVENT_HOOK, m); |
235 | 0 | if (r < 0) { |
236 | 0 | MP_MSG(mpctx, mp_client_id_exists(mpctx, h->client_id) ? MSGL_WARN : MSGL_V, |
237 | 0 | "Failed sending hook command %s/%s. Removing hook.\n", h->client, |
238 | 0 | h->type); |
239 | 0 | hook_remove(mpctx, h); |
240 | 0 | mp_wakeup_core(mpctx); // repeat next iteration to finish |
241 | 0 | } |
242 | 0 | return r; |
243 | 0 | } |
244 | | |
245 | | static int run_next_hook_handler(struct MPContext *mpctx, char *type, int start) |
246 | 2.15M | { |
247 | 2.15M | struct command_ctx *cmd = mpctx->command_ctx; |
248 | | |
249 | 2.15M | for (int n = start; n < cmd->num_hooks; n++) { |
250 | 0 | struct hook_handler *h = cmd->hooks[n]; |
251 | 0 | if (strcmp(h->type, type) == 0) { |
252 | 0 | int ret = invoke_hook_handler(mpctx, h); |
253 | | // Repeat until the hook is successfully started or none are left. |
254 | 0 | if (ret < 0) { |
255 | 0 | --n; |
256 | 0 | continue; |
257 | 0 | } |
258 | 0 | return ret; |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | 2.15M | mp_wakeup_core(mpctx); // finished hook |
263 | 2.15M | return 0; |
264 | 2.15M | } |
265 | | |
266 | | // Start processing script/client API hooks. This is asynchronous, and the |
267 | | // caller needs to use mp_hook_test_completion() to check whether they're done. |
268 | | void mp_hook_start(struct MPContext *mpctx, char *type) |
269 | 2.15M | { |
270 | 2.15M | run_next_hook_handler(mpctx, type, 0); |
271 | 2.15M | } |
272 | | |
273 | | int mp_hook_continue(struct MPContext *mpctx, int64_t client_id, uint64_t id) |
274 | 0 | { |
275 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
276 | |
|
277 | 0 | for (int n = 0; n < cmd->num_hooks; n++) { |
278 | 0 | struct hook_handler *h = cmd->hooks[n]; |
279 | 0 | if (h->client_id == client_id && h->seq == id) { |
280 | 0 | if (!h->active) |
281 | 0 | break; |
282 | 0 | h->active = false; |
283 | 0 | return run_next_hook_handler(mpctx, h->type, n + 1); |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | 0 | MP_ERR(mpctx, "invalid hook API usage\n"); |
288 | 0 | return MPV_ERROR_INVALID_PARAMETER; |
289 | 0 | } |
290 | | |
291 | | static int compare_hook(const void *pa, const void *pb) |
292 | 0 | { |
293 | 0 | struct hook_handler **h1 = (void *)pa; |
294 | 0 | struct hook_handler **h2 = (void *)pb; |
295 | 0 | if ((*h1)->priority != (*h2)->priority) |
296 | 0 | return (*h1)->priority - (*h2)->priority; |
297 | 0 | return (*h1)->seq - (*h2)->seq; |
298 | 0 | } |
299 | | |
300 | | void mp_hook_add(struct MPContext *mpctx, char *client, int64_t client_id, |
301 | | const char *name, uint64_t user_id, int pri) |
302 | 0 | { |
303 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
304 | 0 | struct hook_handler *h = talloc_ptrtype(cmd, h); |
305 | 0 | int64_t seq = ++cmd->hook_seq; |
306 | 0 | *h = (struct hook_handler){ |
307 | 0 | .client = talloc_strdup(h, client), |
308 | 0 | .client_id = client_id, |
309 | 0 | .type = talloc_strdup(h, name), |
310 | 0 | .user_id = user_id, |
311 | 0 | .priority = pri, |
312 | 0 | .seq = seq, |
313 | 0 | }; |
314 | 0 | MP_TARRAY_APPEND(cmd, cmd->hooks, cmd->num_hooks, h); |
315 | 0 | qsort(cmd->hooks, cmd->num_hooks, sizeof(cmd->hooks[0]), compare_hook); |
316 | 0 | } |
317 | | |
318 | | // Call before a seek, in order to allow revert-seek to undo the seek. |
319 | | void mark_seek(struct MPContext *mpctx) |
320 | 0 | { |
321 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
322 | 0 | double now = mp_time_sec(); |
323 | 0 | if (now > cmd->last_seek_time + 2.0 || cmd->last_seek_pts == MP_NOPTS_VALUE) |
324 | 0 | cmd->last_seek_pts = get_current_time(mpctx); |
325 | 0 | cmd->last_seek_time = now; |
326 | 0 | } |
327 | | |
328 | | static char *append_selected_style(struct MPContext *mpctx, char *str) |
329 | 575 | { |
330 | 575 | if (!mpctx->video_out || !mpctx->opts->video_osd) |
331 | 0 | return talloc_strdup_append(str, TERM_ESC_REVERSE_COLORS); |
332 | | |
333 | 575 | return talloc_asprintf_append(str, |
334 | 575 | "%s{\\b1\\1c&H%02hhx%02hhx%02hhx&\\1a&H%02hhx&\\3c&H%02hhx%02hhx%02hhx&\\3a&H%02hhx&}%s", |
335 | 575 | OSD_ASS_0, |
336 | 575 | mpctx->video_out->osd->opts->osd_selected_color.b, |
337 | 575 | mpctx->video_out->osd->opts->osd_selected_color.g, |
338 | 575 | mpctx->video_out->osd->opts->osd_selected_color.r, |
339 | 575 | (uint8_t)(255 - mpctx->video_out->osd->opts->osd_selected_color.a), |
340 | 575 | mpctx->video_out->osd->opts->osd_selected_outline_color.b, |
341 | 575 | mpctx->video_out->osd->opts->osd_selected_outline_color.g, |
342 | 575 | mpctx->video_out->osd->opts->osd_selected_outline_color.r, |
343 | 575 | (uint8_t)(255 - mpctx->video_out->osd->opts->osd_selected_outline_color.a), |
344 | 575 | OSD_ASS_1); |
345 | 575 | } |
346 | | |
347 | | static const char *get_style_reset(struct MPContext *mpctx) |
348 | 575 | { |
349 | 575 | return mpctx->video_out && mpctx->opts->video_osd |
350 | 575 | ? OSD_ASS_0"{\\r}"OSD_ASS_1 |
351 | 575 | : TERM_ESC_CLEAR_COLORS; |
352 | 575 | } |
353 | | |
354 | | static char *skip_n_lines(char *text, int lines) |
355 | 1.75k | { |
356 | 5.71k | while (text && lines > 0) { |
357 | 3.95k | char *next = strchr(text, '\n'); |
358 | 3.95k | text = next ? next + 1 : NULL; |
359 | 3.95k | lines--; |
360 | 3.95k | } |
361 | 1.75k | return text; |
362 | 1.75k | } |
363 | | |
364 | | static int count_lines(char *text) |
365 | 841 | { |
366 | 841 | if (!text[0]) |
367 | 63 | return 0; |
368 | | |
369 | 778 | int count = 0; |
370 | 3.74k | while (text) { |
371 | 3.74k | count++; |
372 | 3.74k | char *next = strchr(text, '\n'); |
373 | 3.74k | if (!next || (next[0] == '\n' && !next[1])) |
374 | 778 | break; |
375 | 2.97k | text = next + 1; |
376 | 2.97k | } |
377 | 778 | return count; |
378 | 841 | } |
379 | | |
380 | | // Given a huge string separated by new lines, attempts to cut off text above |
381 | | // the current line to keep the line visible, and below to keep rendering |
382 | | // performance up. pos gives the current line (0 for the first line). |
383 | | // "text" might be returned as is, or it can be freed and a new allocation is |
384 | | // returned. |
385 | | // This is only a heuristic - we can't deal with line breaking. |
386 | | static char *cut_osd_list(struct MPContext *mpctx, int action, char *header, |
387 | | char *text, int pos) |
388 | 841 | { |
389 | 841 | int count = count_lines(text); |
390 | 841 | if (!count) |
391 | 63 | return text; |
392 | | |
393 | 778 | int max_lines; |
394 | 778 | if (action == M_PROPERTY_PRINT_UNCUT) { |
395 | 0 | max_lines = count + 1; |
396 | 778 | } else if (mpctx->video_out && mpctx->opts->video_osd) { |
397 | 575 | int screen_h, font_h; |
398 | 575 | osd_get_text_size(mpctx->osd, &screen_h, &font_h); |
399 | 575 | max_lines = screen_h / MPMAX(font_h, 1); |
400 | 575 | } else { |
401 | 203 | int w = -1; |
402 | 203 | max_lines = 24; |
403 | 203 | terminal_get_size(&w, &max_lines); |
404 | | // Its lists are printed uncut, so this does not end up here again. |
405 | 203 | char *msg = mp_expand_term_status_msg(mpctx); |
406 | 203 | max_lines -= msg[0] ? count_lines(msg) : 1; |
407 | 203 | talloc_free(msg); |
408 | 203 | } |
409 | | // Subtract 1 for the header. |
410 | 778 | max_lines--; |
411 | | |
412 | 778 | char *new = talloc_asprintf(NULL, "%s [%d/%d]:\n", header, pos + 1, count); |
413 | 778 | if (max_lines > 0) { |
414 | 778 | int start = MPMIN(MPMAX(pos - max_lines / 2, 0), count - max_lines); |
415 | 778 | char *head = skip_n_lines(text, start); |
416 | 778 | char *tail = skip_n_lines(head, max_lines); |
417 | 778 | new = talloc_asprintf_append_buffer(new, "%.*s", |
418 | 778 | (int)(tail ? tail - head : strlen(head)), head); |
419 | 778 | } |
420 | | // Strip the final newline to not print it in the terminal. |
421 | 778 | new[strlen(new) - 1] = '\0'; |
422 | | |
423 | 778 | talloc_free(text); |
424 | 778 | return new; |
425 | 841 | } |
426 | | |
427 | | static char *format_delay(double time) |
428 | 407 | { |
429 | 407 | char *str = mp_format_double(NULL, time * 1000, 6, false, false, true); |
430 | 407 | return talloc_strdup_append(str, " ms"); |
431 | 407 | } |
432 | | |
433 | | // Property-option bridge. (Maps the property to the option with the same name.) |
434 | | static int mp_property_generic_option(void *ctx, struct m_property *prop, |
435 | | int action, void *arg) |
436 | 96.4k | { |
437 | 96.4k | MPContext *mpctx = ctx; |
438 | 96.4k | struct m_config_option *opt = prop->is_option ? prop->priv : |
439 | 96.4k | m_config_get_co(mpctx->mconfig, bstr0(prop->name)); |
440 | 96.4k | if (!opt) |
441 | 0 | return M_PROPERTY_UNKNOWN; |
442 | | |
443 | 96.4k | switch (action) { |
444 | 27.5k | case M_PROPERTY_GET_TYPE: |
445 | 27.5k | *(struct m_option *)arg = *(opt->opt); |
446 | 27.5k | return M_PROPERTY_OK; |
447 | 4.62k | case M_PROPERTY_GET: |
448 | 4.62k | if (!opt->data) |
449 | 153 | return M_PROPERTY_NOT_IMPLEMENTED; |
450 | 4.47k | m_option_copy(opt->opt, arg, opt->data); |
451 | 4.47k | return M_PROPERTY_OK; |
452 | 18.8k | case M_PROPERTY_SET: |
453 | 18.8k | opt->coalesce = prop->coalesce; |
454 | 18.8k | if (m_config_set_option_raw(mpctx->mconfig, opt, arg, 0) < 0) |
455 | 233 | return M_PROPERTY_ERROR; |
456 | 18.5k | return M_PROPERTY_OK; |
457 | 96.4k | } |
458 | 45.4k | return M_PROPERTY_NOT_IMPLEMENTED; |
459 | 96.4k | } |
460 | | |
461 | | /// Playback speed (RW) |
462 | | static int mp_property_playback_speed(void *ctx, struct m_property *prop, |
463 | | int action, void *arg) |
464 | 3.43k | { |
465 | 3.43k | MPContext *mpctx = ctx; |
466 | 3.43k | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { |
467 | 415 | *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_speed, 2, |
468 | 415 | false, false, action != M_PROPERTY_FIXED_LEN_PRINT); |
469 | 415 | return M_PROPERTY_OK; |
470 | 415 | } |
471 | 3.01k | return mp_property_generic_option(mpctx, prop, action, arg); |
472 | 3.43k | } |
473 | | |
474 | | /// Playback pitch (RW) |
475 | | static int mp_property_playback_pitch(void *ctx, struct m_property *prop, |
476 | | int action, void *arg) |
477 | 4.69k | { |
478 | 4.69k | MPContext *mpctx = ctx; |
479 | 4.69k | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { |
480 | 246 | *(char **)arg = mp_format_double(NULL, mpctx->opts->playback_pitch, 2, |
481 | 246 | false, false, action != M_PROPERTY_FIXED_LEN_PRINT); |
482 | 246 | return M_PROPERTY_OK; |
483 | 246 | } |
484 | 4.45k | return mp_property_generic_option(mpctx, prop, action, arg); |
485 | 4.69k | } |
486 | | |
487 | | static int mp_property_av_speed_correction(void *ctx, struct m_property *prop, |
488 | | int action, void *arg) |
489 | 370 | { |
490 | 370 | MPContext *mpctx = ctx; |
491 | 370 | char *type = prop->priv; |
492 | 370 | double val = 0; |
493 | 370 | switch (type[0]) { |
494 | 276 | case 'a': val = mpctx->speed_factor_a; break; |
495 | 94 | case 'v': val = mpctx->speed_factor_v; break; |
496 | 370 | default: MP_ASSERT_UNREACHABLE(); |
497 | 370 | } |
498 | | |
499 | 370 | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { |
500 | 54 | *(char **)arg = mp_format_double(NULL, (val - 1) * 100, 4, true, |
501 | 54 | true, action != M_PROPERTY_FIXED_LEN_PRINT); |
502 | 54 | return M_PROPERTY_OK; |
503 | 54 | } |
504 | | |
505 | 316 | return m_property_double_ro(action, arg, val); |
506 | 370 | } |
507 | | |
508 | | static int mp_property_display_sync_active(void *ctx, struct m_property *prop, |
509 | | int action, void *arg) |
510 | 135 | { |
511 | 135 | MPContext *mpctx = ctx; |
512 | 135 | return m_property_bool_ro(action, arg, mpctx->display_sync_active); |
513 | 135 | } |
514 | | |
515 | | static int mp_property_pid(void *ctx, struct m_property *prop, |
516 | | int action, void *arg) |
517 | 665 | { |
518 | | // 32 bit on linux/windows - which C99 `int' is not guaranteed to hold |
519 | 665 | return m_property_int64_ro(action, arg, mp_getpid()); |
520 | 665 | } |
521 | | |
522 | | /// filename with path (RO) |
523 | | static int mp_property_path(void *ctx, struct m_property *prop, |
524 | | int action, void *arg) |
525 | 173 | { |
526 | 173 | MPContext *mpctx = ctx; |
527 | 173 | if (!mpctx->filename) |
528 | 96 | return M_PROPERTY_UNAVAILABLE; |
529 | 77 | return m_property_strdup_ro(action, arg, mpctx->filename); |
530 | 173 | } |
531 | | |
532 | | static int mp_property_filename(void *ctx, struct m_property *prop, |
533 | | int action, void *arg) |
534 | 5.51M | { |
535 | 5.51M | MPContext *mpctx = ctx; |
536 | 5.51M | if (!mpctx->filename) |
537 | 622 | return M_PROPERTY_UNAVAILABLE; |
538 | 5.51M | const char *name = mpctx->demuxer ? mpctx->demuxer->server_filename : NULL; |
539 | 5.51M | char *filename = talloc_strdup(NULL, name ? name : mpctx->filename); |
540 | 5.51M | char *f = filename; |
541 | 5.51M | if (!name) { |
542 | 5.51M | if (mp_is_url(bstr0(filename))) |
543 | 5.51M | mp_url_unescape_inplace(filename); |
544 | 5.51M | f = (char *)mp_basename(filename); |
545 | 5.51M | if (!f[0]) |
546 | 28.2k | f = filename; |
547 | 5.51M | } |
548 | 5.51M | if (action == M_PROPERTY_KEY_ACTION) { |
549 | 20 | struct m_property_action_arg *ka = arg; |
550 | 20 | if (strcmp(ka->key, "no-ext") == 0) { |
551 | 0 | action = ka->action; |
552 | 0 | arg = ka->arg; |
553 | 0 | f = bstrto0(filename, mp_strip_ext(bstr0(f))); |
554 | 0 | } |
555 | 20 | } |
556 | 5.51M | int r = m_property_strdup_ro(action, arg, f); |
557 | 5.51M | talloc_free(filename); |
558 | 5.51M | return r; |
559 | 5.51M | } |
560 | | |
561 | | static int mp_property_env(void *ctx, struct m_property *prop, |
562 | | int action, void *arg) |
563 | 1.51k | { |
564 | 1.51k | switch (action) { |
565 | 404 | case M_PROPERTY_GET_TYPE: |
566 | 404 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
567 | 404 | return M_PROPERTY_OK; |
568 | 400 | case M_PROPERTY_GET: |
569 | 400 | case M_PROPERTY_GET_NODE: { |
570 | 400 | struct mpv_node node; |
571 | 400 | node_init(&node, MPV_FORMAT_NODE_MAP, NULL); |
572 | 14.0k | for (char **env = environ; env && *env; env++) { |
573 | 13.6k | char *sep = strchr(*env, '='); |
574 | 13.6k | if (!sep) |
575 | 0 | continue; |
576 | 13.6k | bstr key = { .start = *env, .len = sep - *env }; |
577 | 13.6k | struct mpv_node *np = node_map_badd(&node, key, MPV_FORMAT_NONE); |
578 | 13.6k | np->format = MPV_FORMAT_STRING; |
579 | 13.6k | np->u.string = talloc_strdup(node.u.list, sep + 1); |
580 | 13.6k | } |
581 | 400 | *(struct mpv_node *)arg = node; |
582 | 400 | return M_PROPERTY_OK; |
583 | 400 | } |
584 | 187 | case M_PROPERTY_KEY_ACTION: { |
585 | 187 | struct m_property_action_arg *ka = arg; |
586 | 187 | char *val = getenv(ka->key); |
587 | 187 | return m_property_strdup_ro(ka->action, ka->arg, val); |
588 | 400 | } |
589 | 1.51k | } |
590 | 520 | return M_PROPERTY_NOT_IMPLEMENTED; |
591 | 1.51k | } |
592 | | |
593 | | static int mp_property_stream_open_filename(void *ctx, struct m_property *prop, |
594 | | int action, void *arg) |
595 | 488 | { |
596 | 488 | MPContext *mpctx = ctx; |
597 | 488 | if (!mpctx->stream_open_filename || !mpctx->playing) |
598 | 48 | return M_PROPERTY_UNAVAILABLE; |
599 | 440 | switch (action) { |
600 | 0 | case M_PROPERTY_SET: { |
601 | 0 | if (mpctx->demuxer) |
602 | 0 | return M_PROPERTY_ERROR; |
603 | 0 | mpctx->stream_open_filename = |
604 | 0 | talloc_strdup(mpctx->stream_open_filename, *(char **)arg); |
605 | 0 | mp_notify_property(mpctx, prop->name); |
606 | 0 | return M_PROPERTY_OK; |
607 | 0 | } |
608 | 110 | case M_PROPERTY_GET_TYPE: |
609 | 220 | case M_PROPERTY_GET: |
610 | 220 | return m_property_strdup_ro(action, arg, mpctx->stream_open_filename); |
611 | 440 | } |
612 | 220 | return M_PROPERTY_NOT_IMPLEMENTED; |
613 | 440 | } |
614 | | |
615 | | static int mp_property_file_size(void *ctx, struct m_property *prop, |
616 | | int action, void *arg) |
617 | 316 | { |
618 | 316 | MPContext *mpctx = ctx; |
619 | 316 | if (!mpctx->demuxer) |
620 | 99 | return M_PROPERTY_UNAVAILABLE; |
621 | | |
622 | 217 | int64_t size = mpctx->demuxer->filesize; |
623 | 217 | if (size < 0) |
624 | 217 | return M_PROPERTY_UNAVAILABLE; |
625 | | |
626 | 0 | if (action == M_PROPERTY_PRINT) { |
627 | 0 | *(char **)arg = format_file_size(size); |
628 | 0 | return M_PROPERTY_OK; |
629 | 0 | } |
630 | 0 | return m_property_int64_ro(action, arg, size); |
631 | 0 | } |
632 | | |
633 | | static int mp_property_media_title(void *ctx, struct m_property *prop, |
634 | | int action, void *arg) |
635 | 5.58M | { |
636 | 5.58M | MPContext *mpctx = ctx; |
637 | 5.58M | const char *name = mp_find_non_filename_media_title(mpctx); |
638 | 5.58M | if (name && name[0]) |
639 | 69.5k | return m_property_strdup_ro(action, arg, name); |
640 | 5.51M | return mp_property_filename(ctx, prop, action, arg); |
641 | 5.58M | } |
642 | | |
643 | | static int mp_property_stream_path(void *ctx, struct m_property *prop, |
644 | | int action, void *arg) |
645 | 43 | { |
646 | 43 | MPContext *mpctx = ctx; |
647 | 43 | if (!mpctx->demuxer || !mpctx->demuxer->filename) |
648 | 43 | return M_PROPERTY_UNAVAILABLE; |
649 | 0 | return m_property_strdup_ro(action, arg, mpctx->demuxer->filename); |
650 | 43 | } |
651 | | |
652 | | /// Demuxer name (RO) |
653 | | static int mp_property_demuxer(void *ctx, struct m_property *prop, |
654 | | int action, void *arg) |
655 | 178 | { |
656 | 178 | MPContext *mpctx = ctx; |
657 | 178 | struct demuxer *demuxer = mpctx->demuxer; |
658 | 178 | if (!demuxer) |
659 | 42 | return M_PROPERTY_UNAVAILABLE; |
660 | 136 | return m_property_strdup_ro(action, arg, demuxer->desc->name); |
661 | 178 | } |
662 | | |
663 | | static int mp_property_file_format(void *ctx, struct m_property *prop, |
664 | | int action, void *arg) |
665 | 423 | { |
666 | 423 | MPContext *mpctx = ctx; |
667 | 423 | struct demuxer *demuxer = mpctx->demuxer; |
668 | 423 | if (!demuxer) |
669 | 37 | return M_PROPERTY_UNAVAILABLE; |
670 | 386 | const char *name = demuxer->filetype ? demuxer->filetype : demuxer->desc->name; |
671 | 386 | return m_property_strdup_ro(action, arg, name); |
672 | 423 | } |
673 | | |
674 | | static int mp_property_stream_pos(void *ctx, struct m_property *prop, |
675 | | int action, void *arg) |
676 | 208 | { |
677 | 208 | MPContext *mpctx = ctx; |
678 | 208 | struct demuxer *demuxer = mpctx->demuxer; |
679 | 208 | if (!demuxer || demuxer->filepos < 0) |
680 | 208 | return M_PROPERTY_UNAVAILABLE; |
681 | 0 | return m_property_int64_ro(action, arg, demuxer->filepos); |
682 | 208 | } |
683 | | |
684 | | /// Stream end offset (RO) |
685 | | static int mp_property_stream_end(void *ctx, struct m_property *prop, |
686 | | int action, void *arg) |
687 | 168 | { |
688 | 168 | return mp_property_file_size(ctx, prop, action, arg); |
689 | 168 | } |
690 | | |
691 | | // Does some magic to handle "<name>/full" as time formatted with milliseconds. |
692 | | // Assumes prop is the type of the actual property. |
693 | | static int property_time(int action, void *arg, double time) |
694 | 2.17k | { |
695 | 2.17k | if (time == MP_NOPTS_VALUE) |
696 | 198 | return M_PROPERTY_UNAVAILABLE; |
697 | | |
698 | 1.97k | const struct m_option time_type = {.type = CONF_TYPE_TIME}; |
699 | 1.97k | switch (action) { |
700 | 416 | case M_PROPERTY_GET: |
701 | 416 | *(double *)arg = time; |
702 | 416 | return M_PROPERTY_OK; |
703 | 416 | case M_PROPERTY_GET_TYPE: |
704 | 416 | *(struct m_option *)arg = time_type; |
705 | 416 | return M_PROPERTY_OK; |
706 | 388 | case M_PROPERTY_KEY_ACTION: { |
707 | 388 | struct m_property_action_arg *ka = arg; |
708 | | |
709 | 388 | if (strcmp(ka->key, "full") != 0) |
710 | 388 | return M_PROPERTY_UNKNOWN; |
711 | | |
712 | 0 | switch (ka->action) { |
713 | 0 | case M_PROPERTY_GET: |
714 | 0 | *(double *)ka->arg = time; |
715 | 0 | return M_PROPERTY_OK; |
716 | 0 | case M_PROPERTY_PRINT: |
717 | 0 | *(char **)ka->arg = mp_format_time(time, true); |
718 | 0 | return M_PROPERTY_OK; |
719 | 0 | case M_PROPERTY_GET_TYPE: |
720 | 0 | *(struct m_option *)ka->arg = time_type; |
721 | 0 | return M_PROPERTY_OK; |
722 | 0 | } |
723 | 0 | } |
724 | 1.97k | } |
725 | 759 | return M_PROPERTY_NOT_IMPLEMENTED; |
726 | 1.97k | } |
727 | | |
728 | | static int mp_property_duration(void *ctx, struct m_property *prop, |
729 | | int action, void *arg) |
730 | 561 | { |
731 | 561 | MPContext *mpctx = ctx; |
732 | 561 | double len = get_time_length(mpctx); |
733 | | |
734 | 561 | if (len < 0) |
735 | 87 | return M_PROPERTY_UNAVAILABLE; |
736 | | |
737 | 474 | return property_time(action, arg, len); |
738 | 561 | } |
739 | | |
740 | | static int mp_property_avsync(void *ctx, struct m_property *prop, |
741 | | int action, void *arg) |
742 | 1.95k | { |
743 | 1.95k | MPContext *mpctx = ctx; |
744 | 1.95k | if (!mpctx->ao_chain || !mpctx->vo_chain) |
745 | 199 | return M_PROPERTY_UNAVAILABLE; |
746 | 1.75k | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { |
747 | 568 | *(char **)arg = mp_format_double(NULL, mpctx->last_av_difference, 5, |
748 | 568 | true, false, action != M_PROPERTY_FIXED_LEN_PRINT); |
749 | 568 | return M_PROPERTY_OK; |
750 | 568 | } |
751 | 1.18k | return m_property_double_ro(action, arg, mpctx->last_av_difference); |
752 | 1.75k | } |
753 | | |
754 | | static int mp_property_total_avsync_change(void *ctx, struct m_property *prop, |
755 | | int action, void *arg) |
756 | 106 | { |
757 | 106 | MPContext *mpctx = ctx; |
758 | 106 | if (!mpctx->ao_chain || !mpctx->vo_chain) |
759 | 30 | return M_PROPERTY_UNAVAILABLE; |
760 | 76 | if (mpctx->total_avsync_change == MP_NOPTS_VALUE) |
761 | 0 | return M_PROPERTY_UNAVAILABLE; |
762 | 76 | return m_property_double_ro(action, arg, mpctx->total_avsync_change); |
763 | 76 | } |
764 | | |
765 | | static int mp_property_frame_drop_dec(void *ctx, struct m_property *prop, |
766 | | int action, void *arg) |
767 | 197 | { |
768 | 197 | MPContext *mpctx = ctx; |
769 | 197 | struct mp_decoder_wrapper *dec = mpctx->vo_chain && mpctx->vo_chain->track |
770 | 197 | ? mpctx->vo_chain->track->dec : NULL; |
771 | 197 | if (!dec) |
772 | 25 | return M_PROPERTY_UNAVAILABLE; |
773 | | |
774 | 172 | return m_property_int_ro(action, arg, |
775 | 172 | mp_decoder_wrapper_get_frames_dropped(dec)); |
776 | 197 | } |
777 | | |
778 | | static int mp_property_mistimed_frame_count(void *ctx, struct m_property *prop, |
779 | | int action, void *arg) |
780 | 133 | { |
781 | 133 | MPContext *mpctx = ctx; |
782 | 133 | if (!mpctx->vo_chain || !mpctx->display_sync_active) |
783 | 133 | return M_PROPERTY_UNAVAILABLE; |
784 | | |
785 | 0 | return m_property_int_ro(action, arg, mpctx->mistimed_frames_total); |
786 | 133 | } |
787 | | |
788 | | static int mp_property_vsync_ratio(void *ctx, struct m_property *prop, |
789 | | int action, void *arg) |
790 | 115 | { |
791 | 115 | MPContext *mpctx = ctx; |
792 | 115 | if (!mpctx->vo_chain || !mpctx->display_sync_active) |
793 | 115 | return M_PROPERTY_UNAVAILABLE; |
794 | | |
795 | 0 | int vsyncs = 0, frames = 0; |
796 | 0 | for (int n = 0; n < mpctx->num_past_frames; n++) { |
797 | 0 | int vsync = mpctx->past_frames[n].num_vsyncs; |
798 | 0 | if (vsync < 0) |
799 | 0 | break; |
800 | 0 | vsyncs += vsync; |
801 | 0 | frames += 1; |
802 | 0 | } |
803 | |
|
804 | 0 | if (!frames) |
805 | 0 | return M_PROPERTY_UNAVAILABLE; |
806 | | |
807 | 0 | return m_property_double_ro(action, arg, vsyncs / (double)frames); |
808 | 0 | } |
809 | | |
810 | | static int mp_property_frame_drop_vo(void *ctx, struct m_property *prop, |
811 | | int action, void *arg) |
812 | 182 | { |
813 | 182 | MPContext *mpctx = ctx; |
814 | 182 | if (!mpctx->vo_chain) |
815 | 30 | return M_PROPERTY_UNAVAILABLE; |
816 | | |
817 | 152 | return m_property_int_ro(action, arg, vo_get_drop_count(mpctx->video_out)); |
818 | 182 | } |
819 | | |
820 | | static int mp_property_vo_delayed_frame_count(void *ctx, struct m_property *prop, |
821 | | int action, void *arg) |
822 | 106 | { |
823 | 106 | MPContext *mpctx = ctx; |
824 | 106 | if (!mpctx->vo_chain) |
825 | 18 | return M_PROPERTY_UNAVAILABLE; |
826 | | |
827 | 88 | return m_property_int_ro(action, arg, vo_get_delayed_count(mpctx->video_out)); |
828 | 106 | } |
829 | | |
830 | | /// Current position in percent (RW) |
831 | | static int mp_property_percent_pos(void *ctx, struct m_property *prop, |
832 | | int action, void *arg) |
833 | 1.95k | { |
834 | 1.95k | MPContext *mpctx = ctx; |
835 | 1.95k | if (!mpctx->playback_initialized) |
836 | 180 | return M_PROPERTY_UNAVAILABLE; |
837 | | |
838 | 1.77k | switch (action) { |
839 | 0 | case M_PROPERTY_SET: { |
840 | 0 | double pos = *(double *)arg; |
841 | 0 | queue_seek(mpctx, MPSEEK_FACTOR, pos / 100.0, MPSEEK_DEFAULT, 0); |
842 | 0 | return M_PROPERTY_OK; |
843 | 0 | } |
844 | 207 | case M_PROPERTY_GET: { |
845 | 207 | double pos = get_current_pos_ratio(mpctx, false) * 100.0; |
846 | 207 | if (pos < 0) |
847 | 98 | return M_PROPERTY_UNAVAILABLE; |
848 | 109 | *(double *)arg = pos; |
849 | 109 | return M_PROPERTY_OK; |
850 | 207 | } |
851 | 526 | case M_PROPERTY_GET_TYPE: |
852 | 526 | *(struct m_option *)arg = (struct m_option){ |
853 | 526 | .type = CONF_TYPE_DOUBLE, |
854 | 526 | .min = 0, |
855 | 526 | .max = 100, |
856 | 526 | }; |
857 | 526 | return M_PROPERTY_OK; |
858 | 396 | case M_PROPERTY_PRINT: { |
859 | 396 | double pos = get_current_pos_ratio(mpctx, false); |
860 | 396 | if (pos < 0) |
861 | 77 | return M_PROPERTY_UNAVAILABLE; |
862 | 319 | *(char **)arg = talloc_asprintf(NULL, "%.f", pos * 100); |
863 | 319 | return M_PROPERTY_OK; |
864 | 396 | } |
865 | 1.77k | } |
866 | 649 | return M_PROPERTY_NOT_IMPLEMENTED; |
867 | 1.77k | } |
868 | | |
869 | | /// Current position in seconds (RW) |
870 | | static int mp_property_time_pos(void *ctx, struct m_property *prop, |
871 | | int action, void *arg) |
872 | 1.12k | { |
873 | 1.12k | MPContext *mpctx = ctx; |
874 | 1.12k | if (!mpctx->playback_initialized) |
875 | 143 | return M_PROPERTY_UNAVAILABLE; |
876 | | |
877 | 982 | if (action == M_PROPERTY_SET) { |
878 | 0 | queue_seek(mpctx, MPSEEK_ABSOLUTE, *(double *)arg, MPSEEK_DEFAULT, 0); |
879 | 0 | return M_PROPERTY_OK; |
880 | 0 | } |
881 | 982 | return property_time(action, arg, get_playback_time(mpctx)); |
882 | 982 | } |
883 | | |
884 | | /// Current audio pts in seconds (R) |
885 | | static int mp_property_audio_pts(void *ctx, struct m_property *prop, |
886 | | int action, void *arg) |
887 | 214 | { |
888 | 214 | MPContext *mpctx = ctx; |
889 | 214 | if (!mpctx->playback_initialized || mpctx->audio_status < STATUS_PLAYING || |
890 | 38 | mpctx->audio_status >= STATUS_EOF) |
891 | 214 | return M_PROPERTY_UNAVAILABLE; |
892 | | |
893 | 0 | return property_time(action, arg, playing_audio_pts(mpctx)); |
894 | 214 | } |
895 | | |
896 | | static bool time_remaining(MPContext *mpctx, double *remaining) |
897 | 548 | { |
898 | 548 | double len = get_time_length(mpctx); |
899 | 548 | double playback = get_playback_time(mpctx); |
900 | | |
901 | 548 | if (playback == MP_NOPTS_VALUE || len <= 0) |
902 | 151 | return false; |
903 | | |
904 | 397 | *remaining = len - playback; |
905 | | |
906 | 397 | return len >= 0; |
907 | 548 | } |
908 | | |
909 | | static int mp_property_remaining(void *ctx, struct m_property *prop, |
910 | | int action, void *arg) |
911 | 511 | { |
912 | 511 | double remaining; |
913 | 511 | if (!time_remaining(ctx, &remaining)) |
914 | 114 | return M_PROPERTY_UNAVAILABLE; |
915 | | |
916 | 397 | return property_time(action, arg, remaining); |
917 | 511 | } |
918 | | |
919 | | static int mp_property_playtime_remaining(void *ctx, struct m_property *prop, |
920 | | int action, void *arg) |
921 | 37 | { |
922 | 37 | MPContext *mpctx = ctx; |
923 | 37 | double remaining; |
924 | 37 | if (!time_remaining(mpctx, &remaining)) |
925 | 37 | return M_PROPERTY_UNAVAILABLE; |
926 | | |
927 | 0 | double speed = mpctx->video_speed; |
928 | 0 | return property_time(action, arg, remaining / speed); |
929 | 37 | } |
930 | | |
931 | | static int mp_property_remaining_file_loops(void *ctx, struct m_property *prop, |
932 | | int action, void *arg) |
933 | 86 | { |
934 | 86 | MPContext *mpctx = ctx; |
935 | 86 | return m_property_int_ro(action, arg, mpctx->remaining_file_loops); |
936 | 86 | } |
937 | | |
938 | | static int mp_property_remaining_ab_loops(void *ctx, struct m_property *prop, |
939 | | int action, void *arg) |
940 | 122 | { |
941 | 122 | MPContext *mpctx = ctx; |
942 | 122 | return m_property_int_ro(action, arg, mpctx->remaining_ab_loops); |
943 | 122 | } |
944 | | |
945 | | /// Current chapter (RW) |
946 | | static int mp_property_chapter(void *ctx, struct m_property *prop, |
947 | | int action, void *arg) |
948 | 217 | { |
949 | 217 | MPContext *mpctx = ctx; |
950 | 217 | if (!mpctx->playback_initialized) |
951 | 93 | return M_PROPERTY_UNAVAILABLE; |
952 | | |
953 | 124 | int chapter = get_current_chapter(mpctx); |
954 | 124 | int num = get_chapter_count(mpctx); |
955 | 124 | if (chapter < -1) |
956 | 124 | return M_PROPERTY_UNAVAILABLE; |
957 | | |
958 | 0 | switch (action) { |
959 | 0 | case M_PROPERTY_GET: |
960 | 0 | *(int *) arg = chapter; |
961 | 0 | return M_PROPERTY_OK; |
962 | 0 | case M_PROPERTY_GET_TYPE: |
963 | 0 | *(struct m_option *)arg = (struct m_option){ |
964 | 0 | .type = CONF_TYPE_INT, |
965 | 0 | .min = -1, |
966 | 0 | .max = num - 1, |
967 | 0 | }; |
968 | 0 | return M_PROPERTY_OK; |
969 | 0 | case M_PROPERTY_PRINT: { |
970 | 0 | *(char **) arg = chapter_display_name(mpctx, chapter); |
971 | 0 | return M_PROPERTY_OK; |
972 | 0 | } |
973 | 0 | case M_PROPERTY_SWITCH: |
974 | 0 | case M_PROPERTY_SET: ; |
975 | 0 | mark_seek(mpctx); |
976 | 0 | int step_all; |
977 | 0 | if (action == M_PROPERTY_SWITCH) { |
978 | 0 | struct m_property_switch_arg *sarg = arg; |
979 | 0 | step_all = lrint(sarg->inc); |
980 | | // Check threshold for relative backward seeks |
981 | 0 | if (mpctx->opts->chapter_seek_threshold >= 0 && step_all < 0) { |
982 | 0 | double current_chapter_start = |
983 | 0 | chapter_start_time(mpctx, chapter); |
984 | | // If we are far enough into a chapter, seek back to the |
985 | | // beginning of current chapter instead of previous one |
986 | 0 | if (current_chapter_start != MP_NOPTS_VALUE && |
987 | 0 | get_current_time(mpctx) - current_chapter_start > |
988 | 0 | mpctx->opts->chapter_seek_threshold) |
989 | 0 | { |
990 | 0 | step_all++; |
991 | 0 | } |
992 | 0 | } |
993 | 0 | } else // Absolute set |
994 | 0 | step_all = *(int *)arg - chapter; |
995 | 0 | chapter += step_all; |
996 | 0 | if (chapter < 0) // avoid using -1 if first chapter starts at 0 |
997 | 0 | chapter = (chapter_start_time(mpctx, 0) <= 0) ? 0 : -1; |
998 | 0 | if (chapter >= num && step_all > 0) { |
999 | 0 | if (mpctx->opts->keep_open) { |
1000 | 0 | seek_to_last_frame(mpctx); |
1001 | 0 | } else { |
1002 | | // semi-broken file; ignore for user convenience |
1003 | 0 | if (action == M_PROPERTY_SWITCH && num < 2) |
1004 | 0 | return M_PROPERTY_UNAVAILABLE; |
1005 | 0 | if (!mpctx->stop_play) |
1006 | 0 | mpctx->stop_play = PT_NEXT_ENTRY; |
1007 | 0 | mp_wakeup_core(mpctx); |
1008 | 0 | } |
1009 | 0 | } else { |
1010 | 0 | double pts = chapter_start_time(mpctx, chapter); |
1011 | 0 | if (pts != MP_NOPTS_VALUE) { |
1012 | 0 | queue_seek(mpctx, MPSEEK_CHAPTER, pts, MPSEEK_DEFAULT, 0); |
1013 | 0 | mpctx->last_chapter_seek = chapter; |
1014 | 0 | mpctx->last_chapter_flag = true; |
1015 | 0 | } |
1016 | 0 | } |
1017 | 0 | return M_PROPERTY_OK; |
1018 | 0 | } |
1019 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
1020 | 0 | } |
1021 | | |
1022 | | static int get_chapter_entry(int item, int action, void *arg, void *ctx) |
1023 | 0 | { |
1024 | 0 | struct MPContext *mpctx = ctx; |
1025 | 0 | char *name = chapter_name(mpctx, item); |
1026 | 0 | double time = chapter_start_time(mpctx, item); |
1027 | 0 | struct m_sub_property props[] = { |
1028 | 0 | {"title", SUB_PROP_STR(name)}, |
1029 | 0 | {"time", {.type = CONF_TYPE_TIME}, {.time = time}}, |
1030 | 0 | {0} |
1031 | 0 | }; |
1032 | |
|
1033 | 0 | int r = m_property_read_sub(props, action, arg); |
1034 | 0 | return r; |
1035 | 0 | } |
1036 | | |
1037 | | static int parse_node_chapters(struct MPContext *mpctx, |
1038 | | struct mpv_node *given_chapters) |
1039 | 3 | { |
1040 | 3 | if (!mpctx->demuxer) |
1041 | 3 | return M_PROPERTY_UNAVAILABLE; |
1042 | | |
1043 | 0 | if (given_chapters->format != MPV_FORMAT_NODE_ARRAY) |
1044 | 0 | return M_PROPERTY_ERROR; |
1045 | | |
1046 | 0 | double len = get_time_length(mpctx); |
1047 | |
|
1048 | 0 | talloc_free(mpctx->chapters); |
1049 | 0 | mpctx->num_chapters = 0; |
1050 | 0 | mpctx->chapters = talloc_array(NULL, struct demux_chapter, 0); |
1051 | |
|
1052 | 0 | for (int n = 0; n < given_chapters->u.list->num; n++) { |
1053 | 0 | struct mpv_node *chapter_data = &given_chapters->u.list->values[n]; |
1054 | |
|
1055 | 0 | if (chapter_data->format != MPV_FORMAT_NODE_MAP) |
1056 | 0 | continue; |
1057 | | |
1058 | 0 | mpv_node_list *chapter_data_elements = chapter_data->u.list; |
1059 | |
|
1060 | 0 | double time = -1; |
1061 | 0 | char *title = 0; |
1062 | |
|
1063 | 0 | for (int e = 0; e < chapter_data_elements->num; e++) { |
1064 | 0 | struct mpv_node *chapter_data_element = |
1065 | 0 | &chapter_data_elements->values[e]; |
1066 | 0 | char *key = chapter_data_elements->keys[e]; |
1067 | 0 | switch (chapter_data_element->format) { |
1068 | 0 | case MPV_FORMAT_INT64: |
1069 | 0 | if (strcmp(key, "time") == 0) |
1070 | 0 | time = (double)chapter_data_element->u.int64; |
1071 | 0 | break; |
1072 | 0 | case MPV_FORMAT_DOUBLE: |
1073 | 0 | if (strcmp(key, "time") == 0) |
1074 | 0 | time = chapter_data_element->u.double_; |
1075 | 0 | break; |
1076 | 0 | case MPV_FORMAT_STRING: |
1077 | 0 | if (strcmp(key, "title") == 0) |
1078 | 0 | title = chapter_data_element->u.string; |
1079 | 0 | break; |
1080 | 0 | } |
1081 | 0 | } |
1082 | | |
1083 | 0 | if (time >= 0 && time < len) { |
1084 | 0 | struct demux_chapter new = { |
1085 | 0 | .pts = time, |
1086 | 0 | .metadata = talloc_zero(mpctx->chapters, struct mp_tags), |
1087 | 0 | }; |
1088 | 0 | if (title) |
1089 | 0 | mp_tags_set_str(new.metadata, "title", title); |
1090 | 0 | MP_TARRAY_APPEND(NULL, mpctx->chapters, mpctx->num_chapters, new); |
1091 | 0 | } |
1092 | 0 | } |
1093 | | |
1094 | 0 | mp_notify(mpctx, MP_EVENT_CHAPTER_CHANGE, NULL); |
1095 | 0 | mp_notify_property(mpctx, "chapter-list"); |
1096 | |
|
1097 | 0 | return M_PROPERTY_OK; |
1098 | 0 | } |
1099 | | |
1100 | | static int mp_property_list_chapters(void *ctx, struct m_property *prop, |
1101 | | int action, void *arg) |
1102 | 719 | { |
1103 | 719 | MPContext *mpctx = ctx; |
1104 | 719 | int count = get_chapter_count(mpctx); |
1105 | 719 | switch (action) { |
1106 | 226 | case M_PROPERTY_PRINT: |
1107 | 226 | case M_PROPERTY_PRINT_UNCUT: { |
1108 | 226 | int cur = mpctx->playback_initialized ? get_current_chapter(mpctx) : -1; |
1109 | 226 | char *res = NULL; |
1110 | 226 | int n; |
1111 | | |
1112 | 226 | if (count < 1) { |
1113 | 226 | res = talloc_asprintf_append(res, "No chapters."); |
1114 | 226 | } |
1115 | | |
1116 | 226 | for (n = 0; n < count; n++) { |
1117 | 0 | if (n == cur) |
1118 | 0 | res = append_selected_style(mpctx, res); |
1119 | 0 | char *name = chapter_name(mpctx, n); |
1120 | 0 | double t = chapter_start_time(mpctx, n); |
1121 | 0 | char* time = mp_format_time(t, false); |
1122 | 0 | res = talloc_asprintf_append(res, "%s %s%s\n", time, name, |
1123 | 0 | n == cur ? get_style_reset(mpctx) : ""); |
1124 | 0 | talloc_free(time); |
1125 | 0 | } |
1126 | | |
1127 | 226 | *(char **)arg = count ? cut_osd_list(mpctx, action, "Chapters", res, cur) |
1128 | 226 | : res; |
1129 | 226 | return M_PROPERTY_OK; |
1130 | 226 | } |
1131 | 3 | case M_PROPERTY_SET: { |
1132 | 3 | struct mpv_node *given_chapters = arg; |
1133 | 3 | return parse_node_chapters(mpctx, given_chapters); |
1134 | 226 | } |
1135 | 719 | } |
1136 | 490 | return m_property_read_list(action, arg, count, get_chapter_entry, mpctx); |
1137 | 719 | } |
1138 | | |
1139 | | static int mp_property_current_edition(void *ctx, struct m_property *prop, |
1140 | | int action, void *arg) |
1141 | 108 | { |
1142 | 108 | MPContext *mpctx = ctx; |
1143 | 108 | struct demuxer *demuxer = mpctx->demuxer; |
1144 | 108 | if (!demuxer || demuxer->num_editions <= 0) |
1145 | 108 | return M_PROPERTY_UNAVAILABLE; |
1146 | 0 | return m_property_int_ro(action, arg, demuxer->edition); |
1147 | 108 | } |
1148 | | |
1149 | | static int mp_property_disc_menu_active(void *ctx, struct m_property *prop, |
1150 | | int action, void *arg) |
1151 | 25 | { |
1152 | 25 | MPContext *mpctx = ctx; |
1153 | 25 | struct stream *s = disc_nav_get_stream(mpctx); |
1154 | 25 | if (!s) |
1155 | 25 | return M_PROPERTY_UNAVAILABLE; |
1156 | 0 | struct stream_nav_state st = {0}; |
1157 | 0 | if (stream_control(s, STREAM_CTRL_GET_NAV_STATE, &st) < 1) |
1158 | 0 | return M_PROPERTY_UNAVAILABLE; |
1159 | 0 | return m_property_bool_ro(action, arg, st.menu_active); |
1160 | 0 | } |
1161 | | |
1162 | | static int mp_property_edition(void *ctx, struct m_property *prop, |
1163 | | int action, void *arg) |
1164 | 320 | { |
1165 | 320 | MPContext *mpctx = ctx; |
1166 | 320 | struct demuxer *demuxer = mpctx->demuxer; |
1167 | 320 | char *name = NULL; |
1168 | | |
1169 | 320 | if (!demuxer) |
1170 | 177 | return mp_property_generic_option(mpctx, prop, action, arg); |
1171 | | |
1172 | 143 | int ed = demuxer->edition; |
1173 | | |
1174 | 143 | if (demuxer->num_editions <= 1) |
1175 | 143 | return M_PROPERTY_UNAVAILABLE; |
1176 | | |
1177 | 0 | switch (action) { |
1178 | 0 | case M_PROPERTY_GET_CONSTRICTED_TYPE: { |
1179 | 0 | *(struct m_option *)arg = (struct m_option){ |
1180 | 0 | .type = CONF_TYPE_INT, |
1181 | 0 | .min = 0, |
1182 | 0 | .max = demuxer->num_editions - 1, |
1183 | 0 | }; |
1184 | 0 | return M_PROPERTY_OK; |
1185 | 0 | } |
1186 | 0 | case M_PROPERTY_PRINT: { |
1187 | 0 | if (ed < 0) |
1188 | 0 | return M_PROPERTY_UNAVAILABLE; |
1189 | 0 | name = mp_tags_get_str(demuxer->editions[ed].metadata, "title"); |
1190 | 0 | if (name) { |
1191 | 0 | *(char **) arg = talloc_strdup(NULL, name); |
1192 | 0 | } else { |
1193 | 0 | *(char **) arg = talloc_asprintf(NULL, "%d", ed + 1); |
1194 | 0 | } |
1195 | 0 | return M_PROPERTY_OK; |
1196 | 0 | } |
1197 | 0 | case M_PROPERTY_SET: { |
1198 | | // For disc demuxers, jump the title directly, we sometimes need to |
1199 | | // react even if the actual edition "value" doesn't change. |
1200 | 0 | if (disc_nav_get_stream(mpctx)) { |
1201 | 0 | int new_ed = *(int *)arg; |
1202 | 0 | if (new_ed < 0 || new_ed >= demuxer->num_editions) |
1203 | 0 | return M_PROPERTY_ERROR; |
1204 | 0 | unsigned new_title = new_ed; |
1205 | 0 | if (stream_control(demuxer->stream, STREAM_CTRL_SET_CURRENT_TITLE, |
1206 | 0 | &new_title) < 1) |
1207 | 0 | return M_PROPERTY_ERROR; |
1208 | 0 | mpctx->opts->edition_id = new_ed; |
1209 | 0 | m_config_notify_change_opt_ptr(mpctx->mconfig, |
1210 | 0 | &mpctx->opts->edition_id); |
1211 | 0 | mp_notify_property(mpctx, "edition"); |
1212 | 0 | mp_wakeup_core(mpctx); |
1213 | 0 | return M_PROPERTY_OK; |
1214 | 0 | } |
1215 | 0 | return mp_property_generic_option(mpctx, prop, action, arg); |
1216 | 0 | } |
1217 | 0 | default: |
1218 | 0 | return mp_property_generic_option(mpctx, prop, action, arg); |
1219 | 0 | } |
1220 | 0 | } |
1221 | | |
1222 | | static int get_edition_entry(int item, int action, void *arg, void *ctx) |
1223 | 0 | { |
1224 | 0 | struct MPContext *mpctx = ctx; |
1225 | |
|
1226 | 0 | struct demuxer *demuxer = mpctx->demuxer; |
1227 | 0 | struct demux_edition *ed = &demuxer->editions[item]; |
1228 | |
|
1229 | 0 | char *title = mp_tags_get_str(ed->metadata, "title"); |
1230 | |
|
1231 | 0 | struct mp_tags *tags = ed->metadata; |
1232 | 0 | char **tag_list = talloc_zero_array(NULL, char *, tags->num_keys * 2 + 1); |
1233 | 0 | for (int i = 0; i < tags->num_keys; i++) { |
1234 | 0 | tag_list[2 * i] = talloc_strdup(tag_list, tags->keys[i]); |
1235 | 0 | tag_list[2 * i + 1] = talloc_strdup(tag_list, tags->values[i]); |
1236 | 0 | } |
1237 | |
|
1238 | 0 | struct m_sub_property props[] = { |
1239 | 0 | {"id", SUB_PROP_INT(item)}, |
1240 | 0 | {"title", SUB_PROP_STR(title), |
1241 | 0 | .unavailable = !title}, |
1242 | 0 | {"default", SUB_PROP_BOOL(ed->default_edition)}, |
1243 | 0 | {"metadata", SUB_PROP_KEYVALUE_LIST(tag_list), |
1244 | 0 | .unavailable = !tags->num_keys}, |
1245 | 0 | {0} |
1246 | 0 | }; |
1247 | |
|
1248 | 0 | int ret = m_property_read_sub(props, action, arg); |
1249 | 0 | talloc_free(tag_list); |
1250 | 0 | return ret; |
1251 | 0 | } |
1252 | | |
1253 | | static int mp_property_list_editions(void *ctx, struct m_property *prop, |
1254 | | int action, void *arg) |
1255 | 757 | { |
1256 | 757 | MPContext *mpctx = ctx; |
1257 | 757 | struct demuxer *demuxer = mpctx->demuxer; |
1258 | 757 | if (!demuxer) |
1259 | 50 | return M_PROPERTY_UNAVAILABLE; |
1260 | | |
1261 | 707 | if (action == M_PROPERTY_PRINT) { |
1262 | 98 | char *res = NULL; |
1263 | | |
1264 | 98 | struct demux_edition *editions = demuxer->editions; |
1265 | 98 | int num_editions = demuxer->num_editions; |
1266 | 98 | int current = demuxer->edition; |
1267 | | |
1268 | 98 | if (!num_editions) |
1269 | 98 | res = talloc_asprintf_append(res, "No editions."); |
1270 | | |
1271 | 98 | for (int n = 0; n < num_editions; n++) { |
1272 | 0 | struct demux_edition *ed = &editions[n]; |
1273 | |
|
1274 | 0 | if (n == current) |
1275 | 0 | res = append_selected_style(mpctx, res); |
1276 | 0 | res = talloc_asprintf_append(res, "%d: ", n); |
1277 | 0 | char *title = mp_tags_get_str(ed->metadata, "title"); |
1278 | 0 | if (!title) |
1279 | 0 | title = "unnamed"; |
1280 | 0 | res = talloc_asprintf_append(res, "'%s'%s\n", title, |
1281 | 0 | n == current ? get_style_reset(mpctx) : ""); |
1282 | 0 | } |
1283 | | |
1284 | 98 | *(char **)arg = res; |
1285 | 98 | return M_PROPERTY_OK; |
1286 | 98 | } |
1287 | 609 | return m_property_read_list(action, arg, demuxer->num_editions, |
1288 | 609 | get_edition_entry, mpctx); |
1289 | 707 | } |
1290 | | |
1291 | | /// Number of chapters in file |
1292 | | static int mp_property_chapters(void *ctx, struct m_property *prop, |
1293 | | int action, void *arg) |
1294 | 412 | { |
1295 | 412 | MPContext *mpctx = ctx; |
1296 | 412 | if (!mpctx->playback_initialized) |
1297 | 97 | return M_PROPERTY_UNAVAILABLE; |
1298 | 315 | int count = get_chapter_count(mpctx); |
1299 | 315 | return m_property_int_ro(action, arg, count); |
1300 | 412 | } |
1301 | | |
1302 | | static int mp_property_editions(void *ctx, struct m_property *prop, |
1303 | | int action, void *arg) |
1304 | 183 | { |
1305 | 183 | MPContext *mpctx = ctx; |
1306 | 183 | struct demuxer *demuxer = mpctx->demuxer; |
1307 | 183 | if (!demuxer) |
1308 | 63 | return M_PROPERTY_UNAVAILABLE; |
1309 | 120 | if (demuxer->num_editions <= 0) |
1310 | 120 | return M_PROPERTY_UNAVAILABLE; |
1311 | 0 | return m_property_int_ro(action, arg, demuxer->num_editions); |
1312 | 120 | } |
1313 | | |
1314 | | static int get_tag_entry(int item, int action, void *arg, void *ctx) |
1315 | 0 | { |
1316 | 0 | struct mp_tags *tags = ctx; |
1317 | |
|
1318 | 0 | struct m_sub_property props[] = { |
1319 | 0 | {"key", SUB_PROP_STR(tags->keys[item])}, |
1320 | 0 | {"value", SUB_PROP_STR(tags->values[item])}, |
1321 | 0 | {0} |
1322 | 0 | }; |
1323 | |
|
1324 | 0 | return m_property_read_sub(props, action, arg); |
1325 | 0 | } |
1326 | | |
1327 | | // tags can be NULL for M_PROPERTY_GET_TYPE. (In all other cases, tags must be |
1328 | | // provided, even for M_PROPERTY_KEY_ACTION GET_TYPE sub-actions.) |
1329 | | static int tag_property(int action, void *arg, struct mp_tags *tags) |
1330 | 1.68k | { |
1331 | 1.68k | switch (action) { |
1332 | 0 | case M_PROPERTY_GET_NODE: // same as GET, because type==mpv_node |
1333 | 96 | case M_PROPERTY_GET: { |
1334 | 96 | mpv_node_list *list = talloc_zero(NULL, mpv_node_list); |
1335 | 96 | mpv_node node = { |
1336 | 96 | .format = MPV_FORMAT_NODE_MAP, |
1337 | 96 | .u.list = list, |
1338 | 96 | }; |
1339 | 96 | list->num = tags->num_keys; |
1340 | 96 | list->values = talloc_array(list, mpv_node, list->num); |
1341 | 96 | list->keys = talloc_array(list, char*, list->num); |
1342 | 96 | for (int n = 0; n < tags->num_keys; n++) { |
1343 | 0 | list->keys[n] = talloc_strdup(list, tags->keys[n]); |
1344 | 0 | list->values[n] = (struct mpv_node){ |
1345 | 0 | .format = MPV_FORMAT_STRING, |
1346 | 0 | .u.string = talloc_strdup(list, tags->values[n]), |
1347 | 0 | }; |
1348 | 0 | } |
1349 | 96 | *(mpv_node*)arg = node; |
1350 | 96 | return M_PROPERTY_OK; |
1351 | 0 | } |
1352 | 478 | case M_PROPERTY_GET_TYPE: { |
1353 | 478 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
1354 | 478 | return M_PROPERTY_OK; |
1355 | 0 | } |
1356 | 114 | case M_PROPERTY_PRINT: { |
1357 | 114 | char *res = NULL; |
1358 | 114 | for (int n = 0; n < tags->num_keys; n++) { |
1359 | 0 | res = talloc_asprintf_append_buffer(res, "%s: %s\n", |
1360 | 0 | tags->keys[n], tags->values[n]); |
1361 | 0 | } |
1362 | 114 | if (!res) |
1363 | 114 | res = talloc_strdup(NULL, "(empty)"); |
1364 | 114 | *(char **)arg = res; |
1365 | 114 | return M_PROPERTY_OK; |
1366 | 0 | } |
1367 | 691 | case M_PROPERTY_KEY_ACTION: { |
1368 | 691 | struct m_property_action_arg *ka = arg; |
1369 | 691 | bstr key; |
1370 | 691 | const char *rem; |
1371 | 691 | m_property_split_path(ka->key, &key, &rem); |
1372 | 691 | if (bstr_equals0(key, "list")) { |
1373 | 99 | struct m_property_action_arg nka = *ka; |
1374 | 99 | nka.key = rem; |
1375 | 99 | return m_property_read_list(action, &nka, tags->num_keys, |
1376 | 99 | get_tag_entry, tags); |
1377 | 99 | } |
1378 | | // Direct access without this prefix is allowed for compatibility. |
1379 | 592 | bstr k = bstr0(ka->key); |
1380 | 592 | bstr_eatstart0(&k, "by-key/"); |
1381 | 592 | switch (ka->action) { |
1382 | 148 | case M_PROPERTY_GET: { |
1383 | 148 | char *meta = mp_tags_get_bstr(tags, k); |
1384 | 148 | if (!meta) |
1385 | 148 | return M_PROPERTY_UNKNOWN; |
1386 | 0 | *(char **)ka->arg = talloc_strdup(NULL, meta); |
1387 | 0 | return M_PROPERTY_OK; |
1388 | 148 | } |
1389 | 148 | case M_PROPERTY_GET_TYPE: |
1390 | 148 | *(struct m_option *)ka->arg = (struct m_option){ |
1391 | 148 | .type = CONF_TYPE_STRING, |
1392 | 148 | }; |
1393 | 148 | return M_PROPERTY_OK; |
1394 | 0 | case M_PROPERTY_SET: { |
1395 | 0 | bstr v = bstr0(*(char **)ka->arg); |
1396 | 0 | if (v.len == 0) { |
1397 | 0 | mp_tags_remove_bstr(tags, k); |
1398 | 0 | } else { |
1399 | 0 | mp_tags_set_bstr(tags, k, v); |
1400 | 0 | } |
1401 | 0 | return M_PROPERTY_OK; |
1402 | 148 | } |
1403 | 592 | } |
1404 | 592 | } |
1405 | 1.68k | } |
1406 | 600 | return M_PROPERTY_NOT_IMPLEMENTED; |
1407 | 1.68k | } |
1408 | | |
1409 | | /// Demuxer meta data |
1410 | | static int mp_property_metadata(void *ctx, struct m_property *prop, |
1411 | | int action, void *arg) |
1412 | 1.26k | { |
1413 | 1.26k | MPContext *mpctx = ctx; |
1414 | 1.26k | struct demuxer *demuxer = mpctx->demuxer; |
1415 | 1.26k | if (!demuxer) |
1416 | 46 | return M_PROPERTY_UNAVAILABLE; |
1417 | | |
1418 | 1.21k | return tag_property(action, arg, demuxer->metadata); |
1419 | 1.26k | } |
1420 | | |
1421 | | static int mp_property_filtered_metadata(void *ctx, struct m_property *prop, |
1422 | | int action, void *arg) |
1423 | 241 | { |
1424 | 241 | MPContext *mpctx = ctx; |
1425 | 241 | if (!mpctx->filtered_tags) |
1426 | 40 | return M_PROPERTY_UNAVAILABLE; |
1427 | | |
1428 | 201 | return tag_property(action, arg, mpctx->filtered_tags); |
1429 | 241 | } |
1430 | | |
1431 | | static int mp_property_chapter_metadata(void *ctx, struct m_property *prop, |
1432 | | int action, void *arg) |
1433 | 28 | { |
1434 | 28 | MPContext *mpctx = ctx; |
1435 | 28 | int chapter = get_current_chapter(mpctx); |
1436 | 28 | if (chapter < 0) |
1437 | 28 | return M_PROPERTY_UNAVAILABLE; |
1438 | 0 | return tag_property(action, arg, mpctx->chapters[chapter].metadata); |
1439 | 28 | } |
1440 | | |
1441 | | static int mp_property_filter_metadata(void *ctx, struct m_property *prop, |
1442 | | int action, void *arg) |
1443 | 1.15k | { |
1444 | 1.15k | MPContext *mpctx = ctx; |
1445 | 1.15k | const char *type = prop->priv; |
1446 | | |
1447 | 1.15k | if (action == M_PROPERTY_KEY_ACTION) { |
1448 | 1.03k | struct m_property_action_arg *ka = arg; |
1449 | 1.03k | bstr key; |
1450 | 1.03k | const char *rem; |
1451 | 1.03k | m_property_split_path(ka->key, &key, &rem); |
1452 | 1.03k | struct mp_tags *metadata = NULL; |
1453 | 1.03k | struct mp_output_chain *chain = NULL; |
1454 | 1.03k | if (strcmp(type, "vf") == 0) { |
1455 | 433 | chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL; |
1456 | 597 | } else if (strcmp(type, "af") == 0) { |
1457 | 597 | chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL; |
1458 | 597 | } |
1459 | 1.03k | if (!chain) |
1460 | 171 | return M_PROPERTY_UNAVAILABLE; |
1461 | | |
1462 | 859 | int remaining = strlen(rem); |
1463 | 859 | if (remaining || ka->action != M_PROPERTY_GET_TYPE) { |
1464 | 591 | struct mp_filter_command cmd = { |
1465 | 591 | .type = MP_FILTER_COMMAND_GET_META, |
1466 | 591 | .res = &metadata, |
1467 | 591 | }; |
1468 | 591 | mp_output_chain_command(chain, mp_tprintf(80, "%.*s", BSTR_P(key)), |
1469 | 591 | &cmd); |
1470 | | |
1471 | 591 | if (!metadata) |
1472 | 591 | return M_PROPERTY_ERROR; |
1473 | 591 | } |
1474 | | |
1475 | 268 | int res; |
1476 | 268 | if (remaining) { |
1477 | 0 | struct m_property_action_arg next_ka = *ka; |
1478 | 0 | next_ka.key = rem; |
1479 | 0 | res = tag_property(M_PROPERTY_KEY_ACTION, &next_ka, metadata); |
1480 | 268 | } else { |
1481 | 268 | res = tag_property(ka->action, ka->arg, metadata); |
1482 | 268 | } |
1483 | 268 | talloc_free(metadata); |
1484 | 268 | return res; |
1485 | 859 | } |
1486 | 123 | return M_PROPERTY_NOT_IMPLEMENTED; |
1487 | 1.15k | } |
1488 | | |
1489 | | static int mp_property_core_idle(void *ctx, struct m_property *prop, |
1490 | | int action, void *arg) |
1491 | 429 | { |
1492 | 429 | MPContext *mpctx = ctx; |
1493 | 429 | return m_property_bool_ro(action, arg, !mpctx->playback_active); |
1494 | 429 | } |
1495 | | |
1496 | | static int mp_property_deinterlace(void *ctx, struct m_property *prop, |
1497 | | int action, void *arg) |
1498 | 864 | { |
1499 | 864 | MPContext *mpctx = ctx; |
1500 | 864 | struct vo_chain *vo_c = mpctx->vo_chain; |
1501 | 864 | if (!vo_c) |
1502 | 76 | return M_PROPERTY_UNAVAILABLE; |
1503 | | |
1504 | 864 | bool deinterlace_active = mp_output_chain_deinterlace_active(vo_c->filter); |
1505 | 788 | return m_property_bool_ro(action, arg, deinterlace_active); |
1506 | 864 | } |
1507 | | |
1508 | | static int mp_property_idle(void *ctx, struct m_property *prop, |
1509 | | int action, void *arg) |
1510 | 307 | { |
1511 | 307 | MPContext *mpctx = ctx; |
1512 | 307 | return m_property_bool_ro(action, arg, mpctx->stop_play == PT_STOP); |
1513 | 307 | } |
1514 | | |
1515 | | static int mp_property_window_id(void *ctx, struct m_property *prop, |
1516 | | int action, void *arg) |
1517 | 132 | { |
1518 | 132 | MPContext *mpctx = ctx; |
1519 | 132 | struct vo *vo = mpctx->video_out; |
1520 | 132 | int64_t wid; |
1521 | 132 | if (!vo || vo_control(vo, VOCTRL_GET_WINDOW_ID, &wid) <= 0) |
1522 | 132 | return M_PROPERTY_UNAVAILABLE; |
1523 | 0 | return m_property_int64_ro(action, arg, wid); |
1524 | 132 | } |
1525 | | |
1526 | | static int mp_property_eof_reached(void *ctx, struct m_property *prop, |
1527 | | int action, void *arg) |
1528 | 633 | { |
1529 | 633 | MPContext *mpctx = ctx; |
1530 | 633 | if (!mpctx->playback_initialized) |
1531 | 99 | return M_PROPERTY_UNAVAILABLE; |
1532 | 633 | bool eof = mpctx->video_status == STATUS_EOF && |
1533 | 0 | mpctx->audio_status == STATUS_EOF; |
1534 | 534 | return m_property_bool_ro(action, arg, eof); |
1535 | 633 | } |
1536 | | |
1537 | | static int mp_property_seeking(void *ctx, struct m_property *prop, |
1538 | | int action, void *arg) |
1539 | 423 | { |
1540 | 423 | MPContext *mpctx = ctx; |
1541 | 423 | if (!mpctx->playback_initialized) |
1542 | 96 | return M_PROPERTY_UNAVAILABLE; |
1543 | 327 | return m_property_bool_ro(action, arg, !mpctx->restart_complete); |
1544 | 423 | } |
1545 | | |
1546 | | static int mp_property_playback_abort(void *ctx, struct m_property *prop, |
1547 | | int action, void *arg) |
1548 | 189 | { |
1549 | 189 | MPContext *mpctx = ctx; |
1550 | 189 | return m_property_bool_ro(action, arg, !mpctx->playing || mpctx->stop_play); |
1551 | 189 | } |
1552 | | |
1553 | | static int mp_property_cache_speed(void *ctx, struct m_property *prop, |
1554 | | int action, void *arg) |
1555 | 912 | { |
1556 | 912 | MPContext *mpctx = ctx; |
1557 | 912 | if (!mpctx->demuxer) |
1558 | 39 | return M_PROPERTY_UNAVAILABLE; |
1559 | | |
1560 | 873 | struct demux_reader_state s; |
1561 | 873 | demux_get_reader_state(mpctx->demuxer, &s); |
1562 | | |
1563 | 873 | uint64_t val = s.bytes_per_second; |
1564 | | |
1565 | 873 | if (action == M_PROPERTY_PRINT) { |
1566 | 287 | *(char **)arg = talloc_strdup_append(format_file_size(val), "/s"); |
1567 | 287 | return M_PROPERTY_OK; |
1568 | 287 | } |
1569 | 586 | return m_property_int64_ro(action, arg, val); |
1570 | 873 | } |
1571 | | |
1572 | | static int mp_property_demuxer_cache_duration(void *ctx, struct m_property *prop, |
1573 | | int action, void *arg) |
1574 | 140 | { |
1575 | 140 | MPContext *mpctx = ctx; |
1576 | 140 | if (!mpctx->demuxer) |
1577 | 4 | return M_PROPERTY_UNAVAILABLE; |
1578 | | |
1579 | 136 | struct demux_reader_state s; |
1580 | 136 | demux_get_reader_state(mpctx->demuxer, &s); |
1581 | | |
1582 | 136 | if (s.ts_info.duration < 0) |
1583 | 0 | return M_PROPERTY_UNAVAILABLE; |
1584 | | |
1585 | 136 | return m_property_double_ro(action, arg, s.ts_info.duration); |
1586 | 136 | } |
1587 | | |
1588 | | static int mp_property_demuxer_cache_time(void *ctx, struct m_property *prop, |
1589 | | int action, void *arg) |
1590 | 984 | { |
1591 | 984 | MPContext *mpctx = ctx; |
1592 | 984 | if (!mpctx->demuxer) |
1593 | 36 | return M_PROPERTY_UNAVAILABLE; |
1594 | | |
1595 | 948 | struct demux_reader_state s; |
1596 | 948 | demux_get_reader_state(mpctx->demuxer, &s); |
1597 | | |
1598 | 948 | if (s.ts_info.end == MP_NOPTS_VALUE) |
1599 | 144 | return M_PROPERTY_UNAVAILABLE; |
1600 | | |
1601 | 804 | return m_property_double_ro(action, arg, s.ts_info.end); |
1602 | 948 | } |
1603 | | |
1604 | | static int mp_property_demuxer_cache_idle(void *ctx, struct m_property *prop, |
1605 | | int action, void *arg) |
1606 | 25 | { |
1607 | 25 | MPContext *mpctx = ctx; |
1608 | 25 | if (!mpctx->demuxer) |
1609 | 25 | return M_PROPERTY_UNAVAILABLE; |
1610 | | |
1611 | 0 | struct demux_reader_state s; |
1612 | 0 | demux_get_reader_state(mpctx->demuxer, &s); |
1613 | |
|
1614 | 0 | return m_property_bool_ro(action, arg, s.idle); |
1615 | 25 | } |
1616 | | |
1617 | | static int mp_property_demuxer_cache_state(void *ctx, struct m_property *prop, |
1618 | | int action, void *arg) |
1619 | 571 | { |
1620 | 571 | MPContext *mpctx = ctx; |
1621 | 571 | if (!mpctx->demuxer) |
1622 | 53 | return M_PROPERTY_UNAVAILABLE; |
1623 | | |
1624 | 518 | if (action == M_PROPERTY_GET_TYPE) { |
1625 | 128 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
1626 | 128 | return M_PROPERTY_OK; |
1627 | 128 | } |
1628 | 390 | if (action != M_PROPERTY_GET) |
1629 | 262 | return M_PROPERTY_NOT_IMPLEMENTED; |
1630 | | |
1631 | 128 | struct demux_reader_state s; |
1632 | 128 | demux_get_reader_state(mpctx->demuxer, &s); |
1633 | | |
1634 | 128 | struct mpv_node *r = (struct mpv_node *)arg; |
1635 | 128 | node_init(r, MPV_FORMAT_NODE_MAP, NULL); |
1636 | | |
1637 | 128 | if (s.ts_info.end != MP_NOPTS_VALUE) |
1638 | 81 | node_map_add_double(r, "cache-end", s.ts_info.end); |
1639 | | |
1640 | 128 | if (s.ts_info.reader != MP_NOPTS_VALUE) |
1641 | 81 | node_map_add_double(r, "reader-pts", s.ts_info.reader); |
1642 | | |
1643 | 128 | if (s.ts_info.duration >= 0) |
1644 | 128 | node_map_add_double(r, "cache-duration", s.ts_info.duration); |
1645 | | |
1646 | 128 | node_map_add_flag(r, "eof", s.eof); |
1647 | 128 | node_map_add_flag(r, "underrun", s.underrun); |
1648 | 128 | node_map_add_flag(r, "idle", s.idle); |
1649 | 128 | node_map_add_int64(r, "total-bytes", s.total_bytes); |
1650 | 128 | node_map_add_int64(r, "fw-bytes", s.fw_bytes); |
1651 | 128 | if (s.file_cache_bytes >= 0) |
1652 | 0 | node_map_add_int64(r, "file-cache-bytes", s.file_cache_bytes); |
1653 | 128 | if (s.bytes_per_second > 0) |
1654 | 0 | node_map_add_int64(r, "raw-input-rate", s.bytes_per_second); |
1655 | 128 | if (s.seeking != MP_NOPTS_VALUE) |
1656 | 0 | node_map_add_double(r, "debug-seeking", s.seeking); |
1657 | 128 | node_map_add_int64(r, "debug-low-level-seeks", s.low_level_seeks); |
1658 | 128 | node_map_add_int64(r, "debug-byte-level-seeks", s.byte_level_seeks); |
1659 | 128 | if (s.ts_last != MP_NOPTS_VALUE) |
1660 | 115 | node_map_add_double(r, "debug-ts-last", s.ts_last); |
1661 | | |
1662 | 128 | struct mpv_node *stream_types = |
1663 | 128 | node_map_add(r, "ts-per-stream", MPV_FORMAT_NODE_ARRAY); |
1664 | 512 | for (int n = 0; n < STREAM_TYPE_COUNT; n++) { |
1665 | 384 | struct demux_ctrl_ts_info ts = s.ts_per_stream[n]; |
1666 | 384 | if (ts.duration == -1) |
1667 | 303 | continue; |
1668 | | |
1669 | 81 | struct mpv_node *st = node_array_add(stream_types, MPV_FORMAT_NODE_MAP); |
1670 | 81 | node_map_add_string(st, "type", |
1671 | 81 | n == STREAM_VIDEO ? "video" : |
1672 | 81 | n == STREAM_AUDIO ? "audio" : |
1673 | 0 | n == STREAM_SUB ? "subtitle" : "unknown"); |
1674 | 81 | node_map_add_double(st, "cache-duration", ts.duration); |
1675 | 81 | if (ts.reader != MP_NOPTS_VALUE) |
1676 | 81 | node_map_add_double(st, "reader-pts", ts.reader); |
1677 | 81 | if (ts.end != MP_NOPTS_VALUE) |
1678 | 81 | node_map_add_double(st, "cache-end", ts.end); |
1679 | 81 | } |
1680 | | |
1681 | 128 | node_map_add_flag(r, "bof-cached", s.bof_cached); |
1682 | 128 | node_map_add_flag(r, "eof-cached", s.eof_cached); |
1683 | | |
1684 | 128 | struct mpv_node *ranges = |
1685 | 128 | node_map_add(r, "seekable-ranges", MPV_FORMAT_NODE_ARRAY); |
1686 | 128 | for (int n = s.num_seek_ranges - 1; n >= 0; n--) { |
1687 | 0 | struct demux_seek_range *range = &s.seek_ranges[n]; |
1688 | 0 | struct mpv_node *sub = node_array_add(ranges, MPV_FORMAT_NODE_MAP); |
1689 | 0 | node_map_add_double(sub, "start", range->start); |
1690 | 0 | node_map_add_double(sub, "end", range->end); |
1691 | 0 | } |
1692 | | |
1693 | 128 | return M_PROPERTY_OK; |
1694 | 390 | } |
1695 | | |
1696 | | static int mp_property_demuxer_start_time(void *ctx, struct m_property *prop, |
1697 | | int action, void *arg) |
1698 | 148 | { |
1699 | 148 | MPContext *mpctx = ctx; |
1700 | 148 | if (!mpctx->demuxer) |
1701 | 19 | return M_PROPERTY_UNAVAILABLE; |
1702 | | |
1703 | 129 | return m_property_double_ro(action, arg, mpctx->demuxer->start_time); |
1704 | 148 | } |
1705 | | |
1706 | | static int mp_property_paused_for_cache(void *ctx, struct m_property *prop, |
1707 | | int action, void *arg) |
1708 | 207 | { |
1709 | 207 | MPContext *mpctx = ctx; |
1710 | 207 | if (!mpctx->playback_initialized) |
1711 | 108 | return M_PROPERTY_UNAVAILABLE; |
1712 | 99 | return m_property_bool_ro(action, arg, mpctx->paused_for_cache); |
1713 | 207 | } |
1714 | | |
1715 | | static int mp_property_cache_buffering(void *ctx, struct m_property *prop, |
1716 | | int action, void *arg) |
1717 | 288 | { |
1718 | 288 | MPContext *mpctx = ctx; |
1719 | 288 | int state = get_cache_buffering_percentage(mpctx); |
1720 | 288 | if (state < 0) |
1721 | 12 | return M_PROPERTY_UNAVAILABLE; |
1722 | 276 | return m_property_int_ro(action, arg, state); |
1723 | 288 | } |
1724 | | |
1725 | | static int mp_property_demuxer_is_network(void *ctx, struct m_property *prop, |
1726 | | int action, void *arg) |
1727 | 426 | { |
1728 | 426 | MPContext *mpctx = ctx; |
1729 | 426 | if (!mpctx->demuxer) |
1730 | 26 | return M_PROPERTY_UNAVAILABLE; |
1731 | | |
1732 | 400 | return m_property_bool_ro(action, arg, mpctx->demuxer->is_network); |
1733 | 426 | } |
1734 | | |
1735 | | |
1736 | | static int mp_property_clock(void *ctx, struct m_property *prop, |
1737 | | int action, void *arg) |
1738 | 267 | { |
1739 | 267 | char outstr[6]; |
1740 | 267 | time_t t = time(NULL); |
1741 | 267 | struct tm *tmp = localtime(&t); |
1742 | | |
1743 | 267 | if ((tmp != NULL) && (strftime(outstr, sizeof(outstr), "%H:%M", tmp) == 5)) |
1744 | 267 | return m_property_strdup_ro(action, arg, outstr); |
1745 | 0 | return M_PROPERTY_UNAVAILABLE; |
1746 | 267 | } |
1747 | | |
1748 | | static int mp_property_seekable(void *ctx, struct m_property *prop, |
1749 | | int action, void *arg) |
1750 | 177 | { |
1751 | 177 | MPContext *mpctx = ctx; |
1752 | 177 | if (!mpctx->demuxer) |
1753 | 29 | return M_PROPERTY_UNAVAILABLE; |
1754 | 148 | return m_property_bool_ro(action, arg, mpctx->demuxer->seekable); |
1755 | 177 | } |
1756 | | |
1757 | | static int mp_property_partially_seekable(void *ctx, struct m_property *prop, |
1758 | | int action, void *arg) |
1759 | 64 | { |
1760 | 64 | MPContext *mpctx = ctx; |
1761 | 64 | if (!mpctx->demuxer) |
1762 | 4 | return M_PROPERTY_UNAVAILABLE; |
1763 | 60 | return m_property_bool_ro(action, arg, mpctx->demuxer->partially_seekable); |
1764 | 64 | } |
1765 | | |
1766 | | static int mp_property_mixer_active(void *ctx, struct m_property *prop, |
1767 | | int action, void *arg) |
1768 | 220 | { |
1769 | 220 | MPContext *mpctx = ctx; |
1770 | 220 | return m_property_bool_ro(action, arg, !!mpctx->ao); |
1771 | 220 | } |
1772 | | |
1773 | | /// Volume (RW) |
1774 | | static int mp_property_volume(void *ctx, struct m_property *prop, |
1775 | | int action, void *arg) |
1776 | 917 | { |
1777 | 917 | MPContext *mpctx = ctx; |
1778 | 917 | struct MPOpts *opts = mpctx->opts; |
1779 | | |
1780 | 917 | switch (action) { |
1781 | 268 | case M_PROPERTY_GET_CONSTRICTED_TYPE: |
1782 | 268 | *(struct m_option *)arg = (struct m_option){ |
1783 | 268 | .type = CONF_TYPE_FLOAT, |
1784 | 268 | .min = 0, |
1785 | 268 | .max = opts->softvol_max, |
1786 | 268 | }; |
1787 | 268 | return M_PROPERTY_OK; |
1788 | 148 | case M_PROPERTY_PRINT: |
1789 | 148 | *(char **)arg = talloc_asprintf(NULL, "%.f", opts->softvol_volume); |
1790 | 148 | return M_PROPERTY_OK; |
1791 | 917 | } |
1792 | | |
1793 | 501 | return mp_property_generic_option(mpctx, prop, action, arg); |
1794 | 917 | } |
1795 | | |
1796 | | static int mp_property_volume_gain(void *ctx, struct m_property *prop, |
1797 | | int action, void *arg) |
1798 | 1.01k | { |
1799 | 1.01k | MPContext *mpctx = ctx; |
1800 | 1.01k | struct MPOpts *opts = mpctx->opts; |
1801 | | |
1802 | 1.01k | switch (action) { |
1803 | 318 | case M_PROPERTY_GET_CONSTRICTED_TYPE: |
1804 | 318 | *(struct m_option *)arg = (struct m_option){ |
1805 | 318 | .type = CONF_TYPE_FLOAT, |
1806 | 318 | .min = opts->softvol_gain_min, |
1807 | 318 | .max = opts->softvol_gain_max, |
1808 | 318 | }; |
1809 | 318 | return M_PROPERTY_OK; |
1810 | 245 | case M_PROPERTY_PRINT: |
1811 | 245 | *(char **)arg = talloc_asprintf(NULL, "%.1f", opts->softvol_gain); |
1812 | 245 | return M_PROPERTY_OK; |
1813 | 1.01k | } |
1814 | | |
1815 | 450 | return mp_property_generic_option(mpctx, prop, action, arg); |
1816 | 1.01k | } |
1817 | | |
1818 | | static int mp_property_ao_volume(void *ctx, struct m_property *prop, |
1819 | | int action, void *arg) |
1820 | 329 | { |
1821 | 329 | MPContext *mpctx = ctx; |
1822 | 329 | struct ao *ao = mpctx->ao; |
1823 | 329 | if (!ao) |
1824 | 51 | return M_PROPERTY_NOT_IMPLEMENTED; |
1825 | | |
1826 | 278 | switch (action) { |
1827 | 0 | case M_PROPERTY_SET: { |
1828 | 0 | float vol = *(float *)arg; |
1829 | 0 | if (ao_control(ao, AOCONTROL_SET_VOLUME, &vol) != CONTROL_OK) |
1830 | 0 | return M_PROPERTY_UNAVAILABLE; |
1831 | 0 | return M_PROPERTY_OK; |
1832 | 0 | } |
1833 | 72 | case M_PROPERTY_GET: { |
1834 | 72 | if (ao_control(ao, AOCONTROL_GET_VOLUME, arg) != CONTROL_OK) |
1835 | 72 | return M_PROPERTY_UNAVAILABLE; |
1836 | 0 | return M_PROPERTY_OK; |
1837 | 72 | } |
1838 | 72 | case M_PROPERTY_GET_TYPE: |
1839 | 72 | *(struct m_option *)arg = (struct m_option){ |
1840 | 72 | .type = CONF_TYPE_FLOAT, |
1841 | 72 | .min = 0, |
1842 | 72 | .max = 100, |
1843 | 72 | }; |
1844 | 72 | return M_PROPERTY_OK; |
1845 | 62 | case M_PROPERTY_PRINT: { |
1846 | 62 | float vol = 0; |
1847 | 62 | if (ao_control(ao, AOCONTROL_GET_VOLUME, &vol) != CONTROL_OK) |
1848 | 62 | return M_PROPERTY_UNAVAILABLE; |
1849 | 0 | *(char **)arg = talloc_asprintf(NULL, "%.f", vol); |
1850 | 0 | return M_PROPERTY_OK; |
1851 | 62 | } |
1852 | 278 | } |
1853 | 72 | return M_PROPERTY_NOT_IMPLEMENTED; |
1854 | 278 | } |
1855 | | |
1856 | | |
1857 | | static int mp_property_ao_mute(void *ctx, struct m_property *prop, |
1858 | | int action, void *arg) |
1859 | 479 | { |
1860 | 479 | MPContext *mpctx = ctx; |
1861 | 479 | struct ao *ao = mpctx->ao; |
1862 | 479 | if (!ao) |
1863 | 111 | return M_PROPERTY_NOT_IMPLEMENTED; |
1864 | | |
1865 | 368 | switch (action) { |
1866 | 0 | case M_PROPERTY_SET: { |
1867 | 0 | bool value = *(int *)arg; |
1868 | 0 | if (ao_control(ao, AOCONTROL_SET_MUTE, &value) != CONTROL_OK) |
1869 | 0 | return M_PROPERTY_UNAVAILABLE; |
1870 | 0 | return M_PROPERTY_OK; |
1871 | 0 | } |
1872 | 119 | case M_PROPERTY_GET: { |
1873 | 119 | bool value = false; |
1874 | 119 | if (ao_control(ao, AOCONTROL_GET_MUTE, &value) != CONTROL_OK) |
1875 | 119 | return M_PROPERTY_UNAVAILABLE; |
1876 | 0 | *(int *)arg = value; |
1877 | 0 | return M_PROPERTY_OK; |
1878 | 119 | } |
1879 | 119 | case M_PROPERTY_GET_TYPE: |
1880 | 119 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_BOOL}; |
1881 | 119 | return M_PROPERTY_OK; |
1882 | 368 | } |
1883 | 130 | return M_PROPERTY_NOT_IMPLEMENTED; |
1884 | 368 | } |
1885 | | |
1886 | | static int get_device_entry(int item, int action, void *arg, void *ctx) |
1887 | 177 | { |
1888 | 177 | struct ao_device_list *list = ctx; |
1889 | 177 | struct ao_device_desc *entry = &list->devices[item]; |
1890 | | |
1891 | 177 | struct m_sub_property props[] = { |
1892 | 177 | {"name", SUB_PROP_STR(entry->name)}, |
1893 | 177 | {"description", SUB_PROP_STR(entry->desc)}, |
1894 | 177 | {0} |
1895 | 177 | }; |
1896 | | |
1897 | 177 | return m_property_read_sub(props, action, arg); |
1898 | 177 | } |
1899 | | |
1900 | | static void create_hotplug(struct MPContext *mpctx) |
1901 | 820 | { |
1902 | 820 | struct command_ctx *cmd = mpctx->command_ctx; |
1903 | | |
1904 | 820 | if (!cmd->hotplug) { |
1905 | 106 | cmd->hotplug = ao_hotplug_create(mpctx->global, mp_wakeup_core_cb, |
1906 | 106 | mpctx); |
1907 | 106 | } |
1908 | 820 | } |
1909 | | |
1910 | | static int mp_property_audio_device(void *ctx, struct m_property *prop, |
1911 | | int action, void *arg) |
1912 | 768 | { |
1913 | 768 | struct MPContext *mpctx = ctx; |
1914 | 768 | struct command_ctx *cmd = mpctx->command_ctx; |
1915 | 768 | if (action == M_PROPERTY_PRINT) { |
1916 | 232 | create_hotplug(mpctx); |
1917 | | |
1918 | 232 | char *name = NULL; |
1919 | 232 | if (mp_property_generic_option(mpctx, prop, M_PROPERTY_GET, &name) < 1) |
1920 | 0 | name = NULL; |
1921 | | |
1922 | 232 | struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug, mpctx->ao); |
1923 | 232 | for (int n = 0; n < list->num_devices; n++) { |
1924 | 232 | struct ao_device_desc *dev = &list->devices[n]; |
1925 | 232 | if (dev->name && name && strcmp(dev->name, name) == 0) { |
1926 | 232 | *(char **)arg = talloc_strdup(NULL, dev->desc ? dev->desc : "?"); |
1927 | 232 | talloc_free(name); |
1928 | 232 | return M_PROPERTY_OK; |
1929 | 232 | } |
1930 | 232 | } |
1931 | | |
1932 | 0 | talloc_free(name); |
1933 | 0 | } |
1934 | 536 | return mp_property_generic_option(mpctx, prop, action, arg); |
1935 | 768 | } |
1936 | | |
1937 | | static int mp_property_audio_devices(void *ctx, struct m_property *prop, |
1938 | | int action, void *arg) |
1939 | 588 | { |
1940 | 588 | struct MPContext *mpctx = ctx; |
1941 | 588 | struct command_ctx *cmd = mpctx->command_ctx; |
1942 | 588 | create_hotplug(mpctx); |
1943 | | |
1944 | 588 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
1945 | 588 | if (valid != M_PROPERTY_VALID) |
1946 | 390 | return valid; |
1947 | | |
1948 | 198 | struct ao_device_list *list = ao_hotplug_get_device_list(cmd->hotplug, mpctx->ao); |
1949 | 198 | return m_property_read_list(action, arg, list->num_devices, |
1950 | 198 | get_device_entry, list); |
1951 | 588 | } |
1952 | | |
1953 | | static int mp_property_ao(void *ctx, struct m_property *p, int action, void *arg) |
1954 | 240 | { |
1955 | 240 | MPContext *mpctx = ctx; |
1956 | 240 | return m_property_strdup_ro(action, arg, mpctx->ao ? ao_get_name(mpctx->ao) : NULL); |
1957 | 240 | } |
1958 | | |
1959 | | /// Audio delay (RW) |
1960 | | static int mp_property_audio_delay(void *ctx, struct m_property *prop, |
1961 | | int action, void *arg) |
1962 | 1.94k | { |
1963 | 1.94k | MPContext *mpctx = ctx; |
1964 | 1.94k | if (action == M_PROPERTY_PRINT) { |
1965 | 147 | *(char **)arg = format_delay(mpctx->opts->audio_delay); |
1966 | 147 | return M_PROPERTY_OK; |
1967 | 147 | } |
1968 | 1.79k | return mp_property_generic_option(mpctx, prop, action, arg); |
1969 | 1.94k | } |
1970 | | |
1971 | | static int property_audiofmt(struct mp_aframe *fmt, int action, void *arg) |
1972 | 621 | { |
1973 | 621 | if (!fmt || !mp_aframe_config_is_valid(fmt)) |
1974 | 358 | return M_PROPERTY_UNAVAILABLE; |
1975 | | |
1976 | 263 | struct mp_chmap chmap = {0}; |
1977 | 263 | mp_aframe_get_chmap(fmt, &chmap); |
1978 | | |
1979 | 263 | struct m_sub_property props[] = { |
1980 | 263 | {"samplerate", SUB_PROP_INT(mp_aframe_get_rate(fmt))}, |
1981 | 263 | {"channel-count", SUB_PROP_INT(chmap.num)}, |
1982 | 263 | {"channels", SUB_PROP_STR(mp_chmap_to_str(&chmap))}, |
1983 | 263 | {"hr-channels", SUB_PROP_STR(mp_chmap_to_str_hr(&chmap))}, |
1984 | 263 | {"format", SUB_PROP_STR(af_fmt_to_str(mp_aframe_get_format(fmt)))}, |
1985 | 263 | {0} |
1986 | 263 | }; |
1987 | | |
1988 | 263 | return m_property_read_sub(props, action, arg); |
1989 | 621 | } |
1990 | | |
1991 | | static int mp_property_audio_params(void *ctx, struct m_property *prop, |
1992 | | int action, void *arg) |
1993 | 941 | { |
1994 | 941 | MPContext *mpctx = ctx; |
1995 | | |
1996 | 941 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
1997 | 941 | if (valid != M_PROPERTY_VALID) |
1998 | 537 | return valid; |
1999 | | |
2000 | 404 | return property_audiofmt(mpctx->ao_chain ? |
2001 | 404 | mpctx->ao_chain->filter->input_aformat : NULL, action, arg); |
2002 | 941 | } |
2003 | | |
2004 | | static int mp_property_audio_out_params(void *ctx, struct m_property *prop, |
2005 | | int action, void *arg) |
2006 | 534 | { |
2007 | 534 | MPContext *mpctx = ctx; |
2008 | | |
2009 | 534 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2010 | 534 | if (valid != M_PROPERTY_VALID) |
2011 | 317 | return valid; |
2012 | | |
2013 | 217 | struct mp_aframe *frame = NULL; |
2014 | 217 | if (mpctx->ao) { |
2015 | 85 | frame = mp_aframe_create(); |
2016 | 85 | int samplerate; |
2017 | 85 | int format; |
2018 | 85 | struct mp_chmap channels; |
2019 | 85 | ao_get_format(mpctx->ao, &samplerate, &format, &channels); |
2020 | 85 | mp_aframe_set_rate(frame, samplerate); |
2021 | 85 | mp_aframe_set_format(frame, format); |
2022 | 85 | mp_aframe_set_chmap(frame, &channels); |
2023 | 85 | } |
2024 | 217 | int r = property_audiofmt(frame, action, arg); |
2025 | 217 | talloc_free(frame); |
2026 | 217 | return r; |
2027 | 534 | } |
2028 | | |
2029 | | static struct track* track_next(struct MPContext *mpctx, enum stream_type type, |
2030 | | int direction, struct track *track) |
2031 | 0 | { |
2032 | 0 | mp_assert(direction == -1 || direction == +1); |
2033 | 0 | struct track *prev = NULL, *next = NULL; |
2034 | 0 | bool seen = track == NULL; |
2035 | 0 | for (int n = 0; n < mpctx->num_tracks; n++) { |
2036 | 0 | struct track *cur = mpctx->tracks[n]; |
2037 | 0 | if (cur->type == type && track_is_visible(mpctx, cur)) { |
2038 | 0 | if (cur == track) { |
2039 | 0 | seen = true; |
2040 | 0 | } else if (!cur->selected) { |
2041 | 0 | if (seen && !next) { |
2042 | 0 | next = cur; |
2043 | 0 | } |
2044 | 0 | if (!seen || !track) { |
2045 | 0 | prev = cur; |
2046 | 0 | } |
2047 | 0 | } |
2048 | 0 | } |
2049 | 0 | } |
2050 | 0 | return direction > 0 ? next : prev; |
2051 | 0 | } |
2052 | | |
2053 | | static int mp_property_switch_track(void *ctx, struct m_property *prop, |
2054 | | int action, void *arg) |
2055 | 7.92k | { |
2056 | 7.92k | MPContext *mpctx = ctx; |
2057 | 7.92k | const int *def = prop->priv; |
2058 | 7.92k | int order = def[0]; |
2059 | 7.92k | enum stream_type type = def[1]; |
2060 | | |
2061 | 7.92k | struct track *track = mpctx->current_track[order][type]; |
2062 | | |
2063 | 7.92k | switch (action) { |
2064 | 424 | case M_PROPERTY_GET: |
2065 | 424 | if (mpctx->playback_initialized) { |
2066 | 235 | *(int *)arg = track ? track->user_tid : -2; |
2067 | 235 | } else { |
2068 | 189 | *(int *)arg = mpctx->opts->stream_id[order][type]; |
2069 | 189 | } |
2070 | 424 | return M_PROPERTY_OK; |
2071 | 791 | case M_PROPERTY_PRINT: |
2072 | 791 | if (track) { |
2073 | 316 | void *talloc_ctx = talloc_new(NULL); |
2074 | 316 | *(char **)arg = talloc_asprintf(NULL, "(%d) %s", track->user_tid, |
2075 | 316 | mp_format_track_metadata(talloc_ctx, track, true)); |
2076 | 316 | talloc_free(talloc_ctx); |
2077 | 475 | } else { |
2078 | 475 | const char *msg = "no"; |
2079 | 475 | if (!mpctx->playback_initialized && |
2080 | 382 | mpctx->opts->stream_id[order][type] == -1) |
2081 | 276 | msg = "auto"; |
2082 | 475 | *(char **) arg = talloc_strdup(NULL, msg); |
2083 | 475 | } |
2084 | 791 | return M_PROPERTY_OK; |
2085 | | |
2086 | 0 | case M_PROPERTY_SWITCH: { |
2087 | 0 | if (mpctx->playback_initialized) { |
2088 | 0 | struct m_property_switch_arg *sarg = arg; |
2089 | 0 | do { |
2090 | 0 | track = track_next(mpctx, type, sarg->inc >= 0 ? +1 : -1, track); |
2091 | 0 | mp_switch_track_n(mpctx, order, type, track, FLAG_MARK_SELECTION); |
2092 | 0 | } while (mpctx->current_track[order][type] != track); |
2093 | 0 | print_track_list(mpctx, "Track switched:"); |
2094 | 0 | } else { |
2095 | | // Simply cycle between "no" and "auto". It's possible that this does |
2096 | | // not always do what the user means, but keep the complexity low. |
2097 | 0 | mark_track_selection(mpctx, order, type, |
2098 | 0 | mpctx->opts->stream_id[order][type] == -1 ? -2 : -1); |
2099 | 0 | } |
2100 | 0 | return M_PROPERTY_OK; |
2101 | 0 | } |
2102 | 7.92k | } |
2103 | 6.71k | return mp_property_generic_option(mpctx, prop, action, arg); |
2104 | 7.92k | } |
2105 | | |
2106 | | static int track_channels(struct track *track) |
2107 | 5.19k | { |
2108 | 5.19k | return track->stream ? track->stream->codec->channels.num : 0; |
2109 | 5.19k | } |
2110 | | |
2111 | | static int get_track_entry(int item, int action, void *arg, void *ctx) |
2112 | 2.59k | { |
2113 | 2.59k | struct MPContext *mpctx = ctx; |
2114 | 2.59k | struct track *track = mpctx->tracks[item]; |
2115 | | |
2116 | 2.59k | struct mp_codec_params p = |
2117 | 2.59k | track->stream ? *track->stream->codec : (struct mp_codec_params){0}; |
2118 | | |
2119 | 2.59k | bool has_rg = track->stream && track->stream->codec->replaygain_data; |
2120 | 2.59k | struct replaygain_data rg = has_rg ? *track->stream->codec->replaygain_data |
2121 | 2.59k | : (struct replaygain_data){0}; |
2122 | | |
2123 | 2.59k | struct mp_tags *tags = track->stream ? track->stream->tags : &(struct mp_tags){0}; |
2124 | 2.59k | char **tag_list = talloc_zero_array(NULL, char *, tags->num_keys * 2 + 1); |
2125 | 2.59k | for (int i = 0; i < tags->num_keys; i++) { |
2126 | 0 | tag_list[2 * i] = talloc_strdup(tag_list, tags->keys[i]); |
2127 | 0 | tag_list[2 * i + 1] = talloc_strdup(tag_list, tags->values[i]); |
2128 | 0 | } |
2129 | | |
2130 | 2.59k | struct sh_stream *sh = track->stream; |
2131 | | |
2132 | 2.59k | double par = 0.0; |
2133 | 2.59k | if (p.par_h) |
2134 | 1.22k | par = p.par_w / (double) p.par_h; |
2135 | | |
2136 | 2.59k | int order = -1; |
2137 | 2.59k | if (track->selected) { |
2138 | 2.59k | for (int i = 0; i < num_ptracks[track->type]; i++) { |
2139 | 2.59k | if (mpctx->current_track[i][track->type] == track) { |
2140 | 2.59k | order = i; |
2141 | 2.59k | break; |
2142 | 2.59k | } |
2143 | 2.59k | } |
2144 | 2.59k | } |
2145 | | |
2146 | 2.59k | bool has_crop = mp_rect_w(p.crop) > 0 && mp_rect_h(p.crop) > 0; |
2147 | 2.59k | struct m_sub_property props[] = { |
2148 | 2.59k | {"id", SUB_PROP_INT(track->user_tid)}, |
2149 | 2.59k | {"type", SUB_PROP_STR(stream_type_name(track->type)), |
2150 | 2.59k | .unavailable = !stream_type_name(track->type)}, |
2151 | 2.59k | {"src-id", SUB_PROP_INT(track->demuxer_id), |
2152 | 2.59k | .unavailable = track->demuxer_id == -1}, |
2153 | 2.59k | {"title", SUB_PROP_STR(track->title), |
2154 | 2.59k | .unavailable = !track->title}, |
2155 | 2.59k | {"lang", SUB_PROP_STR(track->lang), |
2156 | 2.59k | .unavailable = !track->lang}, |
2157 | 2.59k | {"audio-channels", SUB_PROP_INT(track_channels(track)), |
2158 | 2.59k | .unavailable = track_channels(track) <= 0}, |
2159 | 2.59k | {"image", SUB_PROP_BOOL(track->image)}, |
2160 | 2.59k | {"albumart", SUB_PROP_BOOL(track->attached_picture)}, |
2161 | 2.59k | {"default", SUB_PROP_BOOL(track->default_track)}, |
2162 | 2.59k | {"forced", SUB_PROP_BOOL(track->forced_track)}, |
2163 | 2.59k | {"dependent", SUB_PROP_BOOL(track->dependent_track)}, |
2164 | 2.59k | {"visual-impaired", SUB_PROP_BOOL(track->visual_impaired_track)}, |
2165 | 2.59k | {"hearing-impaired", SUB_PROP_BOOL(track->hearing_impaired_track)}, |
2166 | 2.59k | {"original", SUB_PROP_BOOL(track->original_track)}, |
2167 | 2.59k | {"commentary", SUB_PROP_BOOL(track->commentary_track)}, |
2168 | 2.59k | {"external", SUB_PROP_BOOL(track->is_external)}, |
2169 | 2.59k | {"selected", SUB_PROP_BOOL(track->selected)}, |
2170 | 2.59k | {"main-selection", SUB_PROP_INT(order), .unavailable = order < 0}, |
2171 | 2.59k | {"external-filename", SUB_PROP_STR(track->external_filename), |
2172 | 2.59k | .unavailable = !track->external_filename}, |
2173 | 2.59k | {"ff-index", SUB_PROP_INT(track->ff_index), |
2174 | 2.59k | .unavailable = track->ff_index == -1}, |
2175 | 2.59k | {"hls-bitrate", SUB_PROP_INT(track->hls_bitrate), |
2176 | 2.59k | .unavailable = !track->hls_bitrate}, |
2177 | 2.59k | {"program-id", SUB_PROP_INT(sh && sh->num_program_ids ? sh->program_ids[0] : -1), |
2178 | 2.59k | .unavailable = !sh || !sh->num_program_ids}, |
2179 | 2.59k | {"program-ids", SUB_PROP_INT_ARRAY(sh ? sh->program_ids : NULL, |
2180 | 2.59k | sh ? sh->num_program_ids : 0), |
2181 | 2.59k | .unavailable = !sh || !sh->num_program_ids}, |
2182 | 2.59k | {"decoder", SUB_PROP_STR(p.decoder), |
2183 | 2.59k | .unavailable = !p.decoder}, |
2184 | 2.59k | {"decoder-desc", SUB_PROP_STR(p.decoder_desc), |
2185 | 2.59k | .unavailable = !p.decoder_desc}, |
2186 | 2.59k | {"codec", SUB_PROP_STR(p.codec), |
2187 | 2.59k | .unavailable = !p.codec}, |
2188 | 2.59k | {"codec-desc", SUB_PROP_STR(p.codec_desc), |
2189 | 2.59k | .unavailable = !p.codec_desc}, |
2190 | 2.59k | {"codec-profile", SUB_PROP_STR(p.codec_profile), |
2191 | 2.59k | .unavailable = !p.codec_profile}, |
2192 | 2.59k | {"demux-w", SUB_PROP_INT(p.disp_w), .unavailable = !p.disp_w}, |
2193 | 2.59k | {"demux-h", SUB_PROP_INT(p.disp_h), .unavailable = !p.disp_h}, |
2194 | 2.59k | {"demux-crop-x",SUB_PROP_INT(p.crop.x0), .unavailable = !has_crop}, |
2195 | 2.59k | {"demux-crop-y",SUB_PROP_INT(p.crop.y0), .unavailable = !has_crop}, |
2196 | 2.59k | {"demux-crop-w",SUB_PROP_INT(mp_rect_w(p.crop)), .unavailable = !has_crop}, |
2197 | 2.59k | {"demux-crop-h",SUB_PROP_INT(mp_rect_h(p.crop)), .unavailable = !has_crop}, |
2198 | 2.59k | {"demux-channel-count", SUB_PROP_INT(p.channels.num), |
2199 | 2.59k | .unavailable = !p.channels.num}, |
2200 | 2.59k | {"demux-channels", SUB_PROP_STR(mp_chmap_to_str(&p.channels)), |
2201 | 2.59k | .unavailable = !p.channels.num}, |
2202 | 2.59k | {"demux-samplerate", SUB_PROP_INT(p.samplerate), |
2203 | 2.59k | .unavailable = !p.samplerate}, |
2204 | 2.59k | {"demux-fps", SUB_PROP_DOUBLE(p.fps), .unavailable = p.fps <= 0}, |
2205 | 2.59k | {"demux-bitrate", SUB_PROP_INT(p.bitrate), .unavailable = p.bitrate <= 0}, |
2206 | 2.59k | {"demux-rotation", SUB_PROP_INT(p.rotate), .unavailable = p.rotate <= 0}, |
2207 | 2.59k | {"demux-par", SUB_PROP_DOUBLE(par), .unavailable = par <= 0}, |
2208 | 2.59k | {"demux-duration", SUB_PROP_PTS(p.duration), .unavailable = p.duration <= 0}, |
2209 | 2.59k | {"format-name", SUB_PROP_STR(p.format_name), .unavailable = !p.format_name}, |
2210 | 2.59k | {"replaygain-track-peak", SUB_PROP_FLOAT(rg.track_peak), |
2211 | 2.59k | .unavailable = !has_rg}, |
2212 | 2.59k | {"replaygain-track-gain", SUB_PROP_FLOAT(rg.track_gain), |
2213 | 2.59k | .unavailable = !has_rg}, |
2214 | 2.59k | {"replaygain-album-peak", SUB_PROP_FLOAT(rg.album_peak), |
2215 | 2.59k | .unavailable = !has_rg}, |
2216 | 2.59k | {"replaygain-album-gain", SUB_PROP_FLOAT(rg.album_gain), |
2217 | 2.59k | .unavailable = !has_rg}, |
2218 | 2.59k | {"dolby-vision-profile", SUB_PROP_INT(p.dv_profile), |
2219 | 2.59k | .unavailable = !p.dovi}, |
2220 | 2.59k | {"dolby-vision-level", SUB_PROP_INT(p.dv_level), |
2221 | 2.59k | .unavailable = !p.dovi}, |
2222 | 2.59k | {"metadata", SUB_PROP_KEYVALUE_LIST(tag_list), |
2223 | 2.59k | .unavailable = !tags->num_keys}, |
2224 | 2.59k | {0} |
2225 | 2.59k | }; |
2226 | | |
2227 | 2.59k | int ret = 0; |
2228 | 2.59k | switch (action) { |
2229 | 2.32k | case M_PROPERTY_KEY_ACTION: ; |
2230 | 2.32k | struct m_property_action_arg *ka = arg; |
2231 | 2.32k | if (!strncmp(ka->key, "metadata/", 9)) { |
2232 | 0 | bstr key = {0}; |
2233 | 0 | const char *rem = ""; |
2234 | 0 | m_property_split_path(ka->key, &key, &rem); |
2235 | 0 | ka->key = rem; |
2236 | 0 | if (!rem[0]) { |
2237 | 0 | ret = M_PROPERTY_ERROR; |
2238 | 0 | } else if (tags->num_keys == 0) { |
2239 | 0 | ret = M_PROPERTY_UNAVAILABLE; |
2240 | 0 | } else { |
2241 | 0 | ret = tag_property(action, (void *)ka, tags); |
2242 | 0 | } |
2243 | 0 | goto done; |
2244 | 0 | } |
2245 | 2.32k | MP_FALLTHROUGH; |
2246 | 2.59k | default: |
2247 | 2.59k | ret = m_property_read_sub(props, action, arg); |
2248 | 2.59k | } |
2249 | | |
2250 | 2.59k | done: |
2251 | 2.59k | talloc_free(tag_list); |
2252 | 2.59k | return ret; |
2253 | 2.59k | } |
2254 | | |
2255 | | static const char *track_type_name(struct track *t) |
2256 | 298 | { |
2257 | 298 | if (t->image) |
2258 | 0 | return "Image"; |
2259 | | |
2260 | 298 | switch (t->type) { |
2261 | 149 | case STREAM_VIDEO: return "Video"; |
2262 | 149 | case STREAM_AUDIO: return "Audio"; |
2263 | 0 | case STREAM_SUB: return "Sub"; |
2264 | 298 | } |
2265 | 0 | return NULL; |
2266 | 298 | } |
2267 | | |
2268 | | static char *append_track_info(char *res, struct track *track) |
2269 | 436 | { |
2270 | 436 | res = talloc_strdup_append(res, track->selected ? list_current : list_normal); |
2271 | 436 | res = talloc_asprintf_append(res, "(%d) ", track->user_tid); |
2272 | 436 | res = talloc_strdup_append(res, mp_format_track_metadata(res, track, true)); |
2273 | | |
2274 | 436 | return res; |
2275 | 436 | } |
2276 | | |
2277 | | struct filtered_track_ctx { |
2278 | | struct MPContext *mpctx; |
2279 | | int *map; |
2280 | | }; |
2281 | | |
2282 | | static int get_filtered_track_entry(int item, int action, void *arg, void *ctx) |
2283 | 2.59k | { |
2284 | 2.59k | struct filtered_track_ctx *ft = ctx; |
2285 | 2.59k | return get_track_entry(ft->map[item], action, arg, ft->mpctx); |
2286 | 2.59k | } |
2287 | | |
2288 | | static int mp_property_list_tracks(void *ctx, struct m_property *prop, |
2289 | | int action, void *arg) |
2290 | 6.73k | { |
2291 | 6.73k | MPContext *mpctx = ctx; |
2292 | 6.73k | if (action == M_PROPERTY_PRINT) { |
2293 | 243 | char *res = talloc_strdup(NULL, ""); |
2294 | | |
2295 | 243 | int spacing = 0; |
2296 | 972 | for (int type = 0; type < STREAM_TYPE_COUNT; type++) { |
2297 | 1.62k | for (int n = 0; n < mpctx->num_tracks; n++) { |
2298 | 894 | struct track *track = mpctx->tracks[n]; |
2299 | 894 | if (track->type != type) |
2300 | 596 | continue; |
2301 | 298 | if (!track_is_visible(mpctx, track)) |
2302 | 0 | continue; |
2303 | 298 | res = talloc_asprintf_append(res, "%s%s: ", |
2304 | 298 | spacing == 2 ? "\n\n" : spacing == 1 ? "\n" : "", |
2305 | 298 | track_type_name(track)); |
2306 | 298 | res = append_track_info(res, track); |
2307 | 298 | spacing = 1; |
2308 | 298 | } |
2309 | 729 | if (spacing) |
2310 | 447 | spacing = 2; |
2311 | 729 | } |
2312 | | |
2313 | 243 | struct demuxer *demuxer = mpctx->demuxer; |
2314 | 243 | if (demuxer && demuxer->num_editions > 1) { |
2315 | 0 | res = talloc_asprintf_append(res, "%sEdition: %d of %d", |
2316 | 0 | spacing == 2 ? "\n\n" : "", |
2317 | 0 | demuxer->edition + 1, |
2318 | 0 | demuxer->num_editions); |
2319 | 0 | } |
2320 | | |
2321 | 243 | *(char **)arg = res; |
2322 | 243 | return M_PROPERTY_OK; |
2323 | 243 | } |
2324 | | |
2325 | 6.49k | if (action == M_PROPERTY_KEY_ACTION) { |
2326 | 5.38k | struct m_property_action_arg *ka = arg; |
2327 | | |
2328 | 5.38k | int type = -1; |
2329 | 5.38k | if (!strcmp(ka->key, "video")) { |
2330 | 590 | type = STREAM_VIDEO; |
2331 | 4.79k | } else if (!strcmp(ka->key, "audio")) { |
2332 | 772 | type = STREAM_AUDIO; |
2333 | 4.01k | } else if (!strcmp(ka->key, "sub")) { |
2334 | 530 | type = STREAM_SUB; |
2335 | 530 | } |
2336 | | |
2337 | 5.38k | if (type != -1) { |
2338 | 1.89k | char *res; |
2339 | | |
2340 | 1.89k | switch (ka->action) { |
2341 | 594 | case M_PROPERTY_GET_TYPE: |
2342 | 594 | *(struct m_option *)ka->arg = (struct m_option){.type = CONF_TYPE_STRING}; |
2343 | 594 | return M_PROPERTY_OK; |
2344 | 469 | case M_PROPERTY_PRINT: |
2345 | 469 | res = talloc_asprintf(NULL, "Available %s tracks:", |
2346 | 469 | type == STREAM_SUB ? "subtitle" : stream_type_name(type)); |
2347 | | |
2348 | 789 | for (int n = 0; n < mpctx->num_tracks; n++) { |
2349 | 320 | struct track *track = mpctx->tracks[n]; |
2350 | 320 | if (track->type == type && track_is_visible(mpctx, track)) { |
2351 | 138 | res = talloc_strdup_append(res, "\n"); |
2352 | 138 | res = append_track_info(res, track); |
2353 | 138 | } |
2354 | 320 | } |
2355 | | |
2356 | 469 | *(char **)ka->arg = res; |
2357 | 469 | return M_PROPERTY_OK; |
2358 | 829 | default: |
2359 | 829 | return M_PROPERTY_NOT_IMPLEMENTED; |
2360 | 1.89k | } |
2361 | 1.89k | } |
2362 | 5.38k | } |
2363 | | |
2364 | 4.60k | int *map = talloc_array(NULL, int, mpctx->num_tracks); |
2365 | 4.60k | int count = 0; |
2366 | 10.9k | for (int n = 0; n < mpctx->num_tracks; n++) { |
2367 | 6.31k | if (track_is_visible(mpctx, mpctx->tracks[n])) |
2368 | 6.31k | map[count++] = n; |
2369 | 6.31k | } |
2370 | 4.60k | struct filtered_track_ctx ft = { .mpctx = mpctx, .map = map }; |
2371 | 4.60k | int r = m_property_read_list(action, arg, count, get_filtered_track_entry, &ft); |
2372 | 4.60k | talloc_free(map); |
2373 | 4.60k | return r; |
2374 | 6.49k | } |
2375 | | |
2376 | | static int mp_property_current_tracks(void *ctx, struct m_property *prop, |
2377 | | int action, void *arg) |
2378 | 1.99k | { |
2379 | 1.99k | MPContext *mpctx = ctx; |
2380 | | |
2381 | 1.99k | if (action != M_PROPERTY_KEY_ACTION) |
2382 | 64 | return M_PROPERTY_UNAVAILABLE; |
2383 | | |
2384 | 1.93k | int type = -1; |
2385 | 1.93k | int order = 0; |
2386 | | |
2387 | 1.93k | struct m_property_action_arg *ka = arg; |
2388 | 1.93k | bstr key; |
2389 | 1.93k | const char *rem; |
2390 | 1.93k | m_property_split_path(ka->key, &key, &rem); |
2391 | | |
2392 | 1.93k | if (bstr_equals0(key, "video")) { |
2393 | 613 | type = STREAM_VIDEO; |
2394 | 1.31k | } else if (bstr_equals0(key, "audio")) { |
2395 | 699 | type = STREAM_AUDIO; |
2396 | 699 | } else if (bstr_equals0(key, "sub")) { |
2397 | 67 | type = STREAM_SUB; |
2398 | 553 | } else if (bstr_equals0(key, "sub2")) { |
2399 | 90 | type = STREAM_SUB; |
2400 | 90 | order = 1; |
2401 | 90 | } |
2402 | | |
2403 | 1.93k | if (type < 0) |
2404 | 463 | return M_PROPERTY_UNKNOWN; |
2405 | | |
2406 | 1.46k | struct track *t = mpctx->current_track[order][type]; |
2407 | | |
2408 | 1.46k | if (!t && mpctx->lavfi) { |
2409 | 0 | for (int n = 0; n < mpctx->num_tracks; n++) { |
2410 | 0 | if (mpctx->tracks[n]->type == type && mpctx->tracks[n]->selected) { |
2411 | 0 | t = mpctx->tracks[n]; |
2412 | 0 | break; |
2413 | 0 | } |
2414 | 0 | } |
2415 | 0 | } |
2416 | | |
2417 | 1.46k | if (!t) |
2418 | 308 | return M_PROPERTY_UNAVAILABLE; |
2419 | | |
2420 | 1.16k | int index = -1; |
2421 | 1.16k | int filtered_index = 0; |
2422 | 1.77k | for (int n = 0; n < mpctx->num_tracks; n++) { |
2423 | 1.77k | if (!track_is_visible(mpctx, mpctx->tracks[n])) |
2424 | 0 | continue; |
2425 | 1.77k | if (mpctx->tracks[n] == t) { |
2426 | 1.16k | index = filtered_index; |
2427 | 1.16k | break; |
2428 | 1.16k | } |
2429 | 610 | filtered_index++; |
2430 | 610 | } |
2431 | 1.16k | if (index < 0) |
2432 | 0 | return M_PROPERTY_UNAVAILABLE; |
2433 | | |
2434 | 1.16k | char *name = mp_tprintf(80, "track-list/%d%s%s", index, *rem ? "/" : "", rem); |
2435 | 1.16k | return mp_property_do(name, ka->action, ka->arg, ctx); |
2436 | 1.16k | } |
2437 | | |
2438 | | static int mp_property_hwdec_current(void *ctx, struct m_property *prop, |
2439 | | int action, void *arg) |
2440 | 193 | { |
2441 | 193 | MPContext *mpctx = ctx; |
2442 | 193 | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
2443 | 193 | struct mp_decoder_wrapper *dec = track ? track->dec : NULL; |
2444 | 193 | char *current = NULL; |
2445 | | |
2446 | 193 | if (!dec || !mp_decoder_wrapper_control(dec, VDCTRL_GET_HWDEC, ¤t)) |
2447 | 35 | return M_PROPERTY_UNAVAILABLE; |
2448 | | |
2449 | 158 | if (!current || !current[0]) |
2450 | 158 | current = "no"; |
2451 | 158 | return m_property_strdup_ro(action, arg, current); |
2452 | 193 | } |
2453 | | |
2454 | | static int mp_property_hwdec_interop(void *ctx, struct m_property *prop, |
2455 | | int action, void *arg) |
2456 | 189 | { |
2457 | 189 | MPContext *mpctx = ctx; |
2458 | 189 | if (!mpctx->video_out || !mpctx->video_out->hwdec_devs) |
2459 | 189 | return M_PROPERTY_UNAVAILABLE; |
2460 | | |
2461 | 0 | char *names = hwdec_devices_get_names(mpctx->video_out->hwdec_devs); |
2462 | 0 | int res = m_property_strdup_ro(action, arg, names); |
2463 | 0 | talloc_free(names); |
2464 | 0 | return res; |
2465 | 189 | } |
2466 | | |
2467 | | static int get_frame_count(struct MPContext *mpctx) |
2468 | 1.25k | { |
2469 | 1.25k | struct demuxer *demuxer = mpctx->demuxer; |
2470 | 1.25k | if (!demuxer) |
2471 | 23 | return -1; |
2472 | 1.23k | if (!mpctx->vo_chain) |
2473 | 42 | return -1; |
2474 | 1.19k | double len = get_time_length(mpctx); |
2475 | 1.19k | double fps = mpctx->vo_chain->filter->container_fps; |
2476 | 1.19k | if (len < 0 || fps <= 0) |
2477 | 0 | return 0; |
2478 | | |
2479 | 1.19k | return len * fps; |
2480 | 1.19k | } |
2481 | | |
2482 | | static int mp_property_frame_number(void *ctx, struct m_property *prop, |
2483 | | int action, void *arg) |
2484 | 369 | { |
2485 | 369 | MPContext *mpctx = ctx; |
2486 | 369 | int frames = get_frame_count(mpctx); |
2487 | 369 | if (frames < 0) |
2488 | 37 | return M_PROPERTY_UNAVAILABLE; |
2489 | | |
2490 | 332 | return m_property_int_ro(action, arg, |
2491 | 332 | lrint(get_current_pos_ratio(mpctx, false) * frames)); |
2492 | 369 | } |
2493 | | |
2494 | | static int mp_property_frame_count(void *ctx, struct m_property *prop, |
2495 | | int action, void *arg) |
2496 | 656 | { |
2497 | 656 | MPContext *mpctx = ctx; |
2498 | 656 | int frames = get_frame_count(mpctx); |
2499 | 656 | if (frames < 0) |
2500 | 28 | return M_PROPERTY_UNAVAILABLE; |
2501 | | |
2502 | 628 | return m_property_int_ro(action, arg, frames); |
2503 | 656 | } |
2504 | | |
2505 | | static const char *get_aspect_ratio_name(double ratio) |
2506 | 24.1k | { |
2507 | | // Depending on cropping/mastering exact ratio may differ. |
2508 | 154k | #define RATIO_THRESH 0.025 |
2509 | 24.1k | #define RATIO_CASE(ref, name) \ |
2510 | 154k | if (fabs(ratio - (ref)) < RATIO_THRESH) \ |
2511 | 154k | return name; \ |
2512 | 24.1k | |
2513 | | // https://en.wikipedia.org/wiki/Aspect_ratio_(image) |
2514 | 24.1k | RATIO_CASE(9.0 / 16.0, "Vertical") |
2515 | 24.1k | RATIO_CASE(1.0, "Square"); |
2516 | 24.1k | RATIO_CASE(19.0 / 16.0, "Movietone Ratio"); |
2517 | 24.1k | RATIO_CASE(5.0 / 4.0, "5:4"); |
2518 | 24.1k | RATIO_CASE(4.0 / 3.0, "4:3"); |
2519 | 5.64k | RATIO_CASE(11.0 / 8.0, "Academy Ratio"); |
2520 | 5.64k | RATIO_CASE(1.43, "IMAX Ratio"); |
2521 | 5.64k | RATIO_CASE(3.0 / 2.0, "VistaVision Ratio"); |
2522 | 5.64k | RATIO_CASE(16.0 / 10.0, "16:10"); |
2523 | 5.64k | RATIO_CASE(5.0 / 3.0, "35mm Widescreen Ratio"); |
2524 | 5.64k | RATIO_CASE(16.0 / 9.0, "16:9"); |
2525 | 0 | RATIO_CASE(7.0 / 4.0, "Early 35mm Widescreen Ratio"); |
2526 | 0 | RATIO_CASE(1.85, "Academy Flat"); |
2527 | 0 | RATIO_CASE(256.0 / 135.0, "SMPTE/DCI Ratio"); |
2528 | 0 | RATIO_CASE(2.0, "Univisium"); |
2529 | 0 | RATIO_CASE(2.208, "70mm film"); |
2530 | 0 | RATIO_CASE(2.35, "Scope"); |
2531 | 0 | RATIO_CASE(2.39, "Panavision"); |
2532 | 0 | RATIO_CASE(2.55, "Original CinemaScope"); |
2533 | 0 | RATIO_CASE(2.59, "Full-frame Cinerama"); |
2534 | 0 | RATIO_CASE(24.0 / 9.0, "Full-frame Super 16mm"); |
2535 | 0 | RATIO_CASE(2.76, "Ultra Panavision 70"); |
2536 | 0 | RATIO_CASE(32.0 / 9.0, "32:9"); |
2537 | 0 | RATIO_CASE(3.6, "Ultra-WideScreen 3.6"); |
2538 | 0 | RATIO_CASE(4.0, "Polyvision"); |
2539 | 0 | RATIO_CASE(12.0, "Circle-Vision 360°"); |
2540 | |
|
2541 | 0 | return NULL; |
2542 | |
|
2543 | 0 | #undef RATIO_THRESH |
2544 | 0 | #undef RATIO_CASE |
2545 | 0 | } |
2546 | | |
2547 | | static int property_imgparams(const struct mp_image_params *p, int action, void *arg) |
2548 | 12.4k | { |
2549 | 12.4k | if (!p->imgfmt && !p->imgfmt_name) |
2550 | 338 | return M_PROPERTY_UNAVAILABLE; |
2551 | | |
2552 | 12.0k | int d_w, d_h; |
2553 | 12.0k | mp_image_params_get_dsize(p, &d_w, &d_h); |
2554 | | |
2555 | 12.0k | int bpp = 0; |
2556 | 12.0k | enum pl_alpha_mode alpha = p->repr.alpha; |
2557 | 12.0k | int fmt = p->hw_subfmt ? p->hw_subfmt : p->imgfmt; |
2558 | 12.0k | if (fmt) { |
2559 | 12.0k | struct mp_imgfmt_desc desc = mp_imgfmt_get_desc(fmt); |
2560 | 42.6k | for (int i = 0; i < desc.num_planes; i++) |
2561 | 30.5k | bpp += desc.bpp[i] >> (desc.xs[i] + desc.ys[i]); |
2562 | | |
2563 | | // If PL_ALPHA_NONE is supported, use it directly, unless in auto mode. |
2564 | 12.0k | if ((desc.flags & MP_IMGFLAG_ALPHA) && alpha == PL_ALPHA_UNKNOWN) |
2565 | 472 | alpha = PL_ALPHA_INDEPENDENT; |
2566 | 12.0k | } |
2567 | | |
2568 | 12.0k | const struct pl_hdr_metadata *hdr = &p->color.hdr; |
2569 | 12.0k | bool has_cie_y = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_CIE_Y); |
2570 | 12.0k | bool has_hdr10 = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_HDR10); |
2571 | 12.0k | bool has_hdr10plus = pl_hdr_metadata_contains(hdr, PL_HDR_METADATA_HDR10PLUS); |
2572 | 12.0k | bool custom_prim = pl_primaries_valid(&hdr->prim) && |
2573 | 0 | !pl_raw_primaries_similar(&hdr->prim, pl_raw_primaries_get(p->color.primaries)); |
2574 | | |
2575 | 12.0k | bool has_crop = mp_rect_w(p->crop) > 0 && mp_rect_h(p->crop) > 0; |
2576 | 12.0k | const char *aspect_name = get_aspect_ratio_name(d_w / (double)d_h); |
2577 | 12.0k | const char *sar_name = get_aspect_ratio_name(p->w / (double)p->h); |
2578 | 12.0k | const char *pixelformat_name = p->imgfmt_name ? p->imgfmt_name : |
2579 | 12.0k | mp_imgfmt_to_name(p->imgfmt); |
2580 | 12.0k | struct m_sub_property props[] = { |
2581 | 12.0k | {"pixelformat", SUB_PROP_STR(pixelformat_name)}, |
2582 | 12.0k | {"hw-pixelformat", SUB_PROP_STR(mp_imgfmt_to_name(p->hw_subfmt)), |
2583 | 12.0k | .unavailable = !p->hw_subfmt}, |
2584 | 12.0k | {"average-bpp", SUB_PROP_INT(bpp), |
2585 | 12.0k | .unavailable = !bpp}, |
2586 | 12.0k | {"w", SUB_PROP_INT(p->w)}, |
2587 | 12.0k | {"h", SUB_PROP_INT(p->h)}, |
2588 | 12.0k | {"dw", SUB_PROP_INT(d_w)}, |
2589 | 12.0k | {"dh", SUB_PROP_INT(d_h)}, |
2590 | 12.0k | {"crop-x", SUB_PROP_INT(p->crop.x0), .unavailable = !has_crop}, |
2591 | 12.0k | {"crop-y", SUB_PROP_INT(p->crop.y0), .unavailable = !has_crop}, |
2592 | 12.0k | {"crop-w", SUB_PROP_INT(mp_rect_w(p->crop)), .unavailable = !has_crop}, |
2593 | 12.0k | {"crop-h", SUB_PROP_INT(mp_rect_h(p->crop)), .unavailable = !has_crop}, |
2594 | 12.0k | {"aspect", SUB_PROP_DOUBLE(d_w / (double)d_h)}, |
2595 | 12.0k | {"aspect-name", SUB_PROP_STR(aspect_name), .unavailable = !aspect_name}, |
2596 | 12.0k | {"par", SUB_PROP_DOUBLE(p->p_w / (double)p->p_h)}, |
2597 | 12.0k | {"sar", SUB_PROP_DOUBLE(p->w / (double)p->h)}, |
2598 | 12.0k | {"sar-name", SUB_PROP_STR(sar_name), .unavailable = !sar_name}, |
2599 | 12.0k | {"colormatrix", |
2600 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_csp_names, p->repr.sys))}, |
2601 | 12.0k | {"colorlevels", |
2602 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_csp_levels_names, p->repr.levels))}, |
2603 | 12.0k | {"primaries", |
2604 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_csp_prim_names, p->color.primaries))}, |
2605 | 12.0k | {"gamma", |
2606 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_csp_trc_names, p->color.transfer))}, |
2607 | 12.0k | {"sig-peak", SUB_PROP_FLOAT(p->color.hdr.max_luma / MP_REF_WHITE)}, |
2608 | 12.0k | {"light", |
2609 | 12.0k | SUB_PROP_STR(m_opt_choice_str(mp_csp_light_names, p->light))}, |
2610 | 12.0k | {"chroma-location", |
2611 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_chroma_names, p->chroma_location))}, |
2612 | 12.0k | {"stereo-in", |
2613 | 12.0k | SUB_PROP_STR(m_opt_choice_str(mp_stereo3d_names, p->stereo3d))}, |
2614 | 12.0k | {"rotate", SUB_PROP_INT(p->rotate)}, |
2615 | 12.0k | {"alpha", |
2616 | 12.0k | SUB_PROP_STR(m_opt_choice_str(pl_alpha_names, alpha)), |
2617 | | // avoid using "auto" for "no", so just make it unavailable |
2618 | 12.0k | .unavailable = alpha == PL_ALPHA_UNKNOWN}, |
2619 | 12.0k | {"min-luma", SUB_PROP_FLOAT(hdr->min_luma), .unavailable = !has_hdr10}, |
2620 | 12.0k | {"max-luma", SUB_PROP_FLOAT(hdr->max_luma), .unavailable = !has_hdr10}, |
2621 | 12.0k | {"max-cll", SUB_PROP_FLOAT(hdr->max_cll), .unavailable = !has_hdr10}, |
2622 | 12.0k | {"max-fall", SUB_PROP_FLOAT(hdr->max_fall), .unavailable = !has_hdr10}, |
2623 | 12.0k | {"scene-max-r", SUB_PROP_FLOAT(hdr->scene_max[0]), .unavailable = !has_hdr10plus}, |
2624 | 12.0k | {"scene-max-g", SUB_PROP_FLOAT(hdr->scene_max[1]), .unavailable = !has_hdr10plus}, |
2625 | 12.0k | {"scene-max-b", SUB_PROP_FLOAT(hdr->scene_max[2]), .unavailable = !has_hdr10plus}, |
2626 | 12.0k | {"scene-avg", SUB_PROP_FLOAT(hdr->scene_avg), .unavailable = !has_hdr10plus}, |
2627 | 12.0k | {"max-pq-y", SUB_PROP_FLOAT(hdr->max_pq_y), .unavailable = !has_cie_y}, |
2628 | 12.0k | {"avg-pq-y", SUB_PROP_FLOAT(hdr->avg_pq_y), .unavailable = !has_cie_y}, |
2629 | 12.0k | {"prim-red-x", SUB_PROP_FLOAT(hdr->prim.red.x), .unavailable = !custom_prim }, |
2630 | 12.0k | {"prim-red-y", SUB_PROP_FLOAT(hdr->prim.red.y), .unavailable = !custom_prim }, |
2631 | 12.0k | {"prim-green-x", SUB_PROP_FLOAT(hdr->prim.green.x),.unavailable = !custom_prim }, |
2632 | 12.0k | {"prim-green-y", SUB_PROP_FLOAT(hdr->prim.green.y),.unavailable = !custom_prim }, |
2633 | 12.0k | {"prim-blue-x", SUB_PROP_FLOAT(hdr->prim.blue.x), .unavailable = !custom_prim }, |
2634 | 12.0k | {"prim-blue-y", SUB_PROP_FLOAT(hdr->prim.blue.y), .unavailable = !custom_prim }, |
2635 | 12.0k | {"prim-white-x", SUB_PROP_FLOAT(hdr->prim.white.x),.unavailable = !custom_prim }, |
2636 | 12.0k | {"prim-white-y", SUB_PROP_FLOAT(hdr->prim.white.y),.unavailable = !custom_prim }, |
2637 | 12.0k | {0} |
2638 | 12.0k | }; |
2639 | | |
2640 | 12.0k | return m_property_read_sub(props, action, arg); |
2641 | 12.4k | } |
2642 | | |
2643 | | static struct mp_image_params get_video_out_params(struct MPContext *mpctx) |
2644 | 195 | { |
2645 | 195 | if (!mpctx->vo_chain) |
2646 | 21 | return (struct mp_image_params){0}; |
2647 | | |
2648 | 174 | struct mp_image_params o_params = mpctx->vo_chain->filter->output_params; |
2649 | 174 | if (mpctx->video_out) { |
2650 | 174 | struct m_geometry *gm = &mpctx->video_out->opts->video_crop; |
2651 | 174 | if (gm->xy_valid || (gm->wh_valid && (gm->w > 0 || gm->h > 0))) |
2652 | 0 | { |
2653 | 0 | m_rect_apply(&o_params.crop, o_params.w, o_params.h, gm); |
2654 | 0 | } |
2655 | 174 | } |
2656 | | |
2657 | 174 | return o_params; |
2658 | 195 | } |
2659 | | |
2660 | | static int mp_property_vo_display_swapchain(void *ctx, struct m_property *prop, |
2661 | | int action, void *arg) |
2662 | 77 | { |
2663 | 77 | MPContext *mpctx = ctx; |
2664 | 77 | struct vo *vo = mpctx->video_out; |
2665 | 77 | if (!vo) |
2666 | 18 | return M_PROPERTY_UNAVAILABLE; |
2667 | | |
2668 | 59 | void *swapchain_ptr = vo_get_display_swapchain(vo); |
2669 | 59 | if (swapchain_ptr == NULL) |
2670 | 59 | return M_PROPERTY_UNAVAILABLE; |
2671 | | |
2672 | 0 | int64_t swapchain = (intptr_t)swapchain_ptr; |
2673 | |
|
2674 | 0 | return m_property_int64_ro(action, arg, swapchain); |
2675 | 59 | } |
2676 | | |
2677 | | static int mp_property_vo_imgparams(void *ctx, struct m_property *prop, |
2678 | | int action, void *arg) |
2679 | 8.15k | { |
2680 | 8.15k | MPContext *mpctx = ctx; |
2681 | 8.15k | struct vo *vo = mpctx->video_out; |
2682 | 8.15k | if (!vo) |
2683 | 49 | return M_PROPERTY_UNAVAILABLE; |
2684 | | |
2685 | 8.11k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2686 | 8.11k | if (valid != M_PROPERTY_VALID) |
2687 | 1.11k | return valid; |
2688 | | |
2689 | 6.99k | struct mp_image_params p = vo_get_current_params(vo); |
2690 | 6.99k | return property_imgparams(&p, action, arg); |
2691 | 8.11k | } |
2692 | | |
2693 | | static int mp_property_tgt_imgparams(void *ctx, struct m_property *prop, |
2694 | | int action, void *arg) |
2695 | 224 | { |
2696 | 224 | MPContext *mpctx = ctx; |
2697 | 224 | struct vo *vo = mpctx->video_out; |
2698 | 224 | if (!mpctx->video_out) |
2699 | 32 | return M_PROPERTY_UNAVAILABLE; |
2700 | | |
2701 | 192 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2702 | 192 | if (valid != M_PROPERTY_VALID) |
2703 | 96 | return valid; |
2704 | | |
2705 | 96 | struct mp_image_params p = vo_get_target_params(vo); |
2706 | 96 | return property_imgparams(&p, action, arg); |
2707 | 192 | } |
2708 | | |
2709 | | static int mp_property_dec_imgparams(void *ctx, struct m_property *prop, |
2710 | | int action, void *arg) |
2711 | 1.05k | { |
2712 | 1.05k | MPContext *mpctx = ctx; |
2713 | 1.05k | struct mp_image_params p = {0}; |
2714 | 1.05k | struct vo_chain *vo_c = mpctx->vo_chain; |
2715 | 1.05k | if (!vo_c || !vo_c->track || !vo_c->track->dec) |
2716 | 89 | return M_PROPERTY_UNAVAILABLE; |
2717 | | |
2718 | 964 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2719 | 964 | if (valid != M_PROPERTY_VALID) |
2720 | 663 | return valid; |
2721 | | |
2722 | 301 | mp_decoder_wrapper_get_video_dec_params(vo_c->track->dec, &p); |
2723 | 301 | if (!p.imgfmt) |
2724 | 0 | return M_PROPERTY_UNAVAILABLE; |
2725 | 301 | return property_imgparams(&p, action, arg); |
2726 | 301 | } |
2727 | | |
2728 | | static int mp_property_vd_imgparams(void *ctx, struct m_property *prop, |
2729 | | int action, void *arg) |
2730 | 5.46k | { |
2731 | 5.46k | MPContext *mpctx = ctx; |
2732 | 5.46k | struct vo_chain *vo_c = mpctx->vo_chain; |
2733 | 5.46k | if (!vo_c) |
2734 | 305 | return M_PROPERTY_UNAVAILABLE; |
2735 | | |
2736 | 5.15k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2737 | 5.15k | if (valid != M_PROPERTY_VALID) |
2738 | 142 | return valid; |
2739 | | |
2740 | 5.01k | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
2741 | 5.01k | struct mp_codec_params *c = |
2742 | 5.01k | track && track->stream ? track->stream->codec : NULL; |
2743 | 5.01k | if (vo_c->filter->input_params.imgfmt) { |
2744 | 5.00k | return property_imgparams(&vo_c->filter->input_params, action, arg); |
2745 | 5.00k | } else if (c && c->disp_w && c->disp_h) { |
2746 | | // Simplistic fallback for stupid scripts querying "width"/"height" |
2747 | | // before the first frame is decoded. |
2748 | 7 | struct m_sub_property props[] = { |
2749 | 7 | {"w", SUB_PROP_INT(c->disp_w)}, |
2750 | 7 | {"h", SUB_PROP_INT(c->disp_h)}, |
2751 | 7 | {0} |
2752 | 7 | }; |
2753 | 7 | return m_property_read_sub(props, action, arg); |
2754 | 7 | } |
2755 | 0 | return M_PROPERTY_UNAVAILABLE; |
2756 | 5.01k | } |
2757 | | |
2758 | | static int mp_property_video_frame_info(void *ctx, struct m_property *prop, |
2759 | | int action, void *arg) |
2760 | 1.46k | { |
2761 | 1.46k | MPContext *mpctx = ctx; |
2762 | 1.46k | if (!mpctx->video_out) |
2763 | 12 | return M_PROPERTY_UNAVAILABLE; |
2764 | | |
2765 | 1.44k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
2766 | 1.44k | if (valid != M_PROPERTY_VALID) |
2767 | 838 | return valid; |
2768 | | |
2769 | 611 | struct mp_image *f = vo_get_current_frame(mpctx->video_out); |
2770 | 611 | if (!f) |
2771 | 380 | return M_PROPERTY_UNAVAILABLE; |
2772 | | |
2773 | 231 | const char *pict_types[] = {0, "I", "P", "B"}; |
2774 | 231 | const char *pict_type = f->pict_type >= 1 && f->pict_type <= 3 |
2775 | 231 | ? pict_types[f->pict_type] : NULL; |
2776 | | |
2777 | 231 | char gop_tc[AV_TIMECODE_STR_SIZE] = {0}; |
2778 | 231 | char s12m_tc[AV_TIMECODE_STR_SIZE] = {0}; |
2779 | 231 | for (int n = 0; n < f->num_ff_side_data; n++) { |
2780 | |
|
2781 | 0 | struct mp_ff_side_data *mpsd = &f->ff_side_data[n]; |
2782 | 0 | if (mpsd->type == AV_FRAME_DATA_GOP_TIMECODE) |
2783 | 0 | av_timecode_make_mpeg_tc_string(gop_tc, *(int64_t*)(mpsd->buf->data)); |
2784 | 0 | if (mpctx->vo_chain && mpsd->type == AV_FRAME_DATA_S12M_TIMECODE) { |
2785 | 0 | av_timecode_make_smpte_tc_string2(s12m_tc, |
2786 | 0 | av_d2q(mpctx->vo_chain->filter->container_fps, INT_MAX), |
2787 | 0 | *(uint32_t*)(mpsd->buf->data), 0, 0); |
2788 | 0 | } |
2789 | 0 | } |
2790 | | |
2791 | 231 | char approx_smpte[AV_TIMECODE_STR_SIZE] = {0}; |
2792 | 231 | if (s12m_tc[0] == '\0' && mpctx->vo_chain) { |
2793 | 231 | unsigned container_fps = lrint(mpctx->vo_chain->filter->container_fps); |
2794 | | // Avoid division-by-zero in av_timecode_make_string() if reported |
2795 | | // container_fps is or rounds to 0. |
2796 | 231 | if (container_fps) { |
2797 | 231 | const AVTimecode tcr = { |
2798 | 231 | .start = 0, |
2799 | 231 | .flags = AV_TIMECODE_FLAG_DROPFRAME, |
2800 | 231 | .rate = av_d2q(mpctx->vo_chain->filter->container_fps, INT_MAX), |
2801 | 231 | .fps = container_fps, |
2802 | 231 | }; |
2803 | 231 | int frame = lrint(get_current_pos_ratio(mpctx, false) * get_frame_count(mpctx)); |
2804 | 231 | av_timecode_make_string(&tcr, approx_smpte, frame); |
2805 | 231 | } |
2806 | 231 | } |
2807 | | |
2808 | 231 | struct m_sub_property props[] = { |
2809 | 231 | {"picture-type", SUB_PROP_STR(pict_type), .unavailable = !pict_type}, |
2810 | 231 | {"interlaced", SUB_PROP_BOOL(!!(f->fields & MP_IMGFIELD_INTERLACED))}, |
2811 | 231 | {"tff", SUB_PROP_BOOL(!!(f->fields & MP_IMGFIELD_TOP_FIRST))}, |
2812 | 231 | {"repeat", SUB_PROP_BOOL(!!(f->fields & MP_IMGFIELD_REPEAT_FIRST))}, |
2813 | 231 | {"gop-timecode", SUB_PROP_STR(gop_tc), .unavailable = gop_tc[0] == '\0'}, |
2814 | 231 | {"smpte-timecode", SUB_PROP_STR(s12m_tc), .unavailable = s12m_tc[0] == '\0'}, |
2815 | 231 | {"estimated-smpte-timecode", SUB_PROP_STR(approx_smpte), .unavailable = approx_smpte[0] == '\0'}, |
2816 | 231 | {0} |
2817 | 231 | }; |
2818 | | |
2819 | 231 | talloc_free(f); |
2820 | 231 | return m_property_read_sub(props, action, arg); |
2821 | 611 | } |
2822 | | |
2823 | | static int mp_property_current_window_scale(void *ctx, struct m_property *prop, |
2824 | | int action, void *arg) |
2825 | 205 | { |
2826 | 205 | MPContext *mpctx = ctx; |
2827 | 205 | struct vo *vo = mpctx->video_out; |
2828 | 205 | if (!vo) |
2829 | 10 | return M_PROPERTY_UNAVAILABLE; |
2830 | | |
2831 | 195 | struct mp_image_params params = get_video_out_params(mpctx); |
2832 | 195 | int vid_w, vid_h; |
2833 | 195 | mp_image_params_get_dsize(¶ms, &vid_w, &vid_h); |
2834 | 195 | if (vid_w < 1 || vid_h < 1) |
2835 | 21 | return M_PROPERTY_UNAVAILABLE; |
2836 | | |
2837 | 174 | if (params.rotate % 180 == 90 && (vo->driver->caps & VO_CAP_ROTATE90)) |
2838 | 0 | MPSWAP(int, vid_w, vid_h); |
2839 | | |
2840 | 174 | if (vo->monitor_par < 1) { |
2841 | 0 | vid_h = MPCLAMP(vid_h / vo->monitor_par, 1, 16000); |
2842 | 174 | } else { |
2843 | 174 | vid_w = MPCLAMP(vid_w * vo->monitor_par, 1, 16000); |
2844 | 174 | } |
2845 | | |
2846 | 174 | if (action == M_PROPERTY_SET) { |
2847 | | // Also called by update_window_scale as a NULL property. |
2848 | 0 | double scale = *(double *)arg; |
2849 | 0 | int s[2] = {vid_w * scale, vid_h * scale}; |
2850 | 0 | if (s[0] <= 0 || s[1] <= 0) |
2851 | 0 | return M_PROPERTY_INVALID_FORMAT; |
2852 | 0 | vo_control(vo, VOCTRL_SET_UNFS_WINDOW_SIZE, s); |
2853 | 0 | return M_PROPERTY_OK; |
2854 | 0 | } |
2855 | | |
2856 | 174 | int s[2]; |
2857 | 174 | if (vo_control(vo, VOCTRL_GET_UNFS_WINDOW_SIZE, s) <= 0 || |
2858 | 0 | s[0] < 1 || s[1] < 1) |
2859 | 174 | return M_PROPERTY_UNAVAILABLE; |
2860 | | |
2861 | 0 | double xs = (double)s[0] / vid_w; |
2862 | 0 | double ys = (double)s[1] / vid_h; |
2863 | 0 | return m_property_double_ro(action, arg, (xs + ys) / 2); |
2864 | 174 | } |
2865 | | |
2866 | | static void update_window_scale(struct MPContext *mpctx) |
2867 | 3 | { |
2868 | 3 | double scale = mpctx->opts->vo->window_scale; |
2869 | 3 | mp_property_current_window_scale(mpctx, (struct m_property *)NULL, |
2870 | 3 | M_PROPERTY_SET, (void*)&scale); |
2871 | 3 | } |
2872 | | |
2873 | | static int mp_property_display_fps(void *ctx, struct m_property *prop, |
2874 | | int action, void *arg) |
2875 | 727 | { |
2876 | 727 | MPContext *mpctx = ctx; |
2877 | 727 | double fps = mpctx->video_out ? vo_get_display_fps(mpctx->video_out) : 0; |
2878 | 727 | switch (action) { |
2879 | 173 | case M_PROPERTY_GET: |
2880 | 173 | if (fps <= 0) |
2881 | 173 | return M_PROPERTY_UNAVAILABLE; |
2882 | 0 | return m_property_double_ro(action, arg, fps); |
2883 | 179 | case M_PROPERTY_GET_TYPE: |
2884 | 179 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_DOUBLE}; |
2885 | 179 | return M_PROPERTY_OK; |
2886 | 727 | } |
2887 | 375 | return M_PROPERTY_NOT_IMPLEMENTED; |
2888 | 727 | } |
2889 | | |
2890 | | static int mp_property_estimated_display_fps(void *ctx, struct m_property *prop, |
2891 | | int action, void *arg) |
2892 | 148 | { |
2893 | 148 | MPContext *mpctx = ctx; |
2894 | 148 | struct vo *vo = mpctx->video_out; |
2895 | 148 | if (!vo) |
2896 | 32 | return M_PROPERTY_UNAVAILABLE; |
2897 | 116 | double interval = vo_get_estimated_vsync_interval(vo) / 1e9; |
2898 | 116 | if (interval <= 0) |
2899 | 116 | return M_PROPERTY_UNAVAILABLE; |
2900 | 0 | return m_property_double_ro(action, arg, 1.0 / interval); |
2901 | 116 | } |
2902 | | |
2903 | | static int mp_property_vsync_jitter(void *ctx, struct m_property *prop, |
2904 | | int action, void *arg) |
2905 | 85 | { |
2906 | 85 | MPContext *mpctx = ctx; |
2907 | 85 | struct vo *vo = mpctx->video_out; |
2908 | 85 | if (!vo) |
2909 | 34 | return M_PROPERTY_UNAVAILABLE; |
2910 | 51 | double stddev = vo_get_estimated_vsync_jitter(vo); |
2911 | 51 | if (stddev < 0) |
2912 | 51 | return M_PROPERTY_UNAVAILABLE; |
2913 | 0 | return m_property_double_ro(action, arg, stddev); |
2914 | 51 | } |
2915 | | |
2916 | | static int mp_property_display_resolution(void *ctx, struct m_property *prop, |
2917 | | int action, void *arg) |
2918 | 44 | { |
2919 | 44 | MPContext *mpctx = ctx; |
2920 | 44 | struct vo *vo = mpctx->video_out; |
2921 | 44 | if (!vo) |
2922 | 44 | return M_PROPERTY_UNAVAILABLE; |
2923 | 0 | int res[2]; |
2924 | 0 | if (vo_control(vo, VOCTRL_GET_DISPLAY_RES, &res) <= 0) |
2925 | 0 | return M_PROPERTY_UNAVAILABLE; |
2926 | 0 | if (strcmp(prop->name, "display-width") == 0) { |
2927 | 0 | return m_property_int_ro(action, arg, res[0]); |
2928 | 0 | } else { |
2929 | 0 | return m_property_int_ro(action, arg, res[1]); |
2930 | 0 | } |
2931 | 0 | } |
2932 | | |
2933 | | static int mp_property_hidpi_scale(void *ctx, struct m_property *prop, |
2934 | | int action, void *arg) |
2935 | 67 | { |
2936 | 67 | MPContext *mpctx = ctx; |
2937 | 67 | struct command_ctx *cmd = mpctx->command_ctx; |
2938 | 67 | struct vo *vo = mpctx->video_out; |
2939 | 67 | if (!vo) |
2940 | 12 | return M_PROPERTY_UNAVAILABLE; |
2941 | 55 | if (!cmd->cached_window_scale) { |
2942 | 7 | double scale = 0; |
2943 | 7 | if (vo_control(vo, VOCTRL_GET_HIDPI_SCALE, &scale) < 1 || !scale) |
2944 | 7 | scale = -1; |
2945 | 7 | cmd->cached_window_scale = scale; |
2946 | 7 | } |
2947 | 55 | if (cmd->cached_window_scale < 0) |
2948 | 55 | return M_PROPERTY_UNAVAILABLE; |
2949 | 0 | return m_property_double_ro(action, arg, cmd->cached_window_scale); |
2950 | 55 | } |
2951 | | |
2952 | | static void update_hidpi_window_scale(struct MPContext *mpctx, bool hidpi_scale) |
2953 | 2 | { |
2954 | 2 | struct command_ctx *cmd = mpctx->command_ctx; |
2955 | 2 | struct vo *vo = mpctx->video_out; |
2956 | 2 | if (!vo || cmd->cached_window_scale <= 0) |
2957 | 2 | return; |
2958 | | |
2959 | 0 | double scale = hidpi_scale ? cmd->cached_window_scale : 1 / cmd->cached_window_scale; |
2960 | |
|
2961 | 0 | int s[2]; |
2962 | 0 | if (vo_control(vo, VOCTRL_GET_UNFS_WINDOW_SIZE, s) <= 0 || s[0] < 1 || s[1] < 1) |
2963 | 0 | return; |
2964 | | |
2965 | 0 | s[0] *= scale; |
2966 | 0 | s[1] *= scale; |
2967 | 0 | if (s[0] <= 0 || s[1] <= 0) |
2968 | 0 | return; |
2969 | 0 | vo_control(vo, VOCTRL_SET_UNFS_WINDOW_SIZE, s); |
2970 | 0 | } |
2971 | | |
2972 | | static int mp_property_ambient_light(void *ctx, struct m_property *prop, |
2973 | | int action, void *arg) |
2974 | 162 | { |
2975 | 162 | MPContext *mpctx = ctx; |
2976 | 162 | if (!mpctx->als_state) { |
2977 | 31 | mpctx->als_state = mp_als_create(mpctx, mpctx); |
2978 | 31 | } |
2979 | | |
2980 | 162 | double lux; |
2981 | 162 | switch (mp_als_get_lux(mpctx->als_state, &lux)) { |
2982 | 0 | case MP_ALS_STATUS_OK: |
2983 | 0 | return m_property_double_ro(action, arg, lux); |
2984 | 162 | case MP_ALS_STATUS_NODEVICE: |
2985 | 162 | return M_PROPERTY_UNAVAILABLE; |
2986 | 0 | case MP_ALS_STATUS_READFAILED: |
2987 | 0 | default: |
2988 | 0 | return M_PROPERTY_ERROR; |
2989 | 162 | } |
2990 | 162 | } |
2991 | | |
2992 | | static int mp_property_focused(void *ctx, struct m_property *prop, |
2993 | | int action, void *arg) |
2994 | 221 | { |
2995 | 221 | MPContext *mpctx = ctx; |
2996 | 221 | struct vo *vo = mpctx->video_out; |
2997 | 221 | if (!vo) |
2998 | 32 | return M_PROPERTY_UNAVAILABLE; |
2999 | | |
3000 | 221 | bool focused; |
3001 | 189 | if (vo_control(vo, VOCTRL_GET_FOCUSED, &focused) < 1) |
3002 | 189 | return M_PROPERTY_UNAVAILABLE; |
3003 | | |
3004 | 0 | return m_property_bool_ro(action, arg, focused); |
3005 | 189 | } |
3006 | | |
3007 | | static int mp_property_display_names(void *ctx, struct m_property *prop, |
3008 | | int action, void *arg) |
3009 | 356 | { |
3010 | 356 | MPContext *mpctx = ctx; |
3011 | 356 | struct vo *vo = mpctx->video_out; |
3012 | 356 | if (!vo || vo->display_swapchain) |
3013 | 9 | return M_PROPERTY_UNAVAILABLE; |
3014 | | |
3015 | 347 | switch (action) { |
3016 | 87 | case M_PROPERTY_GET_TYPE: |
3017 | 87 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
3018 | 87 | return M_PROPERTY_OK; |
3019 | 87 | case M_PROPERTY_GET: { |
3020 | 87 | char** display_names; |
3021 | 87 | if (vo_control(vo, VOCTRL_GET_DISPLAY_NAMES, &display_names) < 1) |
3022 | 87 | return M_PROPERTY_UNAVAILABLE; |
3023 | | |
3024 | 0 | *(char ***)arg = display_names; |
3025 | 0 | return M_PROPERTY_OK; |
3026 | 87 | } |
3027 | 347 | } |
3028 | 173 | return M_PROPERTY_NOT_IMPLEMENTED; |
3029 | 347 | } |
3030 | | |
3031 | | static int mp_property_vo_configured(void *ctx, struct m_property *prop, |
3032 | | int action, void *arg) |
3033 | 564 | { |
3034 | 564 | MPContext *mpctx = ctx; |
3035 | 564 | return m_property_bool_ro(action, arg, |
3036 | 564 | mpctx->video_out && mpctx->video_out->config_ok); |
3037 | 564 | } |
3038 | | |
3039 | | static void get_frame_perf(struct mpv_node *node, struct mp_frame_perf *perf) |
3040 | 0 | { |
3041 | 0 | for (int i = 0; i < perf->count; i++) { |
3042 | 0 | struct mp_pass_perf *data = &perf->perf[i]; |
3043 | 0 | struct mpv_node *pass = node_array_add(node, MPV_FORMAT_NODE_MAP); |
3044 | |
|
3045 | 0 | node_map_add_string(pass, "desc", perf->desc[i]); |
3046 | 0 | node_map_add(pass, "last", MPV_FORMAT_INT64)->u.int64 = data->last; |
3047 | 0 | node_map_add(pass, "avg", MPV_FORMAT_INT64)->u.int64 = data->avg; |
3048 | 0 | node_map_add(pass, "peak", MPV_FORMAT_INT64)->u.int64 = data->peak; |
3049 | 0 | node_map_add(pass, "count", MPV_FORMAT_INT64)->u.int64 = data->count; |
3050 | 0 | struct mpv_node *samples = node_map_add(pass, "samples", MPV_FORMAT_NODE_ARRAY); |
3051 | 0 | for (int n = 0; n < data->count; n++) |
3052 | 0 | node_array_add(samples, MPV_FORMAT_INT64)->u.int64 = data->samples[n]; |
3053 | 0 | } |
3054 | 0 | } |
3055 | | |
3056 | | static char *asprint_perf(char *res, struct mp_frame_perf *perf) |
3057 | 0 | { |
3058 | 0 | for (int i = 0; i < perf->count; i++) { |
3059 | 0 | struct mp_pass_perf *pass = &perf->perf[i]; |
3060 | 0 | res = talloc_asprintf_append(res, |
3061 | 0 | "- %s: last %dus avg %dus peak %dus\n", perf->desc[i], |
3062 | 0 | (int)pass->last/1000, (int)pass->avg/1000, (int)pass->peak/1000); |
3063 | 0 | } |
3064 | |
|
3065 | 0 | return res; |
3066 | 0 | } |
3067 | | |
3068 | | static int mp_property_vo_passes(void *ctx, struct m_property *prop, |
3069 | | int action, void *arg) |
3070 | 1.72k | { |
3071 | 1.72k | MPContext *mpctx = ctx; |
3072 | 1.72k | if (!mpctx->video_out) |
3073 | 32 | return M_PROPERTY_UNAVAILABLE; |
3074 | | |
3075 | | // Return early, to avoid having to go through a completely unnecessary VOCTRL |
3076 | 1.69k | switch (action) { |
3077 | 394 | case M_PROPERTY_PRINT: |
3078 | 827 | case M_PROPERTY_GET: |
3079 | 827 | break; |
3080 | 433 | case M_PROPERTY_GET_TYPE: |
3081 | 433 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
3082 | 433 | return M_PROPERTY_OK; |
3083 | 435 | default: |
3084 | 435 | return M_PROPERTY_NOT_IMPLEMENTED; |
3085 | 1.69k | } |
3086 | | |
3087 | 827 | struct voctrl_performance_data *data = talloc_ptrtype(NULL, data); |
3088 | 827 | if (vo_control(mpctx->video_out, VOCTRL_PERFORMANCE_DATA, data) <= 0) { |
3089 | 827 | talloc_free(data); |
3090 | 827 | return M_PROPERTY_UNAVAILABLE; |
3091 | 827 | } |
3092 | | |
3093 | 0 | switch (action) { |
3094 | 0 | case M_PROPERTY_PRINT: { |
3095 | 0 | char *res = NULL; |
3096 | 0 | res = talloc_asprintf_append(res, "fresh:\n"); |
3097 | 0 | res = asprint_perf(res, &data->fresh); |
3098 | 0 | res = talloc_asprintf_append(res, "\nredraw:\n"); |
3099 | 0 | res = asprint_perf(res, &data->redraw); |
3100 | 0 | *(char **)arg = res; |
3101 | 0 | break; |
3102 | 0 | } |
3103 | | |
3104 | 0 | case M_PROPERTY_GET: { |
3105 | 0 | struct mpv_node node; |
3106 | 0 | node_init(&node, MPV_FORMAT_NODE_MAP, NULL); |
3107 | 0 | struct mpv_node *fresh = node_map_add(&node, "fresh", MPV_FORMAT_NODE_ARRAY); |
3108 | 0 | struct mpv_node *redraw = node_map_add(&node, "redraw", MPV_FORMAT_NODE_ARRAY); |
3109 | 0 | get_frame_perf(fresh, &data->fresh); |
3110 | 0 | get_frame_perf(redraw, &data->redraw); |
3111 | 0 | *(struct mpv_node *)arg = node; |
3112 | 0 | break; |
3113 | 0 | } |
3114 | 0 | } |
3115 | | |
3116 | 0 | talloc_free(data); |
3117 | 0 | return M_PROPERTY_OK; |
3118 | 0 | } |
3119 | | |
3120 | | static int mp_property_perf_info(void *ctx, struct m_property *p, int action, |
3121 | | void *arg) |
3122 | 4.89k | { |
3123 | 4.89k | MPContext *mpctx = ctx; |
3124 | | |
3125 | 4.89k | switch (action) { |
3126 | 1.23k | case M_PROPERTY_GET_TYPE: |
3127 | 1.23k | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
3128 | 1.23k | return M_PROPERTY_OK; |
3129 | 1.22k | case M_PROPERTY_GET: { |
3130 | 1.22k | stats_global_query(mpctx->global, (struct mpv_node *)arg); |
3131 | 1.22k | return M_PROPERTY_OK; |
3132 | 0 | } |
3133 | 4.89k | } |
3134 | 2.44k | return M_PROPERTY_NOT_IMPLEMENTED; |
3135 | 4.89k | } |
3136 | | |
3137 | | static int mp_property_vo(void *ctx, struct m_property *p, int action, void *arg) |
3138 | 146 | { |
3139 | 146 | MPContext *mpctx = ctx; |
3140 | 146 | return m_property_strdup_ro(action, arg, mpctx->video_out ? |
3141 | 146 | mpctx->video_out->driver->name : NULL); |
3142 | 146 | } |
3143 | | |
3144 | | static int mp_property_gpu_context(void *ctx, struct m_property *p, int action, void *arg) |
3145 | 297 | { |
3146 | 297 | MPContext *mpctx = ctx; |
3147 | 297 | return m_property_strdup_ro(action, arg, mpctx->video_out ? |
3148 | 297 | mpctx->video_out->context_name : NULL); |
3149 | 297 | } |
3150 | | |
3151 | | static int mp_property_osd_dim(void *ctx, struct m_property *prop, |
3152 | | int action, void *arg) |
3153 | 2.89k | { |
3154 | 2.89k | MPContext *mpctx = ctx; |
3155 | | |
3156 | 2.89k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3157 | 2.89k | if (valid != M_PROPERTY_VALID) |
3158 | 270 | return valid; |
3159 | | |
3160 | 2.62k | struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); |
3161 | | |
3162 | 2.62k | if (!mpctx->video_out || !mpctx->video_out->config_ok) |
3163 | 1.73k | vo_res = (struct mp_osd_res){0}; |
3164 | | |
3165 | 2.62k | double aspect = 1.0 * vo_res.w / MPMAX(vo_res.h, 1) / |
3166 | 2.62k | (vo_res.display_par ? vo_res.display_par : 1); |
3167 | | |
3168 | 2.62k | struct m_sub_property props[] = { |
3169 | 2.62k | {"w", SUB_PROP_INT(vo_res.w)}, |
3170 | 2.62k | {"h", SUB_PROP_INT(vo_res.h)}, |
3171 | 2.62k | {"par", SUB_PROP_DOUBLE(vo_res.display_par)}, |
3172 | 2.62k | {"aspect", SUB_PROP_DOUBLE(aspect)}, |
3173 | 2.62k | {"mt", SUB_PROP_INT(vo_res.mt)}, |
3174 | 2.62k | {"mb", SUB_PROP_INT(vo_res.mb)}, |
3175 | 2.62k | {"ml", SUB_PROP_INT(vo_res.ml)}, |
3176 | 2.62k | {"mr", SUB_PROP_INT(vo_res.mr)}, |
3177 | 2.62k | {0} |
3178 | 2.62k | }; |
3179 | | |
3180 | 2.62k | return m_property_read_sub(props, action, arg); |
3181 | 2.89k | } |
3182 | | |
3183 | | static int mp_property_osd_sym(void *ctx, struct m_property *prop, |
3184 | | int action, void *arg) |
3185 | 621 | { |
3186 | 621 | MPContext *mpctx = ctx; |
3187 | 621 | char temp[20]; |
3188 | 621 | get_current_osd_sym(mpctx, temp, sizeof(temp)); |
3189 | 621 | return m_property_strdup_ro(action, arg, temp); |
3190 | 621 | } |
3191 | | |
3192 | | static int mp_property_osd_ass(void *ctx, struct m_property *prop, |
3193 | | int action, void *arg) |
3194 | 462 | { |
3195 | 462 | struct m_sub_property props[] = { |
3196 | 462 | {"0", SUB_PROP_STR(OSD_ASS_0)}, |
3197 | 462 | {"1", SUB_PROP_STR(OSD_ASS_1)}, |
3198 | 462 | {0} |
3199 | 462 | }; |
3200 | 462 | return m_property_read_sub(props, action, arg); |
3201 | 462 | } |
3202 | | |
3203 | | static int mp_property_term_clip(void *ctx, struct m_property *prop, |
3204 | | int action, void *arg) |
3205 | 140 | { |
3206 | 140 | return m_property_strdup_ro(action, arg, TERM_MSG_0); |
3207 | 140 | } |
3208 | | |
3209 | | static int mp_property_term_size(void *ctx, struct m_property *prop, |
3210 | | int action, void *arg) |
3211 | 45 | { |
3212 | 45 | int w = -1, h = -1; |
3213 | 45 | terminal_get_size(&w, &h); |
3214 | 45 | if (w == -1 || h == -1) |
3215 | 45 | return M_PROPERTY_UNAVAILABLE; |
3216 | | |
3217 | 0 | struct m_sub_property props[] = { |
3218 | 0 | {"w", SUB_PROP_INT(w)}, |
3219 | 0 | {"h", SUB_PROP_INT(h)}, |
3220 | 0 | {0} |
3221 | 0 | }; |
3222 | |
|
3223 | 0 | return m_property_read_sub(props, action, arg); |
3224 | 45 | } |
3225 | | |
3226 | | static int mp_property_player_operation_mode(void *ctx, struct m_property *prop, |
3227 | | int action, void *arg) |
3228 | 14 | { |
3229 | 14 | MPContext *mpctx = ctx; |
3230 | 14 | const struct m_opt_choice_alternatives mode_names[] = {{"cplayer", 0}, {"pseudo-gui", 1}}; |
3231 | 14 | return m_property_strdup_ro(action, arg, m_opt_choice_str(mode_names, mpctx->opts->operation_mode)); |
3232 | 14 | } |
3233 | | |
3234 | | static int mp_property_mouse_pos(void *ctx, struct m_property *prop, |
3235 | | int action, void *arg) |
3236 | 899 | { |
3237 | 899 | MPContext *mpctx = ctx; |
3238 | | |
3239 | 899 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3240 | 899 | if (valid != M_PROPERTY_VALID) |
3241 | 581 | return valid; |
3242 | | |
3243 | 318 | int x, y, hover; |
3244 | 318 | mp_input_get_mouse_pos(mpctx->input, &x, &y, &hover); |
3245 | 318 | struct m_sub_property props[] = { |
3246 | 318 | {"x", SUB_PROP_INT64(x)}, |
3247 | 318 | {"y", SUB_PROP_INT64(y)}, |
3248 | 318 | {"hover", SUB_PROP_BOOL(!!hover)}, |
3249 | 318 | {0} |
3250 | 318 | }; |
3251 | | |
3252 | 318 | return m_property_read_sub(props, action, arg); |
3253 | 899 | } |
3254 | | |
3255 | | static int get_touch_pos(int item, int action, void *arg, void *ctx) |
3256 | 0 | { |
3257 | 0 | const int **pos = (const int **)ctx; |
3258 | 0 | struct m_sub_property props[] = { |
3259 | 0 | {"x", SUB_PROP_INT(pos[0][item])}, |
3260 | 0 | {"y", SUB_PROP_INT(pos[1][item])}, |
3261 | 0 | {"id", SUB_PROP_INT(pos[2][item])}, |
3262 | 0 | {0} |
3263 | 0 | }; |
3264 | |
|
3265 | 0 | int r = m_property_read_sub(props, action, arg); |
3266 | 0 | return r; |
3267 | 0 | } |
3268 | | |
3269 | 485 | #define MAX_TOUCH_POINTS 10 |
3270 | | static int mp_property_touch_pos(void *ctx, struct m_property *prop, |
3271 | | int action, void *arg) |
3272 | 1.06k | { |
3273 | 1.06k | MPContext *mpctx = ctx; |
3274 | | |
3275 | 1.06k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3276 | 1.06k | if (valid != M_PROPERTY_VALID) |
3277 | 583 | return valid; |
3278 | | |
3279 | 485 | int xs[MAX_TOUCH_POINTS], ys[MAX_TOUCH_POINTS], ids[MAX_TOUCH_POINTS]; |
3280 | 485 | int count = mp_input_get_touch_pos(mpctx->input, MAX_TOUCH_POINTS, xs, ys, ids); |
3281 | 485 | const int *pos[3] = {xs, ys, ids}; |
3282 | 485 | return m_property_read_list(action, arg, MPMIN(MAX_TOUCH_POINTS, count), |
3283 | 485 | get_touch_pos, (void *)pos); |
3284 | 1.06k | } |
3285 | | |
3286 | | static int mp_property_tablet_pos(void *ctx, struct m_property *prop, |
3287 | | int action, void *arg) |
3288 | 713 | { |
3289 | 713 | MPContext *mpctx = ctx; |
3290 | | |
3291 | 713 | switch (action) { |
3292 | 170 | case M_PROPERTY_GET_TYPE: |
3293 | 170 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
3294 | 170 | return M_PROPERTY_OK; |
3295 | | |
3296 | 167 | case M_PROPERTY_GET: { |
3297 | 167 | int xs, ys; |
3298 | 167 | bool tool_in_proximity; |
3299 | 167 | bool tool_down; |
3300 | 167 | bool tool_stylus_btn1_pressed; |
3301 | 167 | bool tool_stylus_btn2_pressed; |
3302 | 167 | bool tool_stylus_btn3_pressed; |
3303 | 167 | bool pad_focus; |
3304 | 167 | bool pad_buttons_pressed[MP_MAX_TABLET_PAD_BUTTONS]; |
3305 | 167 | int pad_buttons; |
3306 | 167 | mp_input_get_tablet_pos(mpctx->input, &xs, &ys, &tool_in_proximity, &tool_down, |
3307 | 167 | &tool_stylus_btn1_pressed, &tool_stylus_btn2_pressed, &tool_stylus_btn3_pressed, |
3308 | 167 | &pad_focus, pad_buttons_pressed, &pad_buttons); |
3309 | | |
3310 | 167 | struct mpv_node node; |
3311 | 167 | node_init(&node, MPV_FORMAT_NODE_MAP, NULL); |
3312 | 167 | node_map_add_int64(&node, "x", xs); |
3313 | 167 | node_map_add_int64(&node, "y", ys); |
3314 | 167 | node_map_add_flag(&node, "tool-in-proximity", tool_in_proximity); |
3315 | 167 | node_map_add_string(&node, "tool-tip", tool_down ? "down" : "up"); |
3316 | 167 | node_map_add_string(&node, "tool-stylus-btn1", tool_stylus_btn1_pressed ? "pressed" : "released"); |
3317 | 167 | node_map_add_string(&node, "tool-stylus-btn2", tool_stylus_btn2_pressed ? "pressed" : "released"); |
3318 | 167 | node_map_add_string(&node, "tool-stylus-btn3", tool_stylus_btn3_pressed ? "pressed" : "released"); |
3319 | 167 | node_map_add_flag(&node, "pad-focus", pad_focus); |
3320 | | |
3321 | 167 | struct mpv_node *args = node_map_add(&node, "pad-btns", MPV_FORMAT_NODE_MAP); |
3322 | 167 | for (int i = 0; i < pad_buttons; i++) { |
3323 | 0 | char *name = mp_tprintf(2, "%d", i + 1); |
3324 | 0 | node_map_add_string(args, name, pad_buttons_pressed[i] ? "pressed" : "released"); |
3325 | 0 | } |
3326 | | |
3327 | 167 | *(struct mpv_node *)arg = node; |
3328 | 167 | return M_PROPERTY_OK; |
3329 | 0 | } |
3330 | 713 | } |
3331 | | |
3332 | 376 | return M_PROPERTY_NOT_IMPLEMENTED; |
3333 | 713 | } |
3334 | | |
3335 | | static int mp_property_dropped_files(void *ctx, struct m_property *prop, |
3336 | | int action, void *arg) |
3337 | 547 | { |
3338 | 547 | MPContext *mpctx = ctx; |
3339 | | |
3340 | 547 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3341 | 547 | if (valid != M_PROPERTY_VALID) |
3342 | 274 | return valid; |
3343 | | |
3344 | 273 | int64_t dnd_ts; |
3345 | 273 | enum mp_dnd_action dnd_action; |
3346 | 273 | char **dropped_files; |
3347 | 273 | void *talloc_ctx = talloc_new(NULL); |
3348 | 273 | mp_input_get_dropped_files(mpctx->input, talloc_ctx, &dnd_ts, |
3349 | 273 | &dnd_action, &dropped_files); |
3350 | 273 | int r = M_PROPERTY_UNAVAILABLE; |
3351 | 273 | if (dnd_action == DND_NONE) |
3352 | 273 | goto done; |
3353 | | |
3354 | 0 | char *actionstr = dnd_action == DND_REPLACE ? "replace" : |
3355 | 0 | dnd_action == DND_APPEND ? "append" : |
3356 | 0 | dnd_action == DND_INSERT_NEXT ? "insert-next" : |
3357 | 0 | "none"; |
3358 | |
|
3359 | 0 | struct m_sub_property props[] = { |
3360 | 0 | {"time", SUB_PROP_INT64(dnd_ts)}, |
3361 | 0 | {"action", SUB_PROP_STR(actionstr)}, |
3362 | 0 | {"files", SUB_PROP_STRING_LIST(dropped_files)}, |
3363 | 0 | {0} |
3364 | 0 | }; |
3365 | |
|
3366 | 0 | r = m_property_read_sub(props, action, arg); |
3367 | |
|
3368 | 273 | done: |
3369 | 273 | talloc_free(talloc_ctx); |
3370 | 273 | return r; |
3371 | 0 | } |
3372 | | |
3373 | | /// Video fps (RO) |
3374 | | static int mp_property_fps(void *ctx, struct m_property *prop, |
3375 | | int action, void *arg) |
3376 | 242 | { |
3377 | 242 | MPContext *mpctx = ctx; |
3378 | 242 | float fps = mpctx->vo_chain ? mpctx->vo_chain->filter->container_fps : 0; |
3379 | 242 | if (fps < 0.1 || !isfinite(fps)) |
3380 | 59 | return M_PROPERTY_UNAVAILABLE; |
3381 | 183 | return m_property_float_ro(action, arg, fps); |
3382 | 242 | } |
3383 | | |
3384 | | static int mp_property_vf_fps(void *ctx, struct m_property *prop, |
3385 | | int action, void *arg) |
3386 | 543 | { |
3387 | 543 | MPContext *mpctx = ctx; |
3388 | 543 | if (!mpctx->vo_chain) |
3389 | 52 | return M_PROPERTY_UNAVAILABLE; |
3390 | 491 | double avg = calc_average_frame_duration(mpctx); |
3391 | 491 | if (avg <= 0) |
3392 | 10 | return M_PROPERTY_UNAVAILABLE; |
3393 | 481 | return m_property_double_ro(action, arg, 1.0 / avg); |
3394 | 491 | } |
3395 | | |
3396 | 292 | #define doubles_equal(x, y) (fabs((x) - (y)) <= 0.001) |
3397 | | |
3398 | | static int mp_property_video_aspect_override(void *ctx, struct m_property *prop, |
3399 | | int action, void *arg) |
3400 | 1.81k | { |
3401 | 1.81k | MPContext *mpctx = ctx; |
3402 | 1.81k | if (action == M_PROPERTY_PRINT) { |
3403 | 73 | double aspect_ratio; |
3404 | 73 | mp_property_generic_option(mpctx, prop, M_PROPERTY_GET, &aspect_ratio); |
3405 | | |
3406 | 73 | if (doubles_equal(aspect_ratio, 2.35 / 1.0)) |
3407 | 0 | *(char **)arg = talloc_asprintf(NULL, "2.35:1"); |
3408 | 73 | else if (doubles_equal(aspect_ratio, 16.0 / 9.0)) |
3409 | 0 | *(char **)arg = talloc_asprintf(NULL, "16:9"); |
3410 | 73 | else if (doubles_equal(aspect_ratio, 16.0 / 10.0)) |
3411 | 0 | *(char **)arg = talloc_asprintf(NULL, "16:10"); |
3412 | 73 | else if (doubles_equal(aspect_ratio, 4.0 / 3.0)) |
3413 | 0 | *(char **)arg = talloc_asprintf(NULL, "4:3"); |
3414 | 73 | else if (aspect_ratio < 0.0) |
3415 | 55 | *(char **)arg = talloc_asprintf(NULL, "Original"); |
3416 | 18 | else |
3417 | 18 | *(char **)arg = talloc_asprintf(NULL, "%.3f", aspect_ratio); |
3418 | | |
3419 | 73 | return M_PROPERTY_OK; |
3420 | 73 | } |
3421 | 1.74k | return mp_property_generic_option(mpctx, prop, action, arg); |
3422 | 1.81k | } |
3423 | | |
3424 | | /// Subtitle delay (RW) |
3425 | | static int mp_property_sub_delay(void *ctx, struct m_property *prop, |
3426 | | int action, void *arg) |
3427 | 848 | { |
3428 | 848 | MPContext *mpctx = ctx; |
3429 | 848 | struct MPOpts *opts = mpctx->opts; |
3430 | 848 | int track_ind = *(int *)prop->priv; |
3431 | 848 | switch (action) { |
3432 | 260 | case M_PROPERTY_PRINT: |
3433 | 260 | *(char **)arg = format_delay(opts->subs_shared->sub_delay[track_ind]); |
3434 | 260 | return M_PROPERTY_OK; |
3435 | 848 | } |
3436 | 588 | return mp_property_generic_option(mpctx, prop, action, arg); |
3437 | 848 | } |
3438 | | |
3439 | | /// Subtitle speed (RW) |
3440 | | static int mp_property_sub_speed(void *ctx, struct m_property *prop, |
3441 | | int action, void *arg) |
3442 | 801 | { |
3443 | 801 | MPContext *mpctx = ctx; |
3444 | 801 | struct MPOpts *opts = mpctx->opts; |
3445 | 801 | if (action == M_PROPERTY_PRINT) { |
3446 | 220 | *(char **)arg = |
3447 | 220 | talloc_asprintf(NULL, "%4.1f%%", 100 * opts->subs_rend->sub_speed); |
3448 | 220 | return M_PROPERTY_OK; |
3449 | 220 | } |
3450 | 581 | return mp_property_generic_option(mpctx, prop, action, arg); |
3451 | 801 | } |
3452 | | |
3453 | | static int mp_property_sub_pos(void *ctx, struct m_property *prop, |
3454 | | int action, void *arg) |
3455 | 1.24k | { |
3456 | 1.24k | MPContext *mpctx = ctx; |
3457 | 1.24k | struct MPOpts *opts = mpctx->opts; |
3458 | 1.24k | int track_ind = *(int *)prop->priv; |
3459 | 1.24k | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_FIXED_LEN_PRINT) { |
3460 | 351 | *(char **)arg = mp_format_double(NULL, opts->subs_shared->sub_pos[track_ind], 2, |
3461 | 351 | false, true, action != M_PROPERTY_FIXED_LEN_PRINT); |
3462 | 351 | return M_PROPERTY_OK; |
3463 | 351 | } |
3464 | 890 | return mp_property_generic_option(mpctx, prop, action, arg); |
3465 | 1.24k | } |
3466 | | |
3467 | | static int mp_property_sub_ass_extradata(void *ctx, struct m_property *prop, |
3468 | | int action, void *arg) |
3469 | 50 | { |
3470 | 50 | MPContext *mpctx = ctx; |
3471 | 50 | struct track *track = mpctx->current_track[0][STREAM_SUB]; |
3472 | 50 | struct dec_sub *sub = track ? track->d_sub : NULL; |
3473 | 50 | if (!sub) |
3474 | 50 | return M_PROPERTY_UNAVAILABLE; |
3475 | 0 | switch (action) { |
3476 | 0 | case M_PROPERTY_GET: { |
3477 | 0 | char *data = sub_ass_get_extradata(sub); |
3478 | 0 | if (!data) |
3479 | 0 | return M_PROPERTY_UNAVAILABLE; |
3480 | 0 | *(char **)arg = data; |
3481 | 0 | return M_PROPERTY_OK; |
3482 | 0 | } |
3483 | 0 | case M_PROPERTY_GET_TYPE: |
3484 | 0 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING}; |
3485 | 0 | return M_PROPERTY_OK; |
3486 | 0 | } |
3487 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
3488 | 0 | } |
3489 | | |
3490 | | static int mp_property_sub_text(void *ctx, struct m_property *prop, |
3491 | | int action, void *arg) |
3492 | 811 | { |
3493 | 811 | MPContext *mpctx = ctx; |
3494 | 811 | const int *def = prop->priv; |
3495 | 811 | int sub_index = def[0]; |
3496 | 811 | int type = def[1]; |
3497 | | |
3498 | 811 | if (action == M_PROPERTY_KEY_ACTION) { |
3499 | 663 | struct m_property_action_arg *ka = arg; |
3500 | | |
3501 | 663 | if (!strcmp(ka->key, "ass")) |
3502 | 187 | type = SD_TEXT_TYPE_ASS; |
3503 | 476 | else if (!strcmp(ka->key, "ass-full")) |
3504 | 34 | type = SD_TEXT_TYPE_ASS_FULL; |
3505 | 442 | else |
3506 | 442 | return M_PROPERTY_UNKNOWN; |
3507 | | |
3508 | 221 | action = ka->action; |
3509 | 221 | arg = ka->arg; |
3510 | 221 | } |
3511 | | |
3512 | 369 | struct track *track = mpctx->current_track[sub_index][STREAM_SUB]; |
3513 | 369 | struct dec_sub *sub = track ? track->d_sub : NULL; |
3514 | 369 | double pts = mpctx->playback_pts; |
3515 | 369 | if (!sub || pts == MP_NOPTS_VALUE) |
3516 | 369 | return M_PROPERTY_UNAVAILABLE; |
3517 | | |
3518 | 0 | switch (action) { |
3519 | 0 | case M_PROPERTY_GET: { |
3520 | 0 | char *text = sub_get_text(sub, pts, type); |
3521 | 0 | if (!text) |
3522 | 0 | text = talloc_strdup(NULL, ""); |
3523 | 0 | *(char **)arg = text; |
3524 | 0 | return M_PROPERTY_OK; |
3525 | 0 | } |
3526 | 0 | case M_PROPERTY_GET_TYPE: |
3527 | 0 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING}; |
3528 | 0 | return M_PROPERTY_OK; |
3529 | 0 | } |
3530 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
3531 | 0 | } |
3532 | | |
3533 | | static struct sd_times get_times(void *ctx, struct m_property *prop, |
3534 | | int action, void *arg) |
3535 | 166 | { |
3536 | 166 | struct sd_times res = { .start = MP_NOPTS_VALUE, .end = MP_NOPTS_VALUE }; |
3537 | 166 | MPContext *mpctx = ctx; |
3538 | 166 | int track_ind = *(int *)prop->priv; |
3539 | 166 | struct track *track = mpctx->current_track[track_ind][STREAM_SUB]; |
3540 | 166 | struct dec_sub *sub = track ? track->d_sub : NULL; |
3541 | 166 | double pts = mpctx->playback_pts; |
3542 | 166 | if (!sub || pts == MP_NOPTS_VALUE) |
3543 | 166 | return res; |
3544 | 0 | return sub_get_times(sub, pts); |
3545 | 166 | } |
3546 | | |
3547 | | static int mp_property_sub_start(void *ctx, struct m_property *prop, |
3548 | | int action, void *arg) |
3549 | 80 | { |
3550 | 80 | double start = get_times(ctx, prop, action, arg).start; |
3551 | 80 | if (start == MP_NOPTS_VALUE) |
3552 | 80 | return M_PROPERTY_UNAVAILABLE; |
3553 | 0 | return property_time(action, arg, start); |
3554 | 80 | } |
3555 | | |
3556 | | |
3557 | | static int mp_property_sub_end(void *ctx, struct m_property *prop, |
3558 | | int action, void *arg) |
3559 | 86 | { |
3560 | 86 | double end = get_times(ctx, prop, action, arg).end; |
3561 | 86 | if (end == MP_NOPTS_VALUE) |
3562 | 86 | return M_PROPERTY_UNAVAILABLE; |
3563 | 0 | return property_time(action, arg, end); |
3564 | 86 | } |
3565 | | |
3566 | | static int mp_property_sub_lines(void *ctx, struct m_property *prop, |
3567 | | int action, void *arg) |
3568 | 113 | { |
3569 | 113 | MPContext *mpctx = ctx; |
3570 | 113 | int sub_index = *(int *)prop->priv; |
3571 | 113 | struct track *track = mpctx->current_track[sub_index][STREAM_SUB]; |
3572 | 113 | struct dec_sub *sub = track ? track->d_sub : NULL; |
3573 | 113 | if (!sub) |
3574 | 113 | return M_PROPERTY_UNAVAILABLE; |
3575 | | |
3576 | 0 | switch (action) { |
3577 | 0 | case M_PROPERTY_GET_TYPE: |
3578 | 0 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
3579 | 0 | return M_PROPERTY_OK; |
3580 | 0 | case M_PROPERTY_GET: |
3581 | 0 | case M_PROPERTY_GET_NODE: { |
3582 | 0 | struct sub_lines *lines = sub_get_lines(sub); |
3583 | 0 | if (!lines) |
3584 | 0 | return M_PROPERTY_UNAVAILABLE; |
3585 | | |
3586 | 0 | struct mpv_node *node = arg; |
3587 | 0 | node_init(node, MPV_FORMAT_NODE_ARRAY, NULL); |
3588 | 0 | for (int i = 0; i < lines->num_entries; i++) { |
3589 | 0 | struct sub_line *line = &lines->entries[i]; |
3590 | 0 | struct mpv_node *entry = node_array_add(node, MPV_FORMAT_NODE_MAP); |
3591 | 0 | node_map_add_string(entry, "text", line->text); |
3592 | 0 | node_map_add_double(entry, "start", line->start); |
3593 | 0 | if (line->end != MP_NOPTS_VALUE) |
3594 | 0 | node_map_add_double(entry, "end", line->end); |
3595 | 0 | } |
3596 | 0 | talloc_free(lines); |
3597 | 0 | return M_PROPERTY_OK; |
3598 | 0 | } |
3599 | 0 | case M_PROPERTY_PRINT: |
3600 | 0 | case M_PROPERTY_PRINT_UNCUT: { |
3601 | 0 | struct sub_lines *lines = sub_get_lines(sub); |
3602 | 0 | if (!lines) |
3603 | 0 | return M_PROPERTY_UNAVAILABLE; |
3604 | | |
3605 | 0 | char *res = talloc_strdup(NULL, ""); |
3606 | 0 | double time_pos = get_playback_time(mpctx); |
3607 | 0 | int pos = 0; |
3608 | |
|
3609 | 0 | for (int i = 0; i < lines->num_entries; i++) { |
3610 | 0 | struct sub_line *line = &lines->entries[i]; |
3611 | 0 | const char *reset = ""; |
3612 | |
|
3613 | 0 | if (line->start <= time_pos && line->end > time_pos) { |
3614 | 0 | res = append_selected_style(mpctx, res); |
3615 | 0 | reset = get_style_reset(mpctx); |
3616 | 0 | } |
3617 | |
|
3618 | 0 | res = talloc_asprintf_append(res, "%s%s\n", line->text, reset); |
3619 | |
|
3620 | 0 | if (line->start <= time_pos && i) |
3621 | 0 | pos += count_lines(lines->entries[i - 1].text); |
3622 | 0 | } |
3623 | |
|
3624 | 0 | *(char **)arg = cut_osd_list(mpctx, action, "Subtitle lines", res, pos); |
3625 | 0 | talloc_free(lines); |
3626 | 0 | return M_PROPERTY_OK; |
3627 | 0 | } |
3628 | 0 | } |
3629 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
3630 | 0 | } |
3631 | | |
3632 | | static int mp_property_playlist_current_pos(void *ctx, struct m_property *prop, |
3633 | | int action, void *arg) |
3634 | 389 | { |
3635 | 389 | MPContext *mpctx = ctx; |
3636 | 389 | struct playlist *pl = mpctx->playlist; |
3637 | | |
3638 | 389 | switch (action) { |
3639 | 97 | case M_PROPERTY_GET: { |
3640 | 97 | *(int *)arg = playlist_entry_to_index(pl, pl->current); |
3641 | 97 | return M_PROPERTY_OK; |
3642 | 0 | } |
3643 | 1 | case M_PROPERTY_SET: { |
3644 | 1 | pl->current = playlist_entry_from_index(pl, *(int *)arg); |
3645 | 1 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
3646 | 1 | return M_PROPERTY_OK; |
3647 | 0 | } |
3648 | 101 | case M_PROPERTY_GET_TYPE: |
3649 | 101 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT}; |
3650 | 101 | return M_PROPERTY_OK; |
3651 | 389 | } |
3652 | 190 | return M_PROPERTY_NOT_IMPLEMENTED; |
3653 | 389 | } |
3654 | | |
3655 | | |
3656 | | static int mp_property_playlist_playing_pos(void *ctx, struct m_property *prop, |
3657 | | int action, void *arg) |
3658 | 397 | { |
3659 | 397 | MPContext *mpctx = ctx; |
3660 | 397 | struct playlist *pl = mpctx->playlist; |
3661 | 397 | return m_property_int_ro(action, arg, |
3662 | 397 | playlist_entry_to_index(pl, mpctx->playing)); |
3663 | 397 | } |
3664 | | |
3665 | | static int mp_property_playlist_pos_x(void *ctx, struct m_property *prop, |
3666 | | int action, void *arg, int base) |
3667 | 1.19k | { |
3668 | 1.19k | MPContext *mpctx = ctx; |
3669 | 1.19k | struct playlist *pl = mpctx->playlist; |
3670 | | |
3671 | 1.19k | switch (action) { |
3672 | 197 | case M_PROPERTY_GET: { |
3673 | 197 | int pos = playlist_entry_to_index(pl, pl->current); |
3674 | 197 | *(int *)arg = pos < 0 ? -1 : pos + base; |
3675 | 197 | return M_PROPERTY_OK; |
3676 | 0 | } |
3677 | 92 | case M_PROPERTY_SET: { |
3678 | 92 | int pos = *(int *)arg - base; |
3679 | 92 | if (pos >= 0 && playlist_entry_to_index(pl, pl->current) == pos) |
3680 | 0 | return M_PROPERTY_OK; |
3681 | 92 | mp_set_playlist_entry(mpctx, playlist_entry_from_index(pl, pos)); |
3682 | 92 | return M_PROPERTY_OK; |
3683 | 92 | } |
3684 | 298 | case M_PROPERTY_GET_TYPE: |
3685 | 298 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_INT}; |
3686 | 298 | return M_PROPERTY_OK; |
3687 | 298 | case M_PROPERTY_GET_CONSTRICTED_TYPE: { |
3688 | 298 | struct m_option opt = { |
3689 | 298 | .type = CONF_TYPE_INT, |
3690 | 298 | .min = base, |
3691 | 298 | .max = playlist_entry_count(pl) - 1 + base, |
3692 | 298 | }; |
3693 | 298 | *(struct m_option *)arg = opt; |
3694 | 298 | return M_PROPERTY_OK; |
3695 | 92 | } |
3696 | 1.19k | } |
3697 | 312 | return M_PROPERTY_NOT_IMPLEMENTED; |
3698 | 1.19k | } |
3699 | | |
3700 | | static int mp_property_playlist_pos(void *ctx, struct m_property *prop, |
3701 | | int action, void *arg) |
3702 | 1.03k | { |
3703 | 1.03k | return mp_property_playlist_pos_x(ctx, prop, action, arg, 0); |
3704 | 1.03k | } |
3705 | | |
3706 | | static int mp_property_playlist_pos_1(void *ctx, struct m_property *prop, |
3707 | | int action, void *arg) |
3708 | 158 | { |
3709 | 158 | return mp_property_playlist_pos_x(ctx, prop, action, arg, 1); |
3710 | 158 | } |
3711 | | |
3712 | | static int do_playlist_entry(int item, int action, void *arg, void *ctx) |
3713 | 6.01k | { |
3714 | 6.01k | struct MPContext *mpctx = ctx; |
3715 | | |
3716 | 6.01k | struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist, item); |
3717 | 6.01k | if (!e) |
3718 | 0 | return M_PROPERTY_ERROR; |
3719 | | |
3720 | 6.01k | bool current = mpctx->playlist->current == e; |
3721 | 6.01k | bool playing = mpctx->playing == e; |
3722 | 6.01k | struct m_sub_property props[] = { |
3723 | 6.01k | {"filename", SUB_PROP_STR(e->filename)}, |
3724 | 6.01k | {"current", SUB_PROP_BOOL(1), .unavailable = !current}, |
3725 | 6.01k | {"playing", SUB_PROP_BOOL(1), .unavailable = !playing}, |
3726 | 6.01k | {"title", SUB_PROP_STR(e->title), .unavailable = !e->title}, |
3727 | 6.01k | {"id", SUB_PROP_INT64(e->id)}, |
3728 | 6.01k | {"playlist-path", SUB_PROP_STR(e->playlist_path), .unavailable = !e->playlist_path}, |
3729 | 6.01k | {0} |
3730 | 6.01k | }; |
3731 | | |
3732 | 6.01k | if (action != M_PROPERTY_KEY_ACTION) |
3733 | 4.86k | goto prop_get; |
3734 | | |
3735 | 1.15k | struct m_property_action_arg *ka = arg; |
3736 | 1.15k | if (strcmp(ka->key, "title") != 0) |
3737 | 939 | goto prop_get; |
3738 | | |
3739 | | // handle playlist/N/title writes |
3740 | 215 | switch (ka->action) { |
3741 | 0 | case M_PROPERTY_SET_NODE: { |
3742 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
3743 | 0 | } |
3744 | 54 | case M_PROPERTY_GET_TYPE: { |
3745 | 54 | *(struct m_option *)ka->arg = (struct m_option){ |
3746 | 54 | .type = CONF_TYPE_STRING, |
3747 | 54 | }; |
3748 | 54 | return M_PROPERTY_OK; |
3749 | 0 | } |
3750 | 0 | case M_PROPERTY_SET: { |
3751 | 0 | talloc_replace(e, e->title, *(char **)ka->arg); |
3752 | 0 | mp_notify_property(mpctx, "playlist"); |
3753 | 0 | return M_PROPERTY_OK; |
3754 | 0 | } |
3755 | 215 | } |
3756 | | |
3757 | 5.96k | prop_get: |
3758 | 5.96k | return m_property_read_sub(props, action, arg); |
3759 | 215 | } |
3760 | | |
3761 | | static int mp_property_playlist_path(void *ctx, struct m_property *prop, |
3762 | | int action, void *arg) |
3763 | 124 | { |
3764 | 124 | MPContext *mpctx = ctx; |
3765 | 124 | if (!mpctx->playlist->current) |
3766 | 36 | return M_PROPERTY_UNAVAILABLE; |
3767 | | |
3768 | 88 | struct playlist_entry *e = mpctx->playlist->current; |
3769 | 88 | return m_property_strdup_ro(action, arg, e->playlist_path); |
3770 | 124 | } |
3771 | | |
3772 | | static int mp_property_playlist(void *ctx, struct m_property *prop, |
3773 | | int action, void *arg) |
3774 | 7.40k | { |
3775 | 7.40k | MPContext *mpctx = ctx; |
3776 | 7.40k | if (action == M_PROPERTY_PRINT || action == M_PROPERTY_PRINT_UNCUT) { |
3777 | 841 | struct playlist *pl = mpctx->playlist; |
3778 | 841 | char *res = talloc_strdup(NULL, ""); |
3779 | | |
3780 | 4.58k | for (int n = 0; n < pl->num_entries; n++) { |
3781 | 3.74k | struct playlist_entry *e = pl->entries[n]; |
3782 | 3.74k | if (pl->current == e) |
3783 | 575 | res = append_selected_style(mpctx, res); |
3784 | 3.74k | const char *reset = pl->current == e ? get_style_reset(mpctx) : ""; |
3785 | 3.74k | const char *p = e->title; |
3786 | 3.74k | if (!p || mpctx->opts->playlist_entry_name > 0) { |
3787 | 3.74k | p = e->filename; |
3788 | 3.74k | if (!mp_is_url(bstr0(p))) { |
3789 | 2.53k | const char *s = mp_basename(e->filename); |
3790 | 2.53k | if (s[0]) |
3791 | 2.13k | p = s; |
3792 | 2.53k | } |
3793 | 3.74k | } |
3794 | 3.74k | if (!e->title || p == e->title || mpctx->opts->playlist_entry_name == 1) { |
3795 | 3.74k | res = talloc_asprintf_append(res, "%s%s\n", p, reset); |
3796 | 3.74k | } else { |
3797 | 0 | res = talloc_asprintf_append(res, "%s (%s)%s\n", e->title, p, reset); |
3798 | 0 | } |
3799 | 3.74k | } |
3800 | | |
3801 | 841 | *(char **)arg = cut_osd_list(mpctx, action, "Playlist", res, |
3802 | 841 | playlist_entry_to_index(pl, pl->current)); |
3803 | 841 | return M_PROPERTY_OK; |
3804 | 841 | } |
3805 | | |
3806 | 6.56k | return m_property_read_list(action, arg, playlist_entry_count(mpctx->playlist), |
3807 | 6.56k | do_playlist_entry, mpctx); |
3808 | 7.40k | } |
3809 | | |
3810 | | static char *print_obj_osd_list(struct m_obj_settings *list) |
3811 | 382 | { |
3812 | 382 | char *res = NULL; |
3813 | 569 | for (int n = 0; list && list[n].name; n++) { |
3814 | 187 | res = talloc_asprintf_append(res, "%s [", list[n].name); |
3815 | 430 | for (int i = 0; list[n].attribs && list[n].attribs[i]; i += 2) { |
3816 | 243 | res = talloc_asprintf_append(res, "%s%s=%s", i > 0 ? " " : "", |
3817 | 243 | list[n].attribs[i], |
3818 | 243 | list[n].attribs[i + 1]); |
3819 | 243 | } |
3820 | 187 | res = talloc_asprintf_append(res, "]"); |
3821 | 187 | if (!list[n].enabled) |
3822 | 58 | res = talloc_strdup_append(res, " (disabled)"); |
3823 | 187 | res = talloc_strdup_append(res, "\n"); |
3824 | 187 | } |
3825 | 382 | if (!res) |
3826 | 196 | res = talloc_strdup(NULL, "(empty)"); |
3827 | 382 | return res; |
3828 | 382 | } |
3829 | | |
3830 | | static int property_filter(struct m_property *prop, int action, void *arg, |
3831 | | MPContext *mpctx, enum stream_type mt) |
3832 | 34.1k | { |
3833 | 34.1k | if (action == M_PROPERTY_PRINT) { |
3834 | 382 | struct m_config_option *opt = m_config_get_co(mpctx->mconfig, |
3835 | 382 | bstr0(prop->name)); |
3836 | 382 | *(char **)arg = print_obj_osd_list(*(struct m_obj_settings **)opt->data); |
3837 | 382 | return M_PROPERTY_OK; |
3838 | 382 | } |
3839 | 33.7k | return mp_property_generic_option(mpctx, prop, action, arg); |
3840 | 34.1k | } |
3841 | | |
3842 | | static int mp_property_vf(void *ctx, struct m_property *prop, |
3843 | | int action, void *arg) |
3844 | 25.8k | { |
3845 | 25.8k | return property_filter(prop, action, arg, ctx, STREAM_VIDEO); |
3846 | 25.8k | } |
3847 | | |
3848 | | static int mp_property_af(void *ctx, struct m_property *prop, |
3849 | | int action, void *arg) |
3850 | 8.31k | { |
3851 | 8.31k | return property_filter(prop, action, arg, ctx, STREAM_AUDIO); |
3852 | 8.31k | } |
3853 | | |
3854 | | static int mp_property_ab_loop(void *ctx, struct m_property *prop, |
3855 | | int action, void *arg) |
3856 | 2.79k | { |
3857 | 2.79k | struct MPContext *mpctx = ctx; |
3858 | 2.79k | if (action == M_PROPERTY_KEY_ACTION) { |
3859 | 324 | double val; |
3860 | 324 | if (mp_property_generic_option(mpctx, prop, M_PROPERTY_GET, &val) < 1) |
3861 | 0 | return M_PROPERTY_ERROR; |
3862 | | |
3863 | 324 | return property_time(action, arg, val); |
3864 | 324 | } |
3865 | 2.46k | return mp_property_generic_option(mpctx, prop, action, arg); |
3866 | 2.79k | } |
3867 | | |
3868 | | static int mp_property_packet_bitrate(void *ctx, struct m_property *prop, |
3869 | | int action, void *arg) |
3870 | 246 | { |
3871 | 246 | MPContext *mpctx = ctx; |
3872 | 246 | int type = *(int *)prop->priv; |
3873 | | |
3874 | 246 | struct demuxer *demuxer = NULL; |
3875 | 246 | if (mpctx->current_track[0][type]) |
3876 | 107 | demuxer = mpctx->current_track[0][type]->demuxer; |
3877 | 246 | if (!demuxer) |
3878 | 139 | demuxer = mpctx->demuxer; |
3879 | 246 | if (!demuxer) |
3880 | 65 | return M_PROPERTY_UNAVAILABLE; |
3881 | | |
3882 | 181 | double r[STREAM_TYPE_COUNT]; |
3883 | 181 | demux_get_bitrate_stats(demuxer, r); |
3884 | 181 | if (r[type] < 0) |
3885 | 181 | return M_PROPERTY_UNAVAILABLE; |
3886 | | |
3887 | | // r[type] is in bytes/second -> bits |
3888 | 0 | double rate = r[type] * 8; |
3889 | |
|
3890 | 0 | if (action == M_PROPERTY_PRINT) { |
3891 | 0 | rate /= 1000; |
3892 | 0 | if (rate < 1000) { |
3893 | 0 | *(char **)arg = talloc_asprintf(NULL, "%.f kbps", rate); |
3894 | 0 | } else { |
3895 | 0 | *(char **)arg = talloc_asprintf(NULL, "%.3f Mbps", rate / 1000.0); |
3896 | 0 | } |
3897 | 0 | return M_PROPERTY_OK; |
3898 | 0 | } |
3899 | 0 | return m_property_int64_ro(action, arg, llrint(rate)); |
3900 | 0 | } |
3901 | | |
3902 | | static int mp_property_cwd(void *ctx, struct m_property *prop, |
3903 | | int action, void *arg) |
3904 | 213 | { |
3905 | 213 | switch (action) { |
3906 | 49 | case M_PROPERTY_GET: { |
3907 | 49 | char *cwd = mp_getcwd(NULL); |
3908 | 49 | if (!cwd) |
3909 | 0 | return M_PROPERTY_ERROR; |
3910 | 49 | *(char **)arg = cwd; |
3911 | 49 | return M_PROPERTY_OK; |
3912 | 49 | } |
3913 | 53 | case M_PROPERTY_GET_TYPE: |
3914 | 53 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING}; |
3915 | 53 | return M_PROPERTY_OK; |
3916 | 213 | } |
3917 | 111 | return M_PROPERTY_NOT_IMPLEMENTED; |
3918 | 213 | } |
3919 | | |
3920 | | static int mp_property_current_watch_later_dir(void *ctx, struct m_property *prop, |
3921 | | int action, void *arg) |
3922 | 67 | { |
3923 | 67 | MPContext *mpctx = ctx; |
3924 | 67 | char *dir = mp_get_playback_resume_dir(mpctx); |
3925 | 67 | int r = m_property_strdup_ro(action, arg, dir); |
3926 | 67 | talloc_free(dir); |
3927 | 67 | return r; |
3928 | 67 | } |
3929 | | |
3930 | | static int mp_property_protocols(void *ctx, struct m_property *prop, |
3931 | | int action, void *arg) |
3932 | 1.49k | { |
3933 | 1.49k | switch (action) { |
3934 | 371 | case M_PROPERTY_GET: |
3935 | 371 | *(char ***)arg = stream_get_proto_list(false); |
3936 | 371 | return M_PROPERTY_OK; |
3937 | 374 | case M_PROPERTY_GET_TYPE: |
3938 | 374 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
3939 | 374 | return M_PROPERTY_OK; |
3940 | 1.49k | } |
3941 | 746 | return M_PROPERTY_NOT_IMPLEMENTED; |
3942 | 1.49k | } |
3943 | | |
3944 | | static int mp_property_keylist(void *ctx, struct m_property *prop, |
3945 | | int action, void *arg) |
3946 | 502 | { |
3947 | 502 | switch (action) { |
3948 | 121 | case M_PROPERTY_GET: |
3949 | 121 | *(char ***)arg = mp_get_key_list(); |
3950 | 121 | return M_PROPERTY_OK; |
3951 | 125 | case M_PROPERTY_GET_TYPE: |
3952 | 125 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
3953 | 125 | return M_PROPERTY_OK; |
3954 | 502 | } |
3955 | 256 | return M_PROPERTY_NOT_IMPLEMENTED; |
3956 | 502 | } |
3957 | | |
3958 | | static int get_decoder_entry(int item, int action, void *arg, void *ctx) |
3959 | 2.51M | { |
3960 | 2.51M | struct mp_decoder_list *codecs = ctx; |
3961 | 2.51M | struct mp_decoder_entry *c = &codecs->entries[item]; |
3962 | | |
3963 | 2.51M | struct m_sub_property props[] = { |
3964 | 2.51M | {"codec", SUB_PROP_STR(c->codec)}, |
3965 | 2.51M | {"driver" , SUB_PROP_STR(c->decoder)}, |
3966 | 2.51M | {"description", SUB_PROP_STR(c->desc)}, |
3967 | 2.51M | {0} |
3968 | 2.51M | }; |
3969 | | |
3970 | 2.51M | return m_property_read_sub(props, action, arg); |
3971 | 2.51M | } |
3972 | | |
3973 | | static int mp_property_decoders(void *ctx, struct m_property *prop, |
3974 | | int action, void *arg) |
3975 | 18.8k | { |
3976 | 18.8k | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3977 | 18.8k | if (valid != M_PROPERTY_VALID) |
3978 | 11.9k | return valid; |
3979 | | |
3980 | 6.89k | struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list); |
3981 | 6.89k | struct mp_decoder_list *v = talloc_steal(codecs, video_decoder_list()); |
3982 | 6.89k | struct mp_decoder_list *a = talloc_steal(codecs, audio_decoder_list()); |
3983 | 6.89k | mp_append_decoders(codecs, v); |
3984 | 6.89k | mp_append_decoders(codecs, a); |
3985 | 6.89k | int r = m_property_read_list(action, arg, codecs->num_entries, |
3986 | 6.89k | get_decoder_entry, codecs); |
3987 | 6.89k | talloc_free(codecs); |
3988 | 6.89k | return r; |
3989 | 18.8k | } |
3990 | | |
3991 | | static int mp_property_encoders(void *ctx, struct m_property *prop, |
3992 | | int action, void *arg) |
3993 | 427 | { |
3994 | 427 | int valid = m_property_read_sub_validate(ctx, prop, action, arg); |
3995 | 427 | if (valid != M_PROPERTY_VALID) |
3996 | 287 | return valid; |
3997 | | |
3998 | 140 | struct mp_decoder_list *codecs = talloc_zero(NULL, struct mp_decoder_list); |
3999 | 140 | mp_add_lavc_encoders(codecs); |
4000 | 140 | int r = m_property_read_list(action, arg, codecs->num_entries, |
4001 | 140 | get_decoder_entry, codecs); |
4002 | 140 | talloc_free(codecs); |
4003 | 140 | return r; |
4004 | 427 | } |
4005 | | |
4006 | | static int mp_property_lavf_demuxers(void *ctx, struct m_property *prop, |
4007 | | int action, void *arg) |
4008 | 401 | { |
4009 | 401 | switch (action) { |
4010 | 96 | case M_PROPERTY_GET: |
4011 | 96 | *(char ***)arg = mp_get_lavf_demuxers(); |
4012 | 96 | return M_PROPERTY_OK; |
4013 | 100 | case M_PROPERTY_GET_TYPE: |
4014 | 100 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
4015 | 100 | return M_PROPERTY_OK; |
4016 | 401 | } |
4017 | 205 | return M_PROPERTY_NOT_IMPLEMENTED; |
4018 | 401 | } |
4019 | | |
4020 | | static int mp_property_version(void *ctx, struct m_property *prop, |
4021 | | int action, void *arg) |
4022 | 242 | { |
4023 | 242 | return m_property_strdup_ro(action, arg, mpv_version); |
4024 | 242 | } |
4025 | | |
4026 | | static int mp_property_configuration(void *ctx, struct m_property *prop, |
4027 | | int action, void *arg) |
4028 | 44 | { |
4029 | 44 | return m_property_strdup_ro(action, arg, CONFIGURATION); |
4030 | 44 | } |
4031 | | |
4032 | | static int mp_property_ffmpeg(void *ctx, struct m_property *prop, |
4033 | | int action, void *arg) |
4034 | 119 | { |
4035 | 119 | return m_property_strdup_ro(action, arg, av_version_info()); |
4036 | 119 | } |
4037 | | |
4038 | | static int mp_property_libass_version(void *ctx, struct m_property *prop, |
4039 | | int action, void *arg) |
4040 | 92 | { |
4041 | 92 | return m_property_int64_ro(action, arg, ass_library_version()); |
4042 | 92 | } |
4043 | | |
4044 | | static int mp_property_libplacebo_version(void *ctx, struct m_property *prop, |
4045 | | int action, void *arg) |
4046 | 67 | { |
4047 | 67 | return m_property_strdup_ro(action, arg, PL_VERSION); |
4048 | 67 | } |
4049 | | |
4050 | | static int mp_property_subrandr_version(void *ctx, struct m_property *prop, |
4051 | | int action, void *arg) |
4052 | 54 | { |
4053 | | #if HAVE_SUBRANDR |
4054 | | uint32_t major, minor, patch; |
4055 | | sbr_library_version(&major, &minor, &patch); |
4056 | | const char *result = mp_tprintf(33, "%" PRIu32 ".%" PRIu32 ".%" PRIu32, major, minor, patch); |
4057 | | return m_property_strdup_ro(action, arg, result); |
4058 | | #else |
4059 | 54 | return M_PROPERTY_UNAVAILABLE; |
4060 | 54 | #endif |
4061 | 54 | } |
4062 | | |
4063 | | static int mp_property_platform(void *ctx, struct m_property *prop, |
4064 | | int action, void *arg) |
4065 | 181 | { |
4066 | 181 | return m_property_strdup_ro(action, arg, PLATFORM); |
4067 | 181 | } |
4068 | | |
4069 | | static int mp_property_alias(void *ctx, struct m_property *prop, |
4070 | | int action, void *arg) |
4071 | 12.3k | { |
4072 | 12.3k | const char *real_property = prop->priv; |
4073 | 12.3k | return mp_property_do(real_property, action, arg, ctx); |
4074 | 12.3k | } |
4075 | | |
4076 | | static int mp_property_deprecated_alias(void *ctx, struct m_property *prop, |
4077 | | int action, void *arg) |
4078 | 865 | { |
4079 | 865 | MPContext *mpctx = ctx; |
4080 | 865 | struct command_ctx *cmd = mpctx->command_ctx; |
4081 | 865 | const char *real_property = prop->priv; |
4082 | 1.14k | for (int n = 0; n < cmd->num_warned_deprecated; n++) { |
4083 | 1.05k | if (strcmp(cmd->warned_deprecated[n], prop->name) == 0) |
4084 | 773 | goto done; |
4085 | 1.05k | } |
4086 | 92 | MP_WARN(mpctx, "Warning: property '%s' was replaced with '%s' and " |
4087 | 92 | "might be removed in the future.\n", prop->name, real_property); |
4088 | 92 | MP_TARRAY_APPEND(cmd, cmd->warned_deprecated, cmd->num_warned_deprecated, |
4089 | 92 | (char *)prop->name); |
4090 | | |
4091 | 865 | done: |
4092 | 865 | return mp_property_do(real_property, action, arg, ctx); |
4093 | 92 | } |
4094 | | |
4095 | | static int access_options(struct m_property_action_arg *ka, bool local, |
4096 | | MPContext *mpctx) |
4097 | 866 | { |
4098 | 866 | struct m_config_option *opt = m_config_get_co(mpctx->mconfig, |
4099 | 866 | bstr0(ka->key)); |
4100 | 866 | if (!opt) |
4101 | 212 | return M_PROPERTY_UNKNOWN; |
4102 | 654 | if (!opt->data) |
4103 | 61 | return M_PROPERTY_UNAVAILABLE; |
4104 | | |
4105 | 593 | switch (ka->action) { |
4106 | 119 | case M_PROPERTY_GET: |
4107 | 119 | m_option_copy(opt->opt, ka->arg, opt->data); |
4108 | 119 | return M_PROPERTY_OK; |
4109 | 22 | case M_PROPERTY_SET: { |
4110 | 22 | if (local && !mpctx->playing) |
4111 | 3 | return M_PROPERTY_ERROR; |
4112 | 19 | int flags = local ? M_SETOPT_BACKUP : 0; |
4113 | 19 | int r = m_config_set_option_raw(mpctx->mconfig, opt, ka->arg, flags); |
4114 | 19 | mp_wakeup_core(mpctx); |
4115 | 19 | return r < 0 ? M_PROPERTY_ERROR : M_PROPERTY_OK; |
4116 | 22 | } |
4117 | 152 | case M_PROPERTY_GET_TYPE: |
4118 | 152 | *(struct m_option *)ka->arg = *opt->opt; |
4119 | 152 | return M_PROPERTY_OK; |
4120 | 593 | } |
4121 | 300 | return M_PROPERTY_NOT_IMPLEMENTED; |
4122 | 593 | } |
4123 | | |
4124 | | static int access_option_list(int action, void *arg, bool local, MPContext *mpctx) |
4125 | 25.5k | { |
4126 | 25.5k | switch (action) { |
4127 | 6.21k | case M_PROPERTY_GET_TYPE: |
4128 | 6.21k | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
4129 | 6.21k | return M_PROPERTY_OK; |
4130 | 6.18k | case M_PROPERTY_GET: |
4131 | 6.18k | *(char ***)arg = m_config_list_options(NULL, mpctx->mconfig); |
4132 | 6.18k | return M_PROPERTY_OK; |
4133 | 866 | case M_PROPERTY_KEY_ACTION: |
4134 | 866 | return access_options(arg, local, mpctx); |
4135 | 25.5k | } |
4136 | 12.3k | return M_PROPERTY_NOT_IMPLEMENTED; |
4137 | 25.5k | } |
4138 | | |
4139 | | |
4140 | | static int mp_property_options(void *ctx, struct m_property *prop, |
4141 | | int action, void *arg) |
4142 | 25.4k | { |
4143 | 25.4k | MPContext *mpctx = ctx; |
4144 | 25.4k | return access_option_list(action, arg, false, mpctx); |
4145 | 25.4k | } |
4146 | | |
4147 | | static int mp_property_local_options(void *ctx, struct m_property *prop, |
4148 | | int action, void *arg) |
4149 | 128 | { |
4150 | 128 | MPContext *mpctx = ctx; |
4151 | 128 | return access_option_list(action, arg, true, mpctx); |
4152 | 128 | } |
4153 | | |
4154 | | static int mp_property_option_info(void *ctx, struct m_property *prop, |
4155 | | int action, void *arg) |
4156 | 5.22k | { |
4157 | 5.22k | MPContext *mpctx = ctx; |
4158 | 5.22k | switch (action) { |
4159 | 5.07k | case M_PROPERTY_KEY_ACTION: { |
4160 | 5.07k | struct m_property_action_arg *ka = arg; |
4161 | 5.07k | bstr key; |
4162 | 5.07k | const char *rem; |
4163 | 5.07k | m_property_split_path(ka->key, &key, &rem); |
4164 | 5.07k | struct m_config_option *co = m_config_get_co(mpctx->mconfig, key); |
4165 | 5.07k | if (!co) |
4166 | 203 | return M_PROPERTY_UNKNOWN; |
4167 | 4.86k | const struct m_option *opt = co->opt; |
4168 | | |
4169 | 4.86k | union m_option_value def = m_option_value_default; |
4170 | 4.86k | const void *def_ptr = m_config_get_co_default(mpctx->mconfig, co); |
4171 | 4.86k | bool has_default = def_ptr && opt->type->size > 0; |
4172 | 4.86k | if (has_default) |
4173 | 4.28k | memcpy(&def, def_ptr, opt->type->size); |
4174 | | |
4175 | 4.86k | bool has_minmax = opt->min < opt->max && |
4176 | 921 | (opt->type->flags & M_OPT_TYPE_USES_RANGE); |
4177 | 4.86k | char **choices = NULL; |
4178 | | |
4179 | 4.86k | if (opt->type == &m_option_type_choice) { |
4180 | 1.23k | const struct m_opt_choice_alternatives *alt = opt->priv; |
4181 | 1.23k | int num = 0; |
4182 | 8.61k | for ( ; alt->name; alt++) |
4183 | 7.38k | MP_TARRAY_APPEND(NULL, choices, num, alt->name); |
4184 | 1.23k | MP_TARRAY_APPEND(NULL, choices, num, NULL); |
4185 | 1.23k | } |
4186 | 4.86k | if (opt->type == &m_option_type_obj_settings_list) { |
4187 | 1.65k | const struct m_obj_list *objs = opt->priv; |
4188 | 1.65k | int num = 0; |
4189 | 9.23k | for (int n = 0; ; n++) { |
4190 | 9.23k | struct m_obj_desc desc = {0}; |
4191 | 9.23k | if (!objs->get_desc(&desc, n)) |
4192 | 1.65k | break; |
4193 | 7.58k | MP_TARRAY_APPEND(NULL, choices, num, (char *)desc.name); |
4194 | 7.58k | } |
4195 | 1.65k | if (objs->get_lavfi_filters) { |
4196 | 1.16k | const char **filters = objs->get_lavfi_filters(choices); |
4197 | 1.16k | for (int n = 0; filters[n]; n++) { |
4198 | 0 | MP_TARRAY_APPEND(NULL, choices, num, (char *)filters[n]); |
4199 | 0 | } |
4200 | 1.16k | } |
4201 | 1.65k | MP_TARRAY_APPEND(NULL, choices, num, NULL); |
4202 | 1.65k | } |
4203 | | |
4204 | 4.86k | struct m_sub_property props[] = { |
4205 | 4.86k | {"name", SUB_PROP_STR(co->name)}, |
4206 | 4.86k | {"type", SUB_PROP_STR(opt->type->name)}, |
4207 | 4.86k | {"set-from-commandline", SUB_PROP_BOOL(co->is_set_from_cmdline)}, |
4208 | 4.86k | {"set-locally", SUB_PROP_BOOL(co->is_set_locally)}, |
4209 | 4.86k | {"expects-file", SUB_PROP_BOOL(opt->flags & M_OPT_FILE)}, |
4210 | 4.86k | {"default-value", *opt, def, .unavailable = !has_default}, |
4211 | 4.86k | {"min", SUB_PROP_DOUBLE(opt->min), |
4212 | 4.86k | .unavailable = !(has_minmax && opt->min != DBL_MIN)}, |
4213 | 4.86k | {"max", SUB_PROP_DOUBLE(opt->max), |
4214 | 4.86k | .unavailable = !(has_minmax && opt->max != DBL_MAX)}, |
4215 | 4.86k | {"choices", SUB_PROP_STRING_LIST(choices), |
4216 | 4.86k | .unavailable = !choices}, |
4217 | 4.86k | {0} |
4218 | 4.86k | }; |
4219 | | |
4220 | 4.86k | struct m_property_action_arg next_ka = *ka; |
4221 | 4.86k | next_ka.key = rem; |
4222 | 4.86k | int r = m_property_read_sub(props, M_PROPERTY_KEY_ACTION, &next_ka); |
4223 | 4.86k | talloc_free(choices); |
4224 | 4.86k | return r; |
4225 | 5.07k | } |
4226 | 5.22k | } |
4227 | 155 | return M_PROPERTY_NOT_IMPLEMENTED; |
4228 | 5.22k | } |
4229 | | |
4230 | | static int mp_property_list(void *ctx, struct m_property *prop, |
4231 | | int action, void *arg) |
4232 | 1.52k | { |
4233 | 1.52k | struct MPContext *mpctx = ctx; |
4234 | 1.52k | struct command_ctx *cmd = mpctx->command_ctx; |
4235 | | |
4236 | 1.52k | switch (action) { |
4237 | 394 | case M_PROPERTY_GET_TYPE: |
4238 | 394 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING_LIST}; |
4239 | 394 | return M_PROPERTY_OK; |
4240 | 390 | case M_PROPERTY_GET: { |
4241 | 390 | char **list = NULL; |
4242 | 390 | int num = 0; |
4243 | 360k | for (int n = 0; n < cmd->properties.num; n++) { |
4244 | 360k | MP_TARRAY_APPEND(NULL, list, num, |
4245 | 360k | talloc_strdup(NULL, cmd->properties.props[n].name)); |
4246 | 360k | } |
4247 | 390 | MP_TARRAY_APPEND(NULL, list, num, NULL); |
4248 | 390 | *(char ***)arg = list; |
4249 | 390 | return M_PROPERTY_OK; |
4250 | 0 | } |
4251 | 1.52k | } |
4252 | 740 | return M_PROPERTY_NOT_IMPLEMENTED; |
4253 | 1.52k | } |
4254 | | |
4255 | | static int mp_profile_list(void *ctx, struct m_property *prop, |
4256 | | int action, void *arg) |
4257 | 3.79k | { |
4258 | 3.79k | MPContext *mpctx = ctx; |
4259 | 3.79k | switch (action) { |
4260 | 960 | case M_PROPERTY_GET_TYPE: |
4261 | 960 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
4262 | 960 | return M_PROPERTY_OK; |
4263 | 956 | case M_PROPERTY_GET: { |
4264 | 956 | *(struct mpv_node *)arg = m_config_get_profiles(mpctx->mconfig); |
4265 | 956 | return M_PROPERTY_OK; |
4266 | 0 | } |
4267 | 3.79k | } |
4268 | 1.88k | return M_PROPERTY_NOT_IMPLEMENTED; |
4269 | 3.79k | } |
4270 | | |
4271 | | static int mp_property_commands(void *ctx, struct m_property *prop, |
4272 | | int action, void *arg) |
4273 | 2.56k | { |
4274 | 2.56k | switch (action) { |
4275 | 640 | case M_PROPERTY_GET_TYPE: |
4276 | 640 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
4277 | 640 | return M_PROPERTY_OK; |
4278 | 636 | case M_PROPERTY_GET: { |
4279 | 636 | struct mpv_node *root = arg; |
4280 | 636 | node_init(root, MPV_FORMAT_NODE_ARRAY, NULL); |
4281 | | |
4282 | 55.9k | for (int n = 0; mp_cmds[n].name; n++) { |
4283 | 55.3k | const struct mp_cmd_def *cmd = &mp_cmds[n]; |
4284 | 55.3k | struct mpv_node *entry = node_array_add(root, MPV_FORMAT_NODE_MAP); |
4285 | | |
4286 | 55.3k | node_map_add_string(entry, "name", cmd->name); |
4287 | | |
4288 | 55.3k | struct mpv_node *args = |
4289 | 55.3k | node_map_add(entry, "args", MPV_FORMAT_NODE_ARRAY); |
4290 | 158k | for (int i = 0; i < MP_CMD_DEF_MAX_ARGS; i++) { |
4291 | 157k | const struct m_option *a = &cmd->args[i]; |
4292 | 157k | if (!a->type) |
4293 | 54.6k | break; |
4294 | 103k | struct mpv_node *ae = node_array_add(args, MPV_FORMAT_NODE_MAP); |
4295 | 103k | node_map_add_string(ae, "name", a->name); |
4296 | 103k | node_map_add_string(ae, "type", a->type->name); |
4297 | 103k | node_map_add_flag(ae, "optional", a->flags & MP_CMD_OPT_ARG); |
4298 | 103k | if (a->type == &m_option_type_choice || |
4299 | 89.6k | a->type == &m_option_type_flags) { |
4300 | 19.7k | const struct m_opt_choice_alternatives *alt = a->priv; |
4301 | 19.7k | if (alt) { |
4302 | 19.7k | if (a->defval) { |
4303 | 5.72k | const int ival = *(const int *)a->defval; |
4304 | 5.72k | const char *val = m_opt_choice_str_def(alt, ival, NULL); |
4305 | 5.72k | if (val) |
4306 | 5.72k | node_map_add_string(ae, "default-value", val); |
4307 | 5.72k | } |
4308 | 19.7k | struct mpv_node *choices = |
4309 | 19.7k | node_map_add(ae, "choices", MPV_FORMAT_NODE_ARRAY); |
4310 | 124k | for (int j = 0; alt[j].name; j++) { |
4311 | 104k | struct mpv_node *ce = node_array_add(choices, MPV_FORMAT_NONE); |
4312 | 104k | ce->format = MPV_FORMAT_STRING; |
4313 | 104k | ce->u.string = talloc_strdup(choices->u.list, alt[j].name); |
4314 | 104k | } |
4315 | 19.7k | } |
4316 | 83.3k | } else if (a->defval) { |
4317 | 19.7k | struct mpv_node *def = |
4318 | 19.7k | node_map_add(ae, "default-value", MPV_FORMAT_NONE); |
4319 | 19.7k | m_option_get_node(a, ae->u.list, def, (void *)a->defval); |
4320 | 19.7k | } |
4321 | 103k | } |
4322 | | |
4323 | 55.3k | node_map_add_flag(entry, "vararg", cmd->vararg); |
4324 | 55.3k | } |
4325 | | |
4326 | 636 | return M_PROPERTY_OK; |
4327 | 0 | } |
4328 | 2.56k | } |
4329 | 1.28k | return M_PROPERTY_NOT_IMPLEMENTED; |
4330 | 2.56k | } |
4331 | | |
4332 | | static int mp_property_bindings(void *ctx, struct m_property *prop, |
4333 | | int action, void *arg) |
4334 | 753 | { |
4335 | 753 | MPContext *mpctx = ctx; |
4336 | 753 | switch (action) { |
4337 | 199 | case M_PROPERTY_GET_TYPE: |
4338 | 199 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
4339 | 199 | return M_PROPERTY_OK; |
4340 | 196 | case M_PROPERTY_GET: { |
4341 | 196 | *(struct mpv_node *)arg = mp_input_get_bindings(mpctx->input); |
4342 | 196 | return M_PROPERTY_OK; |
4343 | 0 | } |
4344 | 753 | } |
4345 | 358 | return M_PROPERTY_NOT_IMPLEMENTED; |
4346 | 753 | } |
4347 | | |
4348 | | static int mp_property_mdata(void *ctx, struct m_property *prop, |
4349 | | int action, void *arg) |
4350 | 335 | { |
4351 | 335 | MPContext *mpctx = ctx; |
4352 | 335 | mpv_node *node = &mpctx->command_ctx->mdata; |
4353 | | |
4354 | 335 | switch (action) { |
4355 | 83 | case M_PROPERTY_GET_TYPE: |
4356 | 83 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_NODE}; |
4357 | 83 | return M_PROPERTY_OK; |
4358 | 79 | case M_PROPERTY_GET: |
4359 | 79 | case M_PROPERTY_GET_NODE: |
4360 | 79 | m_option_copy(&mdata_type, arg, node); |
4361 | 79 | return M_PROPERTY_OK; |
4362 | 0 | case M_PROPERTY_SET: |
4363 | 4 | case M_PROPERTY_SET_NODE: { |
4364 | 4 | m_option_copy(&mdata_type, node, arg); |
4365 | 4 | talloc_steal(mpctx->command_ctx, node_get_alloc(node)); |
4366 | 4 | mp_notify_property(mpctx, prop->name); |
4367 | | |
4368 | 4 | struct vo *vo = mpctx->video_out; |
4369 | 4 | if (vo) |
4370 | 0 | vo_control(vo, VOCTRL_UPDATE_MENU, arg); |
4371 | 4 | return M_PROPERTY_OK; |
4372 | 0 | } |
4373 | 335 | } |
4374 | 169 | return M_PROPERTY_NOT_IMPLEMENTED; |
4375 | 335 | } |
4376 | | |
4377 | | static int mp_property_default_menu(void *ctx, struct m_property *prop, |
4378 | | int action, void *arg) |
4379 | 342 | { |
4380 | 342 | switch (action) { |
4381 | 82 | case M_PROPERTY_GET: |
4382 | 82 | *(char **)arg = talloc_strdup(NULL, |
4383 | 82 | #include "etc/menu.conf.inc" |
4384 | 82 | ); |
4385 | 82 | return M_PROPERTY_OK; |
4386 | 85 | case M_PROPERTY_GET_TYPE: |
4387 | 85 | *(struct m_option *)arg = (struct m_option){.type = CONF_TYPE_STRING}; |
4388 | 85 | return M_PROPERTY_OK; |
4389 | 342 | } |
4390 | 175 | return M_PROPERTY_NOT_IMPLEMENTED; |
4391 | 342 | } |
4392 | | |
4393 | | static int do_list_udata(int item, int action, void *arg, void *ctx); |
4394 | | |
4395 | | struct udata_ctx { |
4396 | | MPContext *mpctx; |
4397 | | const char *path; |
4398 | | mpv_node *node; |
4399 | | void *ta_parent; |
4400 | | int depth; |
4401 | | }; |
4402 | | |
4403 | | static int do_op_udata(struct udata_ctx* ctx, int action, void *arg) |
4404 | 25.5k | { |
4405 | 25.5k | MPContext *mpctx = ctx->mpctx; |
4406 | 25.5k | mpv_node *node = ctx->node; |
4407 | | |
4408 | 25.5k | switch (action) { |
4409 | 424 | case M_PROPERTY_GET_TYPE: |
4410 | 424 | *(struct m_option *)arg = udata_type; |
4411 | 424 | return M_PROPERTY_OK; |
4412 | 183 | case M_PROPERTY_GET: |
4413 | 183 | case M_PROPERTY_GET_NODE: // same as GET, because type==mpv_node |
4414 | 183 | mp_assert(node); |
4415 | 183 | m_option_copy(&udata_type, arg, node); |
4416 | 183 | return M_PROPERTY_OK; |
4417 | 100 | case M_PROPERTY_FIXED_LEN_PRINT: |
4418 | 237 | case M_PROPERTY_PRINT: { |
4419 | 237 | char *str = m_option_pretty_print(&udata_type, node, action == M_PROPERTY_FIXED_LEN_PRINT); |
4420 | 237 | *(char **)arg = str; |
4421 | 237 | return str != NULL; |
4422 | 100 | } |
4423 | 0 | case M_PROPERTY_SET: |
4424 | 890 | case M_PROPERTY_SET_NODE: |
4425 | 890 | mp_assert(node); |
4426 | 890 | m_option_copy(&udata_type, node, arg); |
4427 | 890 | talloc_steal(ctx->ta_parent, node_get_alloc(node)); |
4428 | 890 | mp_notify_property(mpctx, ctx->path); |
4429 | 890 | return M_PROPERTY_OK; |
4430 | 23.4k | case M_PROPERTY_KEY_ACTION: { |
4431 | 23.4k | mp_assert(node); |
4432 | | |
4433 | | // If we're operating on an array, sub-object access is handled by m_property_read_list |
4434 | 23.4k | if (node->format == MPV_FORMAT_NODE_ARRAY) |
4435 | 0 | return m_property_read_list(action, arg, node->u.list->num, &do_list_udata, ctx); |
4436 | | |
4437 | | // Sub-objects only make sense for arrays and maps |
4438 | 23.4k | if (node->format != MPV_FORMAT_NODE_MAP) |
4439 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
4440 | | |
4441 | 23.4k | struct m_property_action_arg *act = arg; |
4442 | | |
4443 | | // See if the next layer down will also be a sub-object access |
4444 | 23.4k | bstr key; |
4445 | 23.4k | const char *rem; |
4446 | 23.4k | bool has_split = m_property_split_path(act->key, &key, &rem); |
4447 | | |
4448 | 23.4k | if (!has_split && act->action == M_PROPERTY_DELETE) { |
4449 | | // Find the object we're looking for |
4450 | 0 | int i; |
4451 | 0 | for (i = 0; i < node->u.list->num; i++) { |
4452 | 0 | if (bstr_equals0(key, node->u.list->keys[i])) |
4453 | 0 | break; |
4454 | 0 | } |
4455 | | |
4456 | | // Return if it didn't exist |
4457 | 0 | if (i == node->u.list->num) |
4458 | 0 | return M_PROPERTY_UNKNOWN; |
4459 | | |
4460 | | // Delete the item |
4461 | 0 | m_option_free(&udata_type, &node->u.list->values[i]); |
4462 | 0 | talloc_free(node->u.list->keys[i]); |
4463 | | |
4464 | | // Shift the remaining items back |
4465 | 0 | for (i++; i < node->u.list->num; i++) { |
4466 | 0 | node->u.list->values[i - 1] = node->u.list->values[i]; |
4467 | 0 | node->u.list->keys[i - 1] = node->u.list->keys[i]; |
4468 | 0 | } |
4469 | | |
4470 | | // And decrement the count |
4471 | 0 | node->u.list->num--; |
4472 | |
|
4473 | 0 | return M_PROPERTY_OK; |
4474 | 0 | } |
4475 | | |
4476 | | // Look up the next level down |
4477 | 23.4k | mpv_node *cnode = node_map_bget(node, key); |
4478 | | |
4479 | 23.4k | if (!cnode) { |
4480 | 23.4k | switch (act->action) { |
4481 | 0 | case M_PROPERTY_SET: |
4482 | 20.9k | case M_PROPERTY_SET_NODE: { |
4483 | | // If we're doing a set, and the key doesn't exist, create it. |
4484 | | // If we're recursing another layer down, make it an empty map; |
4485 | | // otherwise, make it NONE, since we'll be overwriting it at the next level. |
4486 | 20.9k | cnode = node_map_badd(node, key, has_split ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NONE); |
4487 | 20.9k | if (!cnode) |
4488 | 0 | return M_PROPERTY_ERROR; |
4489 | 20.9k | break; |
4490 | 20.9k | case M_PROPERTY_GET_TYPE: |
4491 | | // Nonexistent keys have type NODE, so they can be overwritten |
4492 | 1.11k | *(struct m_option *)act->arg = udata_type; |
4493 | 1.11k | return M_PROPERTY_OK; |
4494 | 1.34k | default: |
4495 | | // We can't perform any other options on nonexistent keys |
4496 | 1.34k | return M_PROPERTY_UNKNOWN; |
4497 | 20.9k | } |
4498 | 23.4k | } |
4499 | 23.4k | } |
4500 | | |
4501 | 20.9k | struct udata_ctx nctx = *ctx; |
4502 | 20.9k | nctx.node = cnode; |
4503 | 20.9k | nctx.ta_parent = node_get_alloc(node); |
4504 | | |
4505 | | // If we're going down another level, set up a new key-action. |
4506 | 20.9k | if (has_split) { |
4507 | 20.0k | struct m_property_action_arg sub_act = { |
4508 | 20.0k | .key = rem, |
4509 | 20.0k | .action = act->action, |
4510 | 20.0k | .arg = act->arg, |
4511 | 20.0k | }; |
4512 | | |
4513 | 20.0k | if (nctx.depth++ > 100) |
4514 | 30 | return M_PROPERTY_ERROR; |
4515 | 20.0k | return do_op_udata(&nctx, M_PROPERTY_KEY_ACTION, &sub_act); |
4516 | 20.0k | } else { |
4517 | 890 | return do_op_udata(&nctx, act->action, act->arg); |
4518 | 890 | } |
4519 | 20.9k | } |
4520 | 25.5k | } |
4521 | 424 | return M_PROPERTY_NOT_IMPLEMENTED; |
4522 | 25.5k | } |
4523 | | |
4524 | | static int do_list_udata(int item, int action, void *arg, void *ctx) |
4525 | 0 | { |
4526 | 0 | struct udata_ctx nctx = *(struct udata_ctx*)ctx; |
4527 | 0 | nctx.node = &nctx.node->u.list->values[item]; |
4528 | 0 | nctx.ta_parent = nctx.node->u.list; |
4529 | 0 | nctx.depth = 0; |
4530 | |
|
4531 | 0 | return do_op_udata(&nctx, action, arg); |
4532 | 0 | } |
4533 | | |
4534 | | static int mp_property_udata(void *ctx, struct m_property *prop, |
4535 | | int action, void *arg) |
4536 | 4.65k | { |
4537 | | // The root of udata is a shared map; don't allow overwriting |
4538 | | // or deleting the whole thing |
4539 | 4.65k | if (action == M_PROPERTY_SET || action == M_PROPERTY_SET_NODE || |
4540 | 4.64k | action == M_PROPERTY_DELETE) |
4541 | 7 | return M_PROPERTY_NOT_IMPLEMENTED; |
4542 | | |
4543 | 4.64k | char *path = NULL; |
4544 | 4.64k | if (action == M_PROPERTY_KEY_ACTION) { |
4545 | 3.37k | struct m_property_action_arg *act = arg; |
4546 | 3.37k | if (act->action == M_PROPERTY_SET || act->action == M_PROPERTY_SET_NODE) |
4547 | 920 | path = talloc_asprintf(NULL, "%s/%s", prop->name, act->key); |
4548 | 3.37k | } |
4549 | | |
4550 | 4.64k | struct MPContext *mpctx = ctx; |
4551 | 4.64k | struct udata_ctx nctx = { |
4552 | 4.64k | .mpctx = mpctx, |
4553 | 4.64k | .path = path, |
4554 | 4.64k | .node = &mpctx->command_ctx->udata, |
4555 | 4.64k | .ta_parent = mpctx->command_ctx, |
4556 | 4.64k | }; |
4557 | | |
4558 | 4.64k | int ret = do_op_udata(&nctx, action, arg); |
4559 | | |
4560 | 4.64k | talloc_free(path); |
4561 | | |
4562 | 4.64k | return ret; |
4563 | 4.65k | } |
4564 | | |
4565 | | static int mp_property_current_clipboard_backend(void *ctx, struct m_property *p, |
4566 | | int action, void *arg) |
4567 | 42 | { |
4568 | 42 | MPContext *mpctx = ctx; |
4569 | 42 | return m_property_strdup_ro(action, arg, mp_clipboard_get_backend_name(mpctx->clipboard)); |
4570 | 42 | } |
4571 | | |
4572 | | static int get_clipboard(struct MPContext *mpctx, void *arg, |
4573 | | struct clipboard_access_params *params) |
4574 | 760 | { |
4575 | 760 | struct clipboard_data data; |
4576 | 760 | void *tmp = talloc_new(NULL); |
4577 | | |
4578 | 760 | int ret = mp_clipboard_get_data(mpctx->clipboard, params, &data, tmp); |
4579 | 760 | if (ret != CLIPBOARD_SUCCESS) { |
4580 | 760 | talloc_free(tmp); |
4581 | 760 | return ret == CLIPBOARD_UNAVAILABLE ? M_PROPERTY_UNAVAILABLE : M_PROPERTY_ERROR; |
4582 | 760 | } |
4583 | | |
4584 | 0 | switch (data.type) { |
4585 | 0 | case CLIPBOARD_DATA_TEXT: |
4586 | 0 | *(char **)arg = talloc_steal(NULL, data.u.text); |
4587 | 0 | talloc_free(tmp); |
4588 | 0 | return M_PROPERTY_OK; |
4589 | 0 | default: |
4590 | 0 | talloc_free(tmp); |
4591 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
4592 | 0 | } |
4593 | 0 | } |
4594 | | |
4595 | | static int set_clipboard(struct MPContext *mpctx, void *arg, |
4596 | | struct clipboard_access_params *params) |
4597 | 0 | { |
4598 | 0 | struct clipboard_data data = {0}; |
4599 | |
|
4600 | 0 | switch (params->type) { |
4601 | 0 | case CLIPBOARD_DATA_TEXT: |
4602 | 0 | data.type = CLIPBOARD_DATA_TEXT; |
4603 | 0 | data.u.text = *(char **)arg; |
4604 | 0 | break; |
4605 | 0 | default: |
4606 | 0 | return M_PROPERTY_NOT_IMPLEMENTED; |
4607 | 0 | } |
4608 | | |
4609 | 0 | int ret = mp_clipboard_set_data(mpctx->clipboard, params, &data); |
4610 | 0 | if (ret == CLIPBOARD_SUCCESS) |
4611 | 0 | return M_PROPERTY_OK; |
4612 | 0 | return ret == CLIPBOARD_UNAVAILABLE ? M_PROPERTY_UNAVAILABLE : M_PROPERTY_ERROR; |
4613 | 0 | } |
4614 | | |
4615 | | static int mp_property_clipboard(void *ctx, struct m_property *prop, |
4616 | | int action, void *arg) |
4617 | 2.17k | { |
4618 | 2.17k | struct MPContext *mpctx = ctx; |
4619 | 2.17k | struct clipboard_access_params params = { |
4620 | 2.17k | .type = CLIPBOARD_DATA_TEXT, |
4621 | 2.17k | .target = CLIPBOARD_TARGET_CLIPBOARD, |
4622 | 2.17k | }; |
4623 | | |
4624 | 2.17k | switch (action) { |
4625 | 349 | case M_PROPERTY_GET_TYPE: |
4626 | 349 | *(struct m_option *)arg = (struct m_option){ |
4627 | 349 | .type = CONF_TYPE_NODE, |
4628 | 349 | }; |
4629 | 349 | return M_PROPERTY_OK; |
4630 | 345 | case M_PROPERTY_GET: |
4631 | 345 | case M_PROPERTY_GET_NODE: { |
4632 | 345 | struct mpv_node node; |
4633 | 345 | node_init(&node, MPV_FORMAT_NODE_MAP, NULL); |
4634 | 345 | char *data = NULL; |
4635 | 345 | if (get_clipboard(mpctx, &data, ¶ms) == M_PROPERTY_OK) { |
4636 | 0 | node_map_add_string(&node, "text", data); |
4637 | 0 | talloc_free(data); |
4638 | 0 | } |
4639 | 345 | params.target = CLIPBOARD_TARGET_PRIMARY_SELECTION; |
4640 | 345 | data = NULL; |
4641 | 345 | if (get_clipboard(mpctx, &data, ¶ms) == M_PROPERTY_OK) { |
4642 | 0 | node_map_add_string(&node, "text-primary", data); |
4643 | 0 | talloc_free(data); |
4644 | 0 | } |
4645 | 345 | *(struct mpv_node *)arg = node; |
4646 | 345 | return M_PROPERTY_OK; |
4647 | 345 | } |
4648 | 779 | case M_PROPERTY_KEY_ACTION: { |
4649 | 779 | struct m_property_action_arg *act = arg; |
4650 | 779 | const char *key = act->key; |
4651 | | |
4652 | 779 | if (!strcmp(key, "text-primary")) |
4653 | 81 | params.target = CLIPBOARD_TARGET_PRIMARY_SELECTION; |
4654 | 698 | else if (strcmp(key, "text")) |
4655 | 482 | return M_PROPERTY_UNKNOWN; |
4656 | | |
4657 | 297 | switch (act->action) { |
4658 | 76 | case M_PROPERTY_GET_TYPE: |
4659 | 76 | switch (params.type) { |
4660 | 76 | case CLIPBOARD_DATA_TEXT: |
4661 | 76 | *(struct m_option *)act->arg = (struct m_option){ |
4662 | 76 | .type = CONF_TYPE_STRING, |
4663 | 76 | }; |
4664 | 76 | return M_PROPERTY_OK; |
4665 | 0 | default: |
4666 | 0 | return M_PROPERTY_UNKNOWN; |
4667 | 76 | } |
4668 | 70 | case M_PROPERTY_GET: |
4669 | 70 | return get_clipboard(mpctx, act->arg, ¶ms); |
4670 | 0 | case M_PROPERTY_SET: |
4671 | 0 | return set_clipboard(mpctx, act->arg, ¶ms); |
4672 | 151 | default: |
4673 | 151 | return M_PROPERTY_NOT_IMPLEMENTED; |
4674 | 297 | } |
4675 | 297 | } |
4676 | 697 | default: |
4677 | 697 | return M_PROPERTY_NOT_IMPLEMENTED; |
4678 | 2.17k | } |
4679 | 2.17k | } |
4680 | | |
4681 | | // Redirect a property name to another |
4682 | | #define M_PROPERTY_ALIAS(name, real_property) \ |
4683 | | {(name), mp_property_alias, .priv = (real_property)} |
4684 | | |
4685 | | #define M_PROPERTY_DEPRECATED_ALIAS(name, real_property) \ |
4686 | | {(name), mp_property_deprecated_alias, .priv = (real_property)} |
4687 | | |
4688 | | // Base list of properties. This does not include option-mapped properties. |
4689 | | static const struct m_property mp_properties_base[] = { |
4690 | | // General |
4691 | | {"pid", mp_property_pid}, |
4692 | | {"speed", mp_property_playback_speed}, |
4693 | | {"pitch", mp_property_playback_pitch}, |
4694 | | {"audio-speed-correction", mp_property_av_speed_correction, .priv = "a"}, |
4695 | | {"video-speed-correction", mp_property_av_speed_correction, .priv = "v"}, |
4696 | | {"display-sync-active", mp_property_display_sync_active}, |
4697 | | {"filename", mp_property_filename}, |
4698 | | {"env", mp_property_env}, |
4699 | | {"stream-open-filename", mp_property_stream_open_filename}, |
4700 | | {"file-size", mp_property_file_size}, |
4701 | | {"path", mp_property_path}, |
4702 | | {"media-title", mp_property_media_title}, |
4703 | | {"stream-path", mp_property_stream_path}, |
4704 | | {"current-demuxer", mp_property_demuxer}, |
4705 | | {"file-format", mp_property_file_format}, |
4706 | | {"stream-pos", mp_property_stream_pos}, |
4707 | | {"stream-end", mp_property_stream_end}, |
4708 | | {"duration", mp_property_duration}, |
4709 | | {"avsync", mp_property_avsync}, |
4710 | | {"total-avsync-change", mp_property_total_avsync_change}, |
4711 | | {"mistimed-frame-count", mp_property_mistimed_frame_count}, |
4712 | | {"vsync-ratio", mp_property_vsync_ratio}, |
4713 | | {"display-width", mp_property_display_resolution}, |
4714 | | {"display-height", mp_property_display_resolution}, |
4715 | | {"decoder-frame-drop-count", mp_property_frame_drop_dec}, |
4716 | | {"frame-drop-count", mp_property_frame_drop_vo}, |
4717 | | {"vo-delayed-frame-count", mp_property_vo_delayed_frame_count}, |
4718 | | {"percent-pos", mp_property_percent_pos}, |
4719 | | {"time-pos", mp_property_time_pos}, |
4720 | | {"time-remaining", mp_property_remaining}, |
4721 | | {"audio-pts", mp_property_audio_pts}, |
4722 | | {"playtime-remaining", mp_property_playtime_remaining}, |
4723 | | M_PROPERTY_ALIAS("playback-time", "time-pos"), |
4724 | | {"remaining-file-loops", mp_property_remaining_file_loops}, |
4725 | | {"remaining-ab-loops", mp_property_remaining_ab_loops}, |
4726 | | {"chapter", mp_property_chapter}, |
4727 | | {"edition", mp_property_edition}, |
4728 | | {"current-edition", mp_property_current_edition}, |
4729 | | {"disc-menu-active", mp_property_disc_menu_active}, |
4730 | | {"chapters", mp_property_chapters}, |
4731 | | {"editions", mp_property_editions}, |
4732 | | {"metadata", mp_property_metadata}, |
4733 | | {"filtered-metadata", mp_property_filtered_metadata}, |
4734 | | {"chapter-metadata", mp_property_chapter_metadata}, |
4735 | | {"vf-metadata", mp_property_filter_metadata, .priv = "vf"}, |
4736 | | {"af-metadata", mp_property_filter_metadata, .priv = "af"}, |
4737 | | {"core-idle", mp_property_core_idle}, |
4738 | | {"eof-reached", mp_property_eof_reached}, |
4739 | | {"seeking", mp_property_seeking}, |
4740 | | {"playback-abort", mp_property_playback_abort}, |
4741 | | {"cache-speed", mp_property_cache_speed}, |
4742 | | {"demuxer-cache-duration", mp_property_demuxer_cache_duration}, |
4743 | | {"demuxer-cache-time", mp_property_demuxer_cache_time}, |
4744 | | {"demuxer-cache-idle", mp_property_demuxer_cache_idle}, |
4745 | | {"demuxer-start-time", mp_property_demuxer_start_time}, |
4746 | | {"demuxer-cache-state", mp_property_demuxer_cache_state}, |
4747 | | {"cache-buffering-state", mp_property_cache_buffering}, |
4748 | | {"paused-for-cache", mp_property_paused_for_cache}, |
4749 | | {"demuxer-via-network", mp_property_demuxer_is_network}, |
4750 | | {"clock", mp_property_clock}, |
4751 | | {"seekable", mp_property_seekable}, |
4752 | | {"partially-seekable", mp_property_partially_seekable}, |
4753 | | {"deinterlace-active", mp_property_deinterlace}, |
4754 | | {"idle-active", mp_property_idle}, |
4755 | | {"window-id", mp_property_window_id}, |
4756 | | {"display-swapchain", mp_property_vo_display_swapchain}, |
4757 | | {"player-operation-mode", mp_property_player_operation_mode}, |
4758 | | |
4759 | | {"chapter-list", mp_property_list_chapters}, |
4760 | | {"track-list", mp_property_list_tracks}, |
4761 | | {"current-tracks", mp_property_current_tracks}, |
4762 | | {"edition-list", mp_property_list_editions}, |
4763 | | |
4764 | | {"playlist", mp_property_playlist}, |
4765 | | {"playlist-path", mp_property_playlist_path}, |
4766 | | {"playlist-pos", mp_property_playlist_pos}, |
4767 | | {"playlist-pos-1", mp_property_playlist_pos_1}, |
4768 | | {"playlist-current-pos", mp_property_playlist_current_pos}, |
4769 | | {"playlist-playing-pos", mp_property_playlist_playing_pos}, |
4770 | | M_PROPERTY_ALIAS("playlist-count", "playlist/count"), |
4771 | | |
4772 | | // Audio |
4773 | | {"mixer-active", mp_property_mixer_active}, |
4774 | | {"volume", mp_property_volume}, |
4775 | | {"volume-gain", mp_property_volume_gain}, |
4776 | | {"ao-volume", mp_property_ao_volume}, |
4777 | | {"ao-mute", mp_property_ao_mute}, |
4778 | | {"audio-delay", mp_property_audio_delay}, |
4779 | | M_PROPERTY_ALIAS("audio-codec-name", "current-tracks/audio/codec"), |
4780 | | M_PROPERTY_ALIAS("audio-codec", "current-tracks/audio/codec-desc"), |
4781 | | {"audio-params", mp_property_audio_params}, |
4782 | | {"audio-out-params", mp_property_audio_out_params}, |
4783 | | {"aid", mp_property_switch_track, .priv = (void *)(const int[]){0, STREAM_AUDIO}}, |
4784 | | {"audio-device", mp_property_audio_device}, |
4785 | | {"audio-device-list", mp_property_audio_devices}, |
4786 | | {"current-ao", mp_property_ao}, |
4787 | | |
4788 | | // Video |
4789 | | {"video-target-params", mp_property_tgt_imgparams}, |
4790 | | {"video-out-params", mp_property_vo_imgparams}, |
4791 | | {"video-dec-params", mp_property_dec_imgparams}, |
4792 | | {"video-params", mp_property_vd_imgparams}, |
4793 | | {"video-frame-info", mp_property_video_frame_info}, |
4794 | | M_PROPERTY_ALIAS("video-format", "current-tracks/video/codec"), |
4795 | | M_PROPERTY_ALIAS("video-codec", "current-tracks/video/codec-desc"), |
4796 | | M_PROPERTY_ALIAS("dwidth", "video-out-params/dw"), |
4797 | | M_PROPERTY_ALIAS("dheight", "video-out-params/dh"), |
4798 | | M_PROPERTY_ALIAS("width", "video-params/w"), |
4799 | | M_PROPERTY_ALIAS("height", "video-params/h"), |
4800 | | {"current-window-scale", mp_property_current_window_scale}, |
4801 | | {"vo-configured", mp_property_vo_configured}, |
4802 | | {"vo-passes", mp_property_vo_passes}, |
4803 | | {"perf-info", mp_property_perf_info}, |
4804 | | {"current-vo", mp_property_vo}, |
4805 | | {"current-gpu-context", mp_property_gpu_context}, |
4806 | | {"container-fps", mp_property_fps}, |
4807 | | {"estimated-vf-fps", mp_property_vf_fps}, |
4808 | | {"video-aspect-override", mp_property_video_aspect_override}, |
4809 | | {"vid", mp_property_switch_track, .priv = (void *)(const int[]){0, STREAM_VIDEO}}, |
4810 | | {"hwdec-current", mp_property_hwdec_current}, |
4811 | | {"hwdec-interop", mp_property_hwdec_interop}, |
4812 | | |
4813 | | {"estimated-frame-count", mp_property_frame_count}, |
4814 | | {"estimated-frame-number", mp_property_frame_number}, |
4815 | | |
4816 | | {"osd-dimensions", mp_property_osd_dim}, |
4817 | | M_PROPERTY_ALIAS("osd-width", "osd-dimensions/w"), |
4818 | | M_PROPERTY_ALIAS("osd-height", "osd-dimensions/h"), |
4819 | | M_PROPERTY_ALIAS("osd-par", "osd-dimensions/par"), |
4820 | | |
4821 | | {"osd-sym-cc", mp_property_osd_sym}, |
4822 | | {"osd-ass-cc", mp_property_osd_ass}, |
4823 | | |
4824 | | {"term-clip-cc", mp_property_term_clip}, |
4825 | | |
4826 | | {"mouse-pos", mp_property_mouse_pos}, |
4827 | | {"touch-pos", mp_property_touch_pos}, |
4828 | | {"tablet-pos", mp_property_tablet_pos}, |
4829 | | {"dropped-files", mp_property_dropped_files}, |
4830 | | |
4831 | | // Subs |
4832 | | {"sid", mp_property_switch_track, .priv = (void *)(const int[]){0, STREAM_SUB}}, |
4833 | | {"secondary-sid", mp_property_switch_track, |
4834 | | .priv = (void *)(const int[]){1, STREAM_SUB}}, |
4835 | | {"sub-delay", mp_property_sub_delay, .priv = (void *)&(const int){0}}, |
4836 | | {"secondary-sub-delay", mp_property_sub_delay, |
4837 | | .priv = (void *)&(const int){1}}, |
4838 | | {"sub-speed", mp_property_sub_speed}, |
4839 | | {"sub-pos", mp_property_sub_pos, .priv = (void *)&(const int){0}}, |
4840 | | {"secondary-sub-pos", mp_property_sub_pos, |
4841 | | .priv = (void *)&(const int){1}}, |
4842 | | {"sub-ass-extradata", mp_property_sub_ass_extradata}, |
4843 | | {"sub-text", mp_property_sub_text, |
4844 | | .priv = (void *)&(const int[]){0, SD_TEXT_TYPE_PLAIN}}, |
4845 | | {"secondary-sub-text", mp_property_sub_text, |
4846 | | .priv = (void *)&(const int[]){1, SD_TEXT_TYPE_PLAIN}}, |
4847 | | M_PROPERTY_DEPRECATED_ALIAS("sub-text-ass", "sub-text/ass"), |
4848 | | {"sub-start", mp_property_sub_start, |
4849 | | .priv = (void *)&(const int){0}}, |
4850 | | {"secondary-sub-start", mp_property_sub_start, |
4851 | | .priv = (void *)&(const int){1}}, |
4852 | | {"sub-end", mp_property_sub_end, |
4853 | | .priv = (void *)&(const int){0}}, |
4854 | | {"secondary-sub-end", mp_property_sub_end, |
4855 | | .priv = (void *)&(const int){1}}, |
4856 | | {"sub-lines", mp_property_sub_lines, |
4857 | | .priv = (void *)&(const int){0}}, |
4858 | | {"secondary-sub-lines", mp_property_sub_lines, |
4859 | | .priv = (void *)&(const int){1}}, |
4860 | | |
4861 | | {"vf", mp_property_vf}, |
4862 | | {"af", mp_property_af}, |
4863 | | |
4864 | | {"ab-loop-a", mp_property_ab_loop}, |
4865 | | {"ab-loop-b", mp_property_ab_loop}, |
4866 | | |
4867 | | {"video-bitrate", mp_property_packet_bitrate, .priv = (void *)&(const int){STREAM_VIDEO}}, |
4868 | | {"audio-bitrate", mp_property_packet_bitrate, .priv = (void *)&(const int){STREAM_AUDIO}}, |
4869 | | {"sub-bitrate", mp_property_packet_bitrate, .priv = (void *)&(const int){STREAM_SUB}}, |
4870 | | |
4871 | | {"focused", mp_property_focused}, |
4872 | | {"display-names", mp_property_display_names}, |
4873 | | {"display-fps", mp_property_display_fps}, |
4874 | | {"estimated-display-fps", mp_property_estimated_display_fps}, |
4875 | | {"vsync-jitter", mp_property_vsync_jitter}, |
4876 | | {"display-hidpi-scale", mp_property_hidpi_scale}, |
4877 | | {"ambient-light", mp_property_ambient_light}, |
4878 | | |
4879 | | {"working-directory", mp_property_cwd}, |
4880 | | {"current-watch-later-dir", mp_property_current_watch_later_dir}, |
4881 | | |
4882 | | {"protocol-list", mp_property_protocols}, |
4883 | | {"decoder-list", mp_property_decoders}, |
4884 | | {"encoder-list", mp_property_encoders}, |
4885 | | {"demuxer-lavf-list", mp_property_lavf_demuxers}, |
4886 | | {"input-key-list", mp_property_keylist}, |
4887 | | |
4888 | | {"mpv-version", mp_property_version}, |
4889 | | {"mpv-configuration", mp_property_configuration}, |
4890 | | {"ffmpeg-version", mp_property_ffmpeg}, |
4891 | | {"libass-version", mp_property_libass_version}, |
4892 | | {"libplacebo-version", mp_property_libplacebo_version}, |
4893 | | {"subrandr-version", mp_property_subrandr_version}, |
4894 | | {"platform", mp_property_platform}, |
4895 | | |
4896 | | {"options", mp_property_options}, |
4897 | | {"file-local-options", mp_property_local_options}, |
4898 | | {"option-info", mp_property_option_info}, |
4899 | | {"property-list", mp_property_list}, |
4900 | | {"profile-list", mp_profile_list}, |
4901 | | {"command-list", mp_property_commands}, |
4902 | | {"input-bindings", mp_property_bindings}, |
4903 | | |
4904 | | {"menu-data", mp_property_mdata, .is_noisy = true}, |
4905 | | {"default-menu", mp_property_default_menu}, |
4906 | | |
4907 | | {"user-data", mp_property_udata}, |
4908 | | {"term-size", mp_property_term_size}, |
4909 | | |
4910 | | {"clipboard", mp_property_clipboard}, |
4911 | | {"current-clipboard-backend", mp_property_current_clipboard_backend}, |
4912 | | |
4913 | | M_PROPERTY_ALIAS("video", "vid"), |
4914 | | M_PROPERTY_ALIAS("audio", "aid"), |
4915 | | M_PROPERTY_ALIAS("sub", "sid"), |
4916 | | |
4917 | | // compatibility |
4918 | | M_PROPERTY_ALIAS("colormatrix", "video-params/colormatrix"), |
4919 | | M_PROPERTY_ALIAS("colormatrix-input-range", "video-params/colorlevels"), |
4920 | | M_PROPERTY_ALIAS("colormatrix-primaries", "video-params/primaries"), |
4921 | | M_PROPERTY_ALIAS("colormatrix-gamma", "video-params/gamma"), |
4922 | | |
4923 | | M_PROPERTY_DEPRECATED_ALIAS("sub-forced-only-cur", "sub-forced-events-only"), |
4924 | | }; |
4925 | | |
4926 | | // Each entry describes which properties an event (possibly) changes. |
4927 | | #define E(x, ...) [x] = (const char*const[]){__VA_ARGS__, NULL} |
4928 | | static const char *const *const mp_event_property_change[] = { |
4929 | | E(MPV_EVENT_START_FILE, "*"), |
4930 | | E(MPV_EVENT_END_FILE, "*"), |
4931 | | E(MPV_EVENT_FILE_LOADED, "*"), |
4932 | | E(MP_EVENT_CHANGE_ALL, "*"), |
4933 | | E(MP_EVENT_TRACKS_CHANGED, "track-list", "current-tracks"), |
4934 | | E(MP_EVENT_TRACK_SWITCHED, "track-list", "current-tracks"), |
4935 | | E(MPV_EVENT_IDLE, "*"), |
4936 | | E(MPV_EVENT_TICK, "time-pos", "audio-pts", "stream-pos", "avsync", |
4937 | | "percent-pos", "time-remaining", "playtime-remaining", "playback-time", |
4938 | | "estimated-vf-fps", "total-avsync-change", "audio-speed-correction", |
4939 | | "video-speed-correction", "vo-delayed-frame-count", "mistimed-frame-count", |
4940 | | "vsync-ratio", "estimated-display-fps", "vsync-jitter", "sub-text", |
4941 | | "secondary-sub-text", "audio-bitrate", "video-bitrate", "sub-bitrate", |
4942 | | "decoder-frame-drop-count", "frame-drop-count", "video-frame-info", |
4943 | | "vf-metadata", "af-metadata", "sub-start", "sub-end", "secondary-sub-start", |
4944 | | "secondary-sub-end", "sub-lines", "secondary-sub-lines", "video-out-params", |
4945 | | "video-dec-params", "video-params", "deinterlace-active", "video-target-params"), |
4946 | | E(MP_EVENT_DURATION_UPDATE, "duration"), |
4947 | | E(MPV_EVENT_VIDEO_RECONFIG, "video-out-params", "video-params", |
4948 | | "video-format", "video-codec", "video-bitrate", "dwidth", "dheight", |
4949 | | "width", "height", "container-fps", "aspect", "aspect-name", "vo-configured", "current-vo", |
4950 | | "video-dec-params", "osd-dimensions", "hwdec", "hwdec-current", "hwdec-interop", |
4951 | | "window-id", "track-list", "current-tracks"), |
4952 | | E(MPV_EVENT_AUDIO_RECONFIG, "audio-format", "audio-codec", "audio-bitrate", |
4953 | | "samplerate", "channels", "audio", "volume", "volume-gain", "mute", |
4954 | | "current-ao", "audio-codec-name", "audio-params", "track-list", "current-tracks", |
4955 | | "audio-out-params", "volume-max", "volume-gain-min", "volume-gain-max", "mixer-active"), |
4956 | | E(MPV_EVENT_SEEK, "seeking", "core-idle", "eof-reached"), |
4957 | | E(MPV_EVENT_PLAYBACK_RESTART, "seeking", "core-idle", "eof-reached"), |
4958 | | E(MP_EVENT_METADATA_UPDATE, "metadata", "filtered-metadata", "media-title"), |
4959 | | E(MP_EVENT_CHAPTER_CHANGE, "chapter", "chapter-metadata"), |
4960 | | E(MP_EVENT_CACHE_UPDATE, |
4961 | | "demuxer-cache-duration", "demuxer-cache-idle", "paused-for-cache", |
4962 | | "demuxer-cache-time", "cache-buffering-state", "cache-speed", |
4963 | | "demuxer-cache-state"), |
4964 | | E(MP_EVENT_WIN_RESIZE, "current-window-scale", "osd-width", "osd-height", |
4965 | | "osd-par", "osd-dimensions"), |
4966 | | E(MP_EVENT_WIN_STATE, "display-names", "display-fps", "display-width", |
4967 | | "display-height"), |
4968 | | E(MP_EVENT_WIN_STATE2, "display-hidpi-scale"), |
4969 | | E(MP_EVENT_FOCUS, "focused"), |
4970 | | E(MP_EVENT_AMBIENT_LIGHTING_CHANGED, "ambient-light"), |
4971 | | E(MP_EVENT_CHANGE_PLAYLIST, "playlist", "playlist-pos", "playlist-pos-1", |
4972 | | "playlist-count", "playlist/count", "playlist-current-pos", |
4973 | | "playlist-playing-pos"), |
4974 | | E(MP_EVENT_INPUT_PROCESSED, "mouse-pos", "touch-pos", "tablet-pos", "dropped-files"), |
4975 | | E(MP_EVENT_CORE_IDLE, "core-idle", "eof-reached"), |
4976 | | }; |
4977 | | #undef E |
4978 | | |
4979 | | // If there is no prefix, return length+1 (avoids matching full name as prefix). |
4980 | | static int prefix_len(const char *p) |
4981 | 1.41G | { |
4982 | 1.41G | const char *end = strchr(p, '/'); |
4983 | 1.41G | return end ? end - p : strlen(p) + 1; |
4984 | 1.41G | } |
4985 | | |
4986 | | static bool match_property(const char *a, const char *b) |
4987 | 710M | { |
4988 | 710M | if (strcmp(a, "*") == 0) |
4989 | 1.98M | return true; |
4990 | | // Give options and properties the same ID each, so change notifications |
4991 | | // work both way. |
4992 | 708M | if (strncmp(a, "options/", 8) == 0) |
4993 | 0 | a += 8; |
4994 | 708M | if (strncmp(b, "options/", 8) == 0) |
4995 | 10.0k | b += 8; |
4996 | 708M | int len_a = prefix_len(a); |
4997 | 708M | int len_b = prefix_len(b); |
4998 | 708M | return strncmp(a, b, MPMIN(len_a, len_b)) == 0; |
4999 | 710M | } |
5000 | | |
5001 | | // Return a bitset of events which change the property. |
5002 | | uint64_t mp_get_property_event_mask(const char *name) |
5003 | 396k | { |
5004 | 396k | uint64_t mask = 0; |
5005 | 17.0M | for (int n = 0; n < MP_ARRAY_SIZE(mp_event_property_change); n++) { |
5006 | 16.6M | const char *const *const list = mp_event_property_change[n]; |
5007 | 68.9M | for (int i = 0; list && list[i]; i++) { |
5008 | 52.2M | if (match_property(list[i], name)) |
5009 | 1.98M | mask |= 1ULL << n; |
5010 | 52.2M | } |
5011 | 16.6M | } |
5012 | 396k | return mask; |
5013 | 396k | } |
5014 | | |
5015 | | // Return an ID for the property. It might not be unique, but is good enough |
5016 | | // for property change handling. Return -1 if property unknown. |
5017 | | int mp_get_property_id(struct MPContext *mpctx, const char *name) |
5018 | 1.06M | { |
5019 | 1.06M | struct command_ctx *ctx = mpctx->command_ctx; |
5020 | 657M | for (int n = 0; n < ctx->properties.num; n++) { |
5021 | 657M | if (match_property(ctx->properties.props[n].name, name)) |
5022 | 1.05M | return n; |
5023 | 657M | } |
5024 | 983 | return -1; |
5025 | 1.06M | } |
5026 | | |
5027 | | static bool is_property_set(int action, void *val) |
5028 | 41.1k | { |
5029 | 41.1k | switch (action) { |
5030 | 4.27k | case M_PROPERTY_SET: |
5031 | 4.27k | case M_PROPERTY_SWITCH: |
5032 | 4.27k | case M_PROPERTY_SET_STRING: |
5033 | 27.4k | case M_PROPERTY_SET_NODE: |
5034 | 27.4k | case M_PROPERTY_MULTIPLY: |
5035 | 27.4k | return true; |
5036 | 364 | case M_PROPERTY_KEY_ACTION: { |
5037 | 364 | struct m_property_action_arg *key = val; |
5038 | 364 | return is_property_set(key->action, key->arg); |
5039 | 27.4k | } |
5040 | 13.3k | default: |
5041 | 13.3k | return false; |
5042 | 41.1k | } |
5043 | 41.1k | } |
5044 | | |
5045 | | int mp_property_do(const char *name, int action, void *val, |
5046 | | struct MPContext *ctx) |
5047 | 41.3k | { |
5048 | 41.3k | struct command_ctx *cmd = ctx->command_ctx; |
5049 | 41.3k | int r = m_property_do(ctx->log, &cmd->properties, name, action, val, ctx); |
5050 | | |
5051 | 41.3k | if (mp_msg_test(ctx->log, MSGL_V) && is_property_set(action, val)) { |
5052 | 27.4k | struct m_property *property = m_property_list_find(&cmd->properties, name); |
5053 | 27.4k | if (property && property->is_noisy && !mp_msg_test(ctx->log, MSGL_TRACE)) |
5054 | 0 | return r; |
5055 | | |
5056 | 27.4k | struct m_option option_type = {0}; |
5057 | 27.4k | void *data = val; |
5058 | 27.4k | switch (action) { |
5059 | 23.2k | case M_PROPERTY_SET_NODE: |
5060 | 23.2k | option_type.type = &m_option_type_node; |
5061 | 23.2k | break; |
5062 | 0 | case M_PROPERTY_SET_STRING: |
5063 | 0 | option_type.type = &m_option_type_string; |
5064 | 0 | data = &val; |
5065 | 0 | break; |
5066 | 27.4k | } |
5067 | 27.4k | char *t = option_type.type ? m_option_print(&option_type, data) : NULL; |
5068 | 27.4k | MP_VERBOSE(ctx, "Set property: %s%s%s -> %d\n", |
5069 | 27.4k | name, t ? "=" : "", t ? t : "", r); |
5070 | 27.4k | talloc_free(t); |
5071 | 27.4k | } |
5072 | 41.3k | return r; |
5073 | 41.3k | } |
5074 | | |
5075 | | char *mp_property_expand_string(struct MPContext *mpctx, const char *str) |
5076 | 567k | { |
5077 | 567k | struct command_ctx *ctx = mpctx->command_ctx; |
5078 | 567k | return m_properties_expand_string(&ctx->properties, str, mpctx, |
5079 | 567k | M_PROPERTY_PRINT); |
5080 | 567k | } |
5081 | | |
5082 | | // Before expanding properties, parse C-style escapes like "\n" |
5083 | | static char *expand_escaped_string(struct MPContext *mpctx, const char *str, |
5084 | | int print_action) |
5085 | 17.5k | { |
5086 | 17.5k | void *tmp = talloc_new(NULL); |
5087 | 17.5k | bstr strb = bstr0(str); |
5088 | 17.5k | bstr dst = {0}; |
5089 | 41.3k | while (strb.len) { |
5090 | 35.6k | if (!mp_append_escaped_string(tmp, &dst, &strb)) { |
5091 | 372 | talloc_free(tmp); |
5092 | 372 | return talloc_strdup(NULL, "(broken escape sequences)"); |
5093 | 372 | } |
5094 | | // pass " through literally |
5095 | 35.3k | if (!bstr_eatstart0(&strb, "\"")) |
5096 | 11.4k | break; |
5097 | 23.8k | bstr_xappend(tmp, &dst, bstr0("\"")); |
5098 | 23.8k | } |
5099 | 17.1k | struct command_ctx *ctx = mpctx->command_ctx; |
5100 | 17.1k | char *r = m_properties_expand_string(&ctx->properties, dst.start, mpctx, |
5101 | 17.1k | print_action); |
5102 | 17.1k | talloc_free(tmp); |
5103 | 17.1k | return r; |
5104 | 17.5k | } |
5105 | | |
5106 | | char *mp_property_expand_escaped_string(struct MPContext *mpctx, const char *str) |
5107 | 17.3k | { |
5108 | 17.3k | return expand_escaped_string(mpctx, str, M_PROPERTY_PRINT); |
5109 | 17.3k | } |
5110 | | |
5111 | | char *mp_expand_term_status_msg(struct MPContext *mpctx) |
5112 | 203 | { |
5113 | 203 | const char *str = mpctx->opts->status_msg; |
5114 | 203 | if (mpctx->video_out && mpctx->opts->video_osd) |
5115 | 0 | return mp_property_expand_escaped_string(mpctx, str); |
5116 | | |
5117 | 203 | char *msg = expand_escaped_string(mpctx, str, M_PROPERTY_PRINT_UNCUT); |
5118 | 203 | int w = -1, h = 24; |
5119 | 203 | terminal_get_size(&w, &h); |
5120 | 203 | char *end = skip_n_lines(msg, MPMAX(h, 1)); |
5121 | 203 | if (end) |
5122 | 0 | end[-1] = '\0'; |
5123 | 203 | return msg; |
5124 | 203 | } |
5125 | | |
5126 | | void property_print_help(struct MPContext *mpctx) |
5127 | 1 | { |
5128 | 1 | struct command_ctx *ctx = mpctx->command_ctx; |
5129 | 1 | m_properties_print_help_list(mpctx->log, &ctx->properties); |
5130 | 1 | } |
5131 | | |
5132 | | /* List of default ways to show a property on OSD. |
5133 | | * |
5134 | | * If osd_progbar is set, a bar showing the current position between min/max |
5135 | | * values of the property is shown. In this case osd_msg is only used for |
5136 | | * terminal output if there is no video; it'll be a label shown together with |
5137 | | * percentage. |
5138 | | */ |
5139 | | static const struct property_osd_display { |
5140 | | // property name |
5141 | | const char *name; |
5142 | | // name used on OSD |
5143 | | const char *osd_name; |
5144 | | // progressbar type |
5145 | | int osd_progbar; |
5146 | | // Needs special ways to display the new value (seeks are delayed) |
5147 | | int seek_msg, seek_bar; |
5148 | | // Show a marker thing on OSD bar. Ignored if osd_progbar==0. |
5149 | | float marker; |
5150 | | // Free-form message (if NULL, osd_name or the property name is used) |
5151 | | const char *msg; |
5152 | | } property_osd_display[] = { |
5153 | | // general |
5154 | | {"loop-playlist", "Loop playlist"}, |
5155 | | {"loop-file", "Loop current file"}, |
5156 | | {"chapter", |
5157 | | .seek_msg = OSD_SEEK_INFO_CHAPTER_TEXT, |
5158 | | .seek_bar = OSD_SEEK_INFO_BAR}, |
5159 | | {"hr-seek", "hr-seek"}, |
5160 | | {"speed", "Speed"}, |
5161 | | {"pitch", "Pitch"}, |
5162 | | {"clock", "Clock"}, |
5163 | | {"edition", "Edition"}, |
5164 | | // audio |
5165 | | {"volume", "Volume", |
5166 | | .msg = "Volume: ${?volume:${volume}% ${?mute==yes:(Muted)}}${!volume:${volume}}", |
5167 | | .osd_progbar = OSD_VOLUME, .marker = 100}, |
5168 | | {"volume-gain", "Volume gain", |
5169 | | .msg = "Volume gain: ${?volume-gain:${volume-gain} dB ${?mute==yes:(Muted)}}${!volume-gain:${volume-gain}}", |
5170 | | .osd_progbar = OSD_VOLUME, .marker = 0}, |
5171 | | {"ao-volume", "AO Volume", |
5172 | | .msg = "AO Volume: ${?ao-volume:${ao-volume}% ${?ao-mute==yes:(Muted)}}${!ao-volume:${ao-volume}}", |
5173 | | .osd_progbar = OSD_VOLUME, .marker = 100}, |
5174 | | {"mute", "Mute"}, |
5175 | | {"ao-mute", "AO Mute"}, |
5176 | | {"audio-delay", "A-V delay"}, |
5177 | | {"audio", "Audio"}, |
5178 | | // video |
5179 | | {"panscan", "Panscan", .osd_progbar = OSD_PANSCAN}, |
5180 | | {"taskbar-progress", "Progress in taskbar"}, |
5181 | | {"snap-window", "Snap to screen edges"}, |
5182 | | {"ontop", "Stay on top"}, |
5183 | | {"on-all-workspaces", "Visibility on all workspaces"}, |
5184 | | {"border", "Border"}, |
5185 | | {"framedrop", "Framedrop"}, |
5186 | | {"deinterlace", "Deinterlace"}, |
5187 | | {"gamma", "Gamma", .osd_progbar = OSD_BRIGHTNESS }, |
5188 | | {"brightness", "Brightness", .osd_progbar = OSD_BRIGHTNESS}, |
5189 | | {"contrast", "Contrast", .osd_progbar = OSD_CONTRAST}, |
5190 | | {"saturation", "Saturation", .osd_progbar = OSD_SATURATION}, |
5191 | | {"hue", "Hue", .osd_progbar = OSD_HUE}, |
5192 | | {"angle", "Angle"}, |
5193 | | // subs |
5194 | | {"sub", "Subtitles"}, |
5195 | | {"secondary-sid", "Secondary subtitles"}, |
5196 | | {"sub-pos", "Sub position"}, |
5197 | | {"secondary-sub-pos", "Secondary sub position"}, |
5198 | | {"sub-delay", "Sub delay"}, |
5199 | | {"secondary-sub-delay", "Secondary sub delay"}, |
5200 | | {"sub-speed", "Sub speed"}, |
5201 | | {"sub-visibility", |
5202 | | .msg = "Subtitles ${!sub-visibility==yes:hidden}" |
5203 | | "${?sub-visibility==yes:visible${?sub==no: (but no subtitles selected)}}"}, |
5204 | | {"secondary-sub-visibility", |
5205 | | .msg = "Secondary Subtitles ${!secondary-sub-visibility==yes:hidden}" |
5206 | | "${?secondary-sub-visibility==yes:visible${?secondary-sid==no: (but no secondary subtitles selected)}}"}, |
5207 | | {"sub-forced-events-only", "Forced sub only"}, |
5208 | | {"sub-scale", "Sub Scale"}, |
5209 | | {"secondary-sub-scale", "Secondary sub scale"}, |
5210 | | {"sub-ass-use-video-data", "Subtitle using video properties"}, |
5211 | | {"sub-ass-video-aspect-override", "Subtitle aspect override"}, |
5212 | | {"sub-ass-override", "ASS subtitle style override"}, |
5213 | | {"secondary-sub-ass-override", "Secondary sub ASS subtitle style override"}, |
5214 | | {"vf", "Video filters", .msg = "Video filters:\n${vf}"}, |
5215 | | {"af", "Audio filters", .msg = "Audio filters:\n${af}"}, |
5216 | | {"ab-loop-a", "A-B loop start"}, |
5217 | | {"ab-loop-b", .msg = "A-B loop: ${ab-loop-a} - ${ab-loop-b}" |
5218 | | "${?=ab-loop-count==0: (disabled)}"}, |
5219 | | {"audio-device", "Audio device"}, |
5220 | | {"hwdec", .msg = "Hardware decoding: ${hwdec-current}"}, |
5221 | | {"video-aspect-override", "Aspect ratio override"}, |
5222 | | // By default, don't display the following properties on OSD |
5223 | | {"pause", NULL}, |
5224 | | {"fullscreen", NULL}, |
5225 | | {"window-minimized", NULL}, |
5226 | | {"window-maximized", NULL}, |
5227 | | {0} |
5228 | | }; |
5229 | | |
5230 | | static void show_property_osd(MPContext *mpctx, const char *name, int osd_mode) |
5231 | 0 | { |
5232 | 0 | struct MPOpts *opts = mpctx->opts; |
5233 | 0 | struct property_osd_display disp = {.name = name, .osd_name = name}; |
5234 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
5235 | |
|
5236 | 0 | if (!osd_mode) |
5237 | 0 | return; |
5238 | | |
5239 | | // hwdec is a special case that must wait until video reconfig |
5240 | 0 | if (strcmp(name, "hwdec") == 0 && !cmd->hwdec_osd_mode) { |
5241 | 0 | cmd->hwdec_osd_mode = osd_mode; |
5242 | 0 | return; |
5243 | 0 | } |
5244 | | |
5245 | | // look for the command |
5246 | 0 | for (const struct property_osd_display *p = property_osd_display; p->name; p++) |
5247 | 0 | { |
5248 | 0 | if (!strcmp(p->name, name)) { |
5249 | 0 | disp = *p; |
5250 | 0 | break; |
5251 | 0 | } |
5252 | 0 | } |
5253 | |
|
5254 | 0 | if (osd_mode == MP_ON_OSD_AUTO) { |
5255 | 0 | osd_mode = |
5256 | 0 | ((disp.msg || disp.osd_name || disp.seek_msg) ? MP_ON_OSD_MSG : 0) | |
5257 | 0 | ((disp.osd_progbar || disp.seek_bar) ? MP_ON_OSD_BAR : 0); |
5258 | 0 | } |
5259 | |
|
5260 | 0 | if (!disp.osd_progbar) |
5261 | 0 | disp.osd_progbar = ' '; |
5262 | |
|
5263 | 0 | if (!disp.osd_name) |
5264 | 0 | disp.osd_name = name; |
5265 | |
|
5266 | 0 | if (disp.seek_msg || disp.seek_bar) { |
5267 | 0 | mpctx->add_osd_seek_info |= |
5268 | 0 | (osd_mode & MP_ON_OSD_MSG ? disp.seek_msg : 0) | |
5269 | 0 | (osd_mode & MP_ON_OSD_BAR ? disp.seek_bar : 0); |
5270 | 0 | return; |
5271 | 0 | } |
5272 | | |
5273 | 0 | struct m_option prop = {0}; |
5274 | 0 | mp_property_do(name, M_PROPERTY_GET_CONSTRICTED_TYPE, &prop, mpctx); |
5275 | 0 | if ((osd_mode & MP_ON_OSD_BAR)) { |
5276 | 0 | if (prop.type == CONF_TYPE_INT && prop.min < prop.max) { |
5277 | 0 | int n = prop.min; |
5278 | 0 | if (disp.osd_progbar) |
5279 | 0 | n = disp.marker; |
5280 | 0 | int i; |
5281 | 0 | if (mp_property_do(name, M_PROPERTY_GET, &i, mpctx) > 0) |
5282 | 0 | set_osd_bar(mpctx, disp.osd_progbar, prop.min, prop.max, n, i); |
5283 | 0 | } else if (prop.type == CONF_TYPE_FLOAT && prop.min < prop.max) { |
5284 | 0 | float n = prop.min; |
5285 | 0 | if (disp.osd_progbar) |
5286 | 0 | n = disp.marker; |
5287 | 0 | float f; |
5288 | 0 | if (mp_property_do(name, M_PROPERTY_GET, &f, mpctx) > 0) |
5289 | 0 | set_osd_bar(mpctx, disp.osd_progbar, prop.min, prop.max, n, f); |
5290 | 0 | } |
5291 | 0 | } |
5292 | |
|
5293 | 0 | if (osd_mode & MP_ON_OSD_MSG) { |
5294 | 0 | void *tmp = talloc_new(NULL); |
5295 | |
|
5296 | 0 | const char *msg = disp.msg; |
5297 | 0 | if (!msg) |
5298 | 0 | msg = talloc_asprintf(tmp, "%s: ${%s}", disp.osd_name, name); |
5299 | |
|
5300 | 0 | char *osd_msg = talloc_steal(tmp, mp_property_expand_string(mpctx, msg)); |
5301 | |
|
5302 | 0 | if (osd_msg && osd_msg[0]) |
5303 | 0 | set_osd_msg(mpctx, 1, opts->osd_duration, "%s", osd_msg); |
5304 | |
|
5305 | 0 | talloc_free(tmp); |
5306 | 0 | } |
5307 | 0 | } |
5308 | | |
5309 | | static bool reinit_filters(MPContext *mpctx, enum stream_type mediatype) |
5310 | 8.25k | { |
5311 | 8.25k | switch (mediatype) { |
5312 | 6.32k | case STREAM_VIDEO: |
5313 | 6.32k | return reinit_video_filters(mpctx) >= 0; |
5314 | 1.92k | case STREAM_AUDIO: |
5315 | 1.92k | return reinit_audio_filters(mpctx) >= 0; |
5316 | 8.25k | } |
5317 | 0 | return false; |
5318 | 8.25k | } |
5319 | | |
5320 | | static const char *const filter_opt[STREAM_TYPE_COUNT] = { |
5321 | | [STREAM_VIDEO] = "vf", |
5322 | | [STREAM_AUDIO] = "af", |
5323 | | }; |
5324 | | |
5325 | | static int set_filters(struct MPContext *mpctx, enum stream_type mediatype, |
5326 | | struct m_obj_settings *new_chain) |
5327 | 8.25k | { |
5328 | 8.25k | bstr option = bstr0(filter_opt[mediatype]); |
5329 | 8.25k | struct m_config_option *co = m_config_get_co(mpctx->mconfig, option); |
5330 | 8.25k | if (!co) |
5331 | 0 | return -1; |
5332 | | |
5333 | 8.25k | struct m_obj_settings **list = co->data; |
5334 | 8.25k | struct m_obj_settings *old_settings = *list; |
5335 | 8.25k | *list = NULL; |
5336 | 8.25k | m_option_copy(co->opt, list, &new_chain); |
5337 | | |
5338 | 8.25k | bool success = reinit_filters(mpctx, mediatype); |
5339 | | |
5340 | 8.25k | if (success) { |
5341 | 8.25k | m_option_free(co->opt, &old_settings); |
5342 | 8.25k | m_config_notify_change_opt_ptr(mpctx->mconfig, list); |
5343 | 8.25k | } else { |
5344 | 0 | m_option_free(co->opt, list); |
5345 | 0 | *list = old_settings; |
5346 | 0 | } |
5347 | | |
5348 | 8.25k | return success ? 0 : -1; |
5349 | 8.25k | } |
5350 | | |
5351 | | static int edit_filters(struct MPContext *mpctx, struct mp_log *log, |
5352 | | enum stream_type mediatype, |
5353 | | const char *cmd, const char *arg) |
5354 | 0 | { |
5355 | 0 | bstr option = bstr0(filter_opt[mediatype]); |
5356 | 0 | struct m_config_option *co = m_config_get_co(mpctx->mconfig, option); |
5357 | 0 | if (!co) |
5358 | 0 | return -1; |
5359 | | |
5360 | | // The option parser is used to modify the filter list itself. |
5361 | 0 | char optname[20]; |
5362 | 0 | snprintf(optname, sizeof(optname), "%.*s-%s", BSTR_P(option), cmd); |
5363 | |
|
5364 | 0 | struct m_obj_settings *new_chain = NULL; |
5365 | 0 | m_option_copy(co->opt, &new_chain, co->data); |
5366 | |
|
5367 | 0 | int r = m_option_parse(log, co->opt, bstr0(optname), bstr0(arg), &new_chain); |
5368 | 0 | if (r >= 0) |
5369 | 0 | r = set_filters(mpctx, mediatype, new_chain); |
5370 | |
|
5371 | 0 | m_option_free(co->opt, &new_chain); |
5372 | |
|
5373 | 0 | return r >= 0 ? 0 : -1; |
5374 | 0 | } |
5375 | | |
5376 | | static int edit_filters_osd(struct MPContext *mpctx, enum stream_type mediatype, |
5377 | | const char *cmd, const char *arg, bool on_osd) |
5378 | 0 | { |
5379 | 0 | int r = edit_filters(mpctx, mpctx->log, mediatype, cmd, arg); |
5380 | 0 | if (on_osd) { |
5381 | 0 | if (r >= 0) { |
5382 | 0 | const char *prop = filter_opt[mediatype]; |
5383 | 0 | show_property_osd(mpctx, prop, MP_ON_OSD_MSG); |
5384 | 0 | } else { |
5385 | 0 | set_osd_msg(mpctx, 1, mpctx->opts->osd_duration, |
5386 | 0 | "Changing filters failed!"); |
5387 | 0 | } |
5388 | 0 | } |
5389 | 0 | return r; |
5390 | 0 | } |
5391 | | |
5392 | | static void recreate_overlays(struct MPContext *mpctx) |
5393 | 0 | { |
5394 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
5395 | 0 | int overlay_next = !cmd->overlay_osd_current; |
5396 | 0 | struct sub_bitmaps *new = &cmd->overlay_osd[overlay_next]; |
5397 | 0 | new->format = SUBBITMAP_BGRA; |
5398 | 0 | new->change_id = 1; |
5399 | |
|
5400 | 0 | bool valid = false; |
5401 | |
|
5402 | 0 | new->num_parts = 0; |
5403 | 0 | for (int n = 0; n < cmd->num_overlays; n++) { |
5404 | 0 | struct overlay *o = &cmd->overlays[n]; |
5405 | 0 | if (o->source) { |
5406 | 0 | struct mp_image *s = o->source; |
5407 | 0 | struct sub_bitmap b = { |
5408 | 0 | .bitmap = s->planes[0], |
5409 | 0 | .stride = s->stride[0], |
5410 | 0 | .w = s->w, .dw = o->dw, |
5411 | 0 | .h = s->h, .dh = o->dh, |
5412 | 0 | .x = o->x, |
5413 | 0 | .y = o->y, |
5414 | 0 | .bgra = { |
5415 | 0 | .primaries = o->primaries, |
5416 | 0 | .transfer = o->transfer, |
5417 | 0 | .max_luma = o->max_luma, |
5418 | 0 | .video_color_space = o->video_colorspace, |
5419 | 0 | }, |
5420 | 0 | }; |
5421 | 0 | MP_TARRAY_APPEND(cmd, new->parts, new->num_parts, b); |
5422 | 0 | } |
5423 | 0 | } |
5424 | |
|
5425 | 0 | if (!cmd->overlay_packer) |
5426 | 0 | cmd->overlay_packer = talloc_zero(cmd, struct bitmap_packer); |
5427 | |
|
5428 | 0 | cmd->overlay_packer->padding = 1; // assume bilinear scaling |
5429 | 0 | packer_set_size(cmd->overlay_packer, new->num_parts); |
5430 | |
|
5431 | 0 | for (int n = 0; n < new->num_parts; n++) |
5432 | 0 | cmd->overlay_packer->in[n] = (struct pos){new->parts[n].w, new->parts[n].h}; |
5433 | |
|
5434 | 0 | if (packer_pack(cmd->overlay_packer) < 0 || new->num_parts == 0) |
5435 | 0 | goto done; |
5436 | | |
5437 | 0 | struct pos bb[2]; |
5438 | 0 | packer_get_bb(cmd->overlay_packer, bb); |
5439 | |
|
5440 | 0 | new->packed_w = bb[1].x; |
5441 | 0 | new->packed_h = bb[1].y; |
5442 | |
|
5443 | 0 | if (!new->packed || new->packed->w < new->packed_w || |
5444 | 0 | new->packed->h < new->packed_h) |
5445 | 0 | { |
5446 | 0 | talloc_free(new->packed); |
5447 | 0 | new->packed = mp_image_alloc(IMGFMT_BGRA, cmd->overlay_packer->w, |
5448 | 0 | cmd->overlay_packer->h); |
5449 | 0 | if (!new->packed) |
5450 | 0 | goto done; |
5451 | 0 | } |
5452 | | |
5453 | 0 | if (!mp_image_make_writeable(new->packed)) |
5454 | 0 | goto done; |
5455 | | |
5456 | | // clear padding |
5457 | 0 | mp_image_clear(new->packed, 0, 0, new->packed->w, new->packed->h); |
5458 | |
|
5459 | 0 | for (int n = 0; n < new->num_parts; n++) { |
5460 | 0 | struct sub_bitmap *b = &new->parts[n]; |
5461 | 0 | struct pos pos = cmd->overlay_packer->result[n]; |
5462 | |
|
5463 | 0 | int stride = new->packed->stride[0]; |
5464 | 0 | void *pdata = (uint8_t *)new->packed->planes[0] + pos.y * stride + pos.x * 4; |
5465 | 0 | memcpy_pic(pdata, b->bitmap, b->w * 4, b->h, stride, b->stride); |
5466 | |
|
5467 | 0 | b->bitmap = pdata; |
5468 | 0 | b->stride = stride; |
5469 | |
|
5470 | 0 | b->src_x = pos.x; |
5471 | 0 | b->src_y = pos.y; |
5472 | 0 | } |
5473 | |
|
5474 | 0 | valid = true; |
5475 | 0 | done: |
5476 | 0 | if (!valid) { |
5477 | 0 | new->format = SUBBITMAP_EMPTY; |
5478 | 0 | new->num_parts = 0; |
5479 | 0 | } |
5480 | |
|
5481 | 0 | osd_set_bitmaps(mpctx->osd, OSDTYPE_EXTERNAL2, new); |
5482 | 0 | mp_wakeup_core(mpctx); |
5483 | 0 | cmd->overlay_osd_current = overlay_next; |
5484 | 0 | } |
5485 | | |
5486 | | // Set overlay with the given ID to the contents as described by "new". |
5487 | | static void replace_overlay(struct MPContext *mpctx, int id, struct overlay *new) |
5488 | 0 | { |
5489 | 0 | struct command_ctx *cmd = mpctx->command_ctx; |
5490 | 0 | mp_assert(id >= 0); |
5491 | 0 | if (id >= cmd->num_overlays) { |
5492 | 0 | MP_TARRAY_GROW(cmd, cmd->overlays, id); |
5493 | 0 | while (cmd->num_overlays <= id) |
5494 | 0 | cmd->overlays[cmd->num_overlays++] = (struct overlay){0}; |
5495 | 0 | } |
5496 | |
|
5497 | 0 | struct overlay *ptr = &cmd->overlays[id]; |
5498 | |
|
5499 | 0 | talloc_free(ptr->source); |
5500 | 0 | *ptr = *new; |
5501 | |
|
5502 | 0 | recreate_overlays(mpctx); |
5503 | 0 | } |
5504 | | |
5505 | | static bool |
5506 | | fread_pic(FILE *fp, mp_image_t *dst, size_t bytesPerLine, size_t h, size_t stride, size_t offset) |
5507 | 0 | { |
5508 | 0 | size_t bytesRead = 0, expectedBytes = h * bytesPerLine; |
5509 | 0 | size_t src_off = offset, dst_off = 0; |
5510 | 0 | if (stride == dst->stride[0]) { |
5511 | 0 | if (fseek(fp, src_off, SEEK_SET)) |
5512 | 0 | return false; |
5513 | 0 | bytesRead = fread(dst->planes[0], 1, expectedBytes, fp); |
5514 | 0 | } else { |
5515 | 0 | for (int i = 0; i < h; i++) { |
5516 | 0 | if (fseek(fp, src_off, SEEK_SET)) |
5517 | 0 | break; |
5518 | 0 | size_t r = fread(dst->planes[0] + dst_off, 1, bytesPerLine, fp); |
5519 | 0 | if (r != bytesPerLine) |
5520 | 0 | break; |
5521 | 0 | bytesRead += bytesPerLine; |
5522 | 0 | src_off += stride; |
5523 | 0 | dst_off += dst->stride[0]; |
5524 | 0 | } |
5525 | 0 | } |
5526 | 0 | return bytesRead == expectedBytes; |
5527 | 0 | } |
5528 | | |
5529 | | static void cmd_overlay_add(void *pcmd) |
5530 | 0 | { |
5531 | 0 | struct mp_cmd_ctx *cmd = pcmd; |
5532 | 0 | struct MPContext *mpctx = cmd->mpctx; |
5533 | 0 | int id = cmd->args[0].v.i, x = cmd->args[1].v.i, y = cmd->args[2].v.i; |
5534 | 0 | char *file = cmd->args[3].v.s; |
5535 | 0 | int offset = cmd->args[4].v.i; |
5536 | 0 | char *fmt = cmd->args[5].v.s; |
5537 | 0 | int w = cmd->args[6].v.i, h = cmd->args[7].v.i, stride = cmd->args[8].v.i; |
5538 | 0 | int dw = cmd->args[9].v.i, dh = cmd->args[10].v.i; |
5539 | 0 | bool video_colorspace = cmd->args[11].v.b; |
5540 | 0 | float max_luma = cmd->args[12].v.f; |
5541 | 0 | int primaries = cmd->args[13].v.i, transfer = cmd->args[14].v.i; |
5542 | | |
5543 | | // video_colorspace overrides explicit tags |
5544 | 0 | if (video_colorspace) |
5545 | 0 | primaries = transfer = 0; |
5546 | |
|
5547 | 0 | if (dw <= 0) |
5548 | 0 | dw = w; |
5549 | 0 | if (dh <= 0) |
5550 | 0 | dh = h; |
5551 | 0 | if (strcmp(fmt, "bgra") != 0) { |
5552 | 0 | MP_ERR(mpctx, "overlay-add: unsupported OSD format '%s'\n", fmt); |
5553 | 0 | goto error; |
5554 | 0 | } |
5555 | 0 | if (id < 0 || id >= 64) { // arbitrary upper limit |
5556 | 0 | MP_ERR(mpctx, "overlay-add: invalid id %d\n", id); |
5557 | 0 | goto error; |
5558 | 0 | } |
5559 | 0 | if (w <= 0 || h <= 0 || stride / 4 < w || (stride % 4) || offset < 0) { |
5560 | 0 | MP_ERR(mpctx, "overlay-add: inconsistent parameters\n"); |
5561 | 0 | goto error; |
5562 | 0 | } |
5563 | 0 | #ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION |
5564 | | // The source is memory, an fd or a file, never the fuzzer input. |
5565 | 0 | goto error; |
5566 | 0 | #endif |
5567 | 0 | struct overlay overlay = { |
5568 | 0 | .source = mp_image_alloc(IMGFMT_BGRA, w, h), |
5569 | 0 | .x = x, |
5570 | 0 | .y = y, |
5571 | 0 | .dw = dw, |
5572 | 0 | .dh = dh, |
5573 | 0 | .primaries = primaries, |
5574 | 0 | .transfer = transfer, |
5575 | 0 | .max_luma = max_luma, |
5576 | 0 | .video_colorspace = video_colorspace, |
5577 | 0 | }; |
5578 | 0 | if (!overlay.source) |
5579 | 0 | goto error; |
5580 | 0 | int fd = -1; |
5581 | 0 | void *p = NULL; |
5582 | 0 | if (file[0] == '@') { |
5583 | 0 | char *end; |
5584 | 0 | int srcfd = strtol(&file[1], &end, 10); |
5585 | 0 | if (file[1] && end[0] == '\0' && srcfd >= 0) |
5586 | 0 | fd = mp_dup_cloexec(srcfd); // fdopen will "own" the fd |
5587 | 0 | } else if (file[0] == '&') { |
5588 | 0 | char *end; |
5589 | 0 | unsigned long long addr = strtoull(&file[1], &end, 0); |
5590 | 0 | if (!file[1] || end[0]) |
5591 | 0 | addr = 0; |
5592 | 0 | p = (void *)(uintptr_t)addr; |
5593 | 0 | } else { |
5594 | 0 | fd = open(file, O_RDONLY | O_BINARY | O_CLOEXEC); |
5595 | 0 | } |
5596 | |
|
5597 | 0 | FILE *fp = (fd >= 0) ? fdopen(fd, "rb") : NULL; |
5598 | 0 | int open_or_read_err = 1, bytesPerLine = w * 4; |
5599 | 0 | if (p) { |
5600 | 0 | memcpy_pic(overlay.source->planes[0], (char *)p + offset, bytesPerLine, |
5601 | 0 | h, overlay.source->stride[0], stride); |
5602 | 0 | open_or_read_err = 0; |
5603 | 0 | } else if (fp) { |
5604 | 0 | open_or_read_err = !fread_pic(fp, overlay.source, bytesPerLine, h, stride, offset); |
5605 | 0 | fclose(fp); |
5606 | 0 | } |
5607 | 0 | if (open_or_read_err) { |
5608 | 0 | MP_ERR(mpctx, "overlay-add: could not open or read '%s'\n", file); |
5609 | 0 | talloc_free(overlay.source); |
5610 | 0 | goto error; |
5611 | 0 | } |
5612 | | |
5613 | 0 | replace_overlay(mpctx, id, &overlay); |
5614 | 0 | return; |
5615 | 0 | error: |
5616 | 0 | cmd->success = false; |
5617 | 0 | } |
5618 | | |
5619 | | static void cmd_overlay_remove(void *p) |
5620 | 0 | { |
5621 | 0 | struct mp_cmd_ctx *cmd = p; |
5622 | 0 | struct MPContext *mpctx = cmd->mpctx; |
5623 | 0 | struct command_ctx *cmdctx = mpctx->command_ctx; |
5624 | 0 | int id = cmd->args[0].v.i; |
5625 | 0 | if (id >= 0 && id < cmdctx->num_overlays) |
5626 | 0 | replace_overlay(mpctx, id, &(struct overlay){0}); |
5627 | 0 | } |
5628 | | |
5629 | | static void overlay_uninit(struct MPContext *mpctx) |
5630 | 227k | { |
5631 | 227k | struct command_ctx *cmd = mpctx->command_ctx; |
5632 | 227k | if (!mpctx->osd) |
5633 | 0 | return; |
5634 | 227k | for (int id = 0; id < cmd->num_overlays; id++) |
5635 | 0 | replace_overlay(mpctx, id, &(struct overlay){0}); |
5636 | 227k | osd_set_bitmaps(mpctx->osd, OSDTYPE_EXTERNAL2, NULL); |
5637 | 682k | for (int n = 0; n < 2; n++) |
5638 | 455k | mp_image_unrefp(&cmd->overlay_osd[n].packed); |
5639 | 227k | } |
5640 | | |
5641 | | static void cmd_osd_overlay(void *p) |
5642 | 0 | { |
5643 | 0 | struct mp_cmd_ctx *cmd = p; |
5644 | 0 | struct MPContext *mpctx = cmd->mpctx; |
5645 | 0 | double rc[4] = {0}; |
5646 | |
|
5647 | 0 | struct osd_external_ass ov = { |
5648 | 0 | .owner = cmd->cmd->sender, |
5649 | 0 | .id = cmd->args[0].v.i64, |
5650 | 0 | .format = cmd->args[1].v.i, |
5651 | 0 | .data = cmd->args[2].v.s, |
5652 | 0 | .res_x = cmd->args[3].v.i, |
5653 | 0 | .res_y = cmd->args[4].v.i, |
5654 | 0 | .z = cmd->args[5].v.i, |
5655 | 0 | .hidden = cmd->args[6].v.b, |
5656 | 0 | .out_rc = cmd->args[7].v.b ? rc : NULL, |
5657 | 0 | }; |
5658 | |
|
5659 | 0 | osd_set_external(mpctx->osd, &ov); |
5660 | |
|
5661 | 0 | struct mpv_node *res = &cmd->result; |
5662 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
5663 | | |
5664 | | // (An empty rc uses INFINITY, avoid in JSON, just leave it unset.) |
5665 | 0 | if (rc[0] < rc[2] && rc[1] < rc[3]) { |
5666 | 0 | node_map_add_double(res, "x0", rc[0]); |
5667 | 0 | node_map_add_double(res, "y0", rc[1]); |
5668 | 0 | node_map_add_double(res, "x1", rc[2]); |
5669 | 0 | node_map_add_double(res, "y1", rc[3]); |
5670 | 0 | } |
5671 | |
|
5672 | 0 | mp_wakeup_core(mpctx); |
5673 | 0 | } |
5674 | | |
5675 | | static struct track *find_track_with_url(struct MPContext *mpctx, int type, const char *url) |
5676 | 0 | { |
5677 | 0 | char *path = mp_get_user_path(NULL, mpctx->global, url); |
5678 | 0 | struct track *t = NULL; |
5679 | 0 | for (int n = 0; n < mpctx->num_tracks; n++) { |
5680 | 0 | struct track *track = mpctx->tracks[n]; |
5681 | 0 | if (track && track->type == type && track->is_external) { |
5682 | 0 | char *normalized = mp_normalize_user_path(NULL, mpctx->global, track->external_filename); |
5683 | 0 | t = strcmp(normalized, path) == 0 ? track : NULL; |
5684 | 0 | talloc_free(normalized); |
5685 | 0 | if (t) |
5686 | 0 | break; |
5687 | 0 | } |
5688 | 0 | } |
5689 | 0 | talloc_free(path); |
5690 | 0 | return t; |
5691 | 0 | } |
5692 | | |
5693 | | // Whether this property should react to key events generated by auto-repeat. |
5694 | | static bool check_property_autorepeat(char *property, struct MPContext *mpctx) |
5695 | 0 | { |
5696 | 0 | struct m_option prop = {0}; |
5697 | 0 | if (mp_property_do(property, M_PROPERTY_GET_TYPE, &prop, mpctx) <= 0) |
5698 | 0 | return true; |
5699 | | |
5700 | | // This is a heuristic at best. |
5701 | 0 | if (prop.type->flags & M_OPT_TYPE_CHOICE) |
5702 | 0 | return false; |
5703 | | |
5704 | 0 | return true; |
5705 | 0 | } |
5706 | | |
5707 | | // Whether changes to this property (add/cycle cmds) benefit from cmd->scale |
5708 | | static bool check_property_scalable(char *property, struct MPContext *mpctx) |
5709 | 0 | { |
5710 | 0 | struct m_option prop = {0}; |
5711 | 0 | if (mp_property_do(property, M_PROPERTY_GET_TYPE, &prop, mpctx) <= 0) |
5712 | 0 | return true; |
5713 | | |
5714 | | // These properties are backed by a floating-point number |
5715 | 0 | return prop.type == &m_option_type_float || |
5716 | 0 | prop.type == &m_option_type_double || |
5717 | 0 | prop.type == &m_option_type_time || |
5718 | 0 | prop.type == &m_option_type_aspect; |
5719 | 0 | } |
5720 | | |
5721 | | static void show_property_status(struct mp_cmd_ctx *cmd, const char *name, int r) |
5722 | 0 | { |
5723 | 0 | struct MPContext *mpctx = cmd->mpctx; |
5724 | 0 | struct MPOpts *opts = mpctx->opts; |
5725 | 0 | int osd_duration = opts->osd_duration; |
5726 | 0 | int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; |
5727 | |
|
5728 | 0 | if (r == M_PROPERTY_OK || r == M_PROPERTY_UNAVAILABLE) { |
5729 | 0 | show_property_osd(mpctx, name, cmd->on_osd); |
5730 | 0 | if (r == M_PROPERTY_UNAVAILABLE) |
5731 | 0 | cmd->success = false; |
5732 | 0 | } else if (r == M_PROPERTY_UNKNOWN) { |
5733 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Unknown property: '%s'", name); |
5734 | 0 | cmd->success = false; |
5735 | 0 | } else if (r <= 0) { |
5736 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Failed to set property '%s'", |
5737 | 0 | name); |
5738 | 0 | cmd->success = false; |
5739 | 0 | } |
5740 | 0 | } |
5741 | | |
5742 | | static void change_property_cmd(struct mp_cmd_ctx *cmd, |
5743 | | const char *name, int action, void *arg) |
5744 | 0 | { |
5745 | 0 | struct m_property *prop = NULL; |
5746 | 0 | if (cmd->cmd->coalesce) { |
5747 | 0 | struct command_ctx *ctx = cmd->mpctx->command_ctx; |
5748 | 0 | prop = m_property_list_find(&ctx->properties, name); |
5749 | 0 | if (prop) |
5750 | 0 | prop->coalesce = true; |
5751 | 0 | } |
5752 | 0 | int r = mp_property_do(name, action, arg, cmd->mpctx); |
5753 | 0 | show_property_status(cmd, name, r); |
5754 | 0 | if (prop) |
5755 | 0 | prop->coalesce = false; |
5756 | 0 | } |
5757 | | |
5758 | | static void cmd_cycle_values(void *p) |
5759 | 0 | { |
5760 | 0 | struct mp_cmd_ctx *cmd = p; |
5761 | 0 | struct MPContext *mpctx = cmd->mpctx; |
5762 | 0 | int first = 0, dir = 1; |
5763 | |
|
5764 | 0 | if (strcmp(cmd->args[first].v.s, "!reverse") == 0) { |
5765 | 0 | first += 1; |
5766 | 0 | dir = -1; |
5767 | 0 | } |
5768 | |
|
5769 | 0 | const char *name = cmd->args[first].v.s; |
5770 | 0 | first += 1; |
5771 | |
|
5772 | 0 | if (first >= cmd->num_args) { |
5773 | 0 | MP_ERR(mpctx, "cycle-values command does not have any value arguments.\n"); |
5774 | 0 | cmd->success = false; |
5775 | 0 | return; |
5776 | 0 | } |
5777 | | |
5778 | 0 | struct m_option prop = {0}; |
5779 | 0 | int r = mp_property_do(name, M_PROPERTY_GET_TYPE, &prop, mpctx); |
5780 | 0 | if (r <= 0) { |
5781 | 0 | show_property_status(cmd, name, r); |
5782 | 0 | return; |
5783 | 0 | } |
5784 | | |
5785 | 0 | union m_option_value curval = m_option_value_default; |
5786 | 0 | r = mp_property_do(name, M_PROPERTY_GET, &curval, mpctx); |
5787 | 0 | if (r <= 0) { |
5788 | 0 | show_property_status(cmd, name, r); |
5789 | 0 | return; |
5790 | 0 | } |
5791 | | |
5792 | 0 | int current = -1; |
5793 | 0 | for (int n = first; n < cmd->num_args; n++) { |
5794 | 0 | union m_option_value val = m_option_value_default; |
5795 | 0 | if (m_option_parse(mpctx->log, &prop, bstr0(name), |
5796 | 0 | bstr0(cmd->args[n].v.s), &val) < 0) |
5797 | 0 | continue; |
5798 | | |
5799 | 0 | if (m_option_equal(&prop, &curval, &val)) |
5800 | 0 | current = n; |
5801 | |
|
5802 | 0 | m_option_free(&prop, &val); |
5803 | |
|
5804 | 0 | if (current >= 0) |
5805 | 0 | break; |
5806 | 0 | } |
5807 | |
|
5808 | 0 | m_option_free(&prop, &curval); |
5809 | |
|
5810 | 0 | if (current >= 0) { |
5811 | 0 | current += dir; |
5812 | 0 | if (current < first) |
5813 | 0 | current = cmd->num_args - 1; |
5814 | 0 | if (current >= cmd->num_args) |
5815 | 0 | current = first; |
5816 | 0 | } else { |
5817 | 0 | MP_VERBOSE(mpctx, "Current value not found. Picking default.\n"); |
5818 | 0 | current = dir > 0 ? first : cmd->num_args - 1; |
5819 | 0 | } |
5820 | |
|
5821 | 0 | change_property_cmd(cmd, name, M_PROPERTY_SET_STRING, cmd->args[current].v.s); |
5822 | 0 | } |
5823 | | |
5824 | | struct cmd_list_ctx { |
5825 | | struct MPContext *mpctx; |
5826 | | |
5827 | | // actual list command |
5828 | | struct mp_cmd_ctx *parent; |
5829 | | |
5830 | | bool current_valid; |
5831 | | mp_thread_id current_tid; |
5832 | | bool completed_recursive; |
5833 | | |
5834 | | // list of sub commands yet to run |
5835 | | struct mp_cmd **sub; |
5836 | | int num_sub; |
5837 | | }; |
5838 | | |
5839 | | static void continue_cmd_list(struct cmd_list_ctx *list); |
5840 | | |
5841 | | static void on_cmd_list_sub_completion(struct mp_cmd_ctx *cmd) |
5842 | 0 | { |
5843 | 0 | struct cmd_list_ctx *list = cmd->on_completion_priv; |
5844 | |
|
5845 | 0 | if (list->current_valid && mp_thread_id_equal(list->current_tid, mp_thread_current_id())) { |
5846 | 0 | list->completed_recursive = true; |
5847 | 0 | } else { |
5848 | 0 | continue_cmd_list(list); |
5849 | 0 | } |
5850 | 0 | } |
5851 | | |
5852 | | static void continue_cmd_list(struct cmd_list_ctx *list) |
5853 | 0 | { |
5854 | 0 | while (list->parent->args[0].v.p) { |
5855 | 0 | struct mp_cmd *sub = list->parent->args[0].v.p; |
5856 | 0 | list->parent->args[0].v.p = sub->queue_next; |
5857 | |
|
5858 | 0 | ta_set_parent(sub, NULL); |
5859 | |
|
5860 | 0 | if (sub->flags & MP_ASYNC_CMD) { |
5861 | | // We run it "detached" (fire & forget) |
5862 | 0 | run_command(list->mpctx, sub, NULL, NULL, NULL); |
5863 | 0 | } else { |
5864 | | // Run the next command once this one completes. |
5865 | |
|
5866 | 0 | list->completed_recursive = false; |
5867 | 0 | list->current_valid = true; |
5868 | 0 | list->current_tid = mp_thread_current_id(); |
5869 | |
|
5870 | 0 | run_command(list->mpctx, sub, NULL, on_cmd_list_sub_completion, list); |
5871 | |
|
5872 | 0 | list->current_valid = false; |
5873 | | |
5874 | | // run_command() either recursively calls the completion function, |
5875 | | // or lets the command continue run in the background. If it was |
5876 | | // completed recursively, we can just continue our loop. Otherwise |
5877 | | // the completion handler will invoke this loop again elsewhere. |
5878 | | // We could unconditionally call continue_cmd_list() in the handler |
5879 | | // instead, but then stack depth would grow with list length. |
5880 | 0 | if (!list->completed_recursive) |
5881 | 0 | return; |
5882 | 0 | } |
5883 | 0 | } |
5884 | | |
5885 | 0 | mp_cmd_ctx_complete(list->parent); |
5886 | 0 | talloc_free(list); |
5887 | 0 | } |
5888 | | |
5889 | | static void cmd_list(void *p) |
5890 | 0 | { |
5891 | 0 | struct mp_cmd_ctx *cmd = p; |
5892 | |
|
5893 | 0 | cmd->completed = false; |
5894 | |
|
5895 | 0 | struct cmd_list_ctx *list = talloc_zero(NULL, struct cmd_list_ctx); |
5896 | 0 | list->mpctx = cmd->mpctx; |
5897 | 0 | list->parent = p; |
5898 | |
|
5899 | 0 | continue_cmd_list(list); |
5900 | 0 | } |
5901 | | |
5902 | | const struct mp_cmd_def mp_cmd_list = { "list", cmd_list, .exec_async = true }; |
5903 | | |
5904 | | // Signal that the command is complete now. This also deallocates cmd. |
5905 | | // You must call this function in a state where the core is locked for the |
5906 | | // current thread (e.g. from the main thread, or from within mp_dispatch_lock()). |
5907 | | // Completion means the command is finished, even if it errored or never ran. |
5908 | | // Keep in mind that calling this can execute further user command that can |
5909 | | // change arbitrary state (due to cmd_list). |
5910 | | void mp_cmd_ctx_complete(struct mp_cmd_ctx *cmd) |
5911 | 601k | { |
5912 | 601k | cmd->completed = true; |
5913 | 601k | if (!cmd->success) |
5914 | 0 | mpv_free_node_contents(&cmd->result); |
5915 | 601k | if (cmd->on_completion) |
5916 | 601k | cmd->on_completion(cmd); |
5917 | 601k | if (cmd->abort) |
5918 | 0 | mp_abort_remove(cmd->mpctx, cmd->abort); |
5919 | 601k | mpv_free_node_contents(&cmd->result); |
5920 | 601k | talloc_free(cmd); |
5921 | 601k | } |
5922 | | |
5923 | | static void run_command_on_worker_thread(void *p) |
5924 | 0 | { |
5925 | 0 | struct mp_cmd_ctx *ctx = p; |
5926 | 0 | struct MPContext *mpctx = ctx->mpctx; |
5927 | |
|
5928 | 0 | mp_core_lock(mpctx); |
5929 | |
|
5930 | 0 | bool exec_async = ctx->cmd->def->exec_async; |
5931 | 0 | ctx->cmd->def->handler(ctx); |
5932 | 0 | if (!exec_async) |
5933 | 0 | mp_cmd_ctx_complete(ctx); |
5934 | |
|
5935 | 0 | mpctx->outstanding_async -= 1; |
5936 | 0 | if (!mpctx->outstanding_async && mp_is_shutting_down(mpctx)) |
5937 | 0 | mp_wakeup_core(mpctx); |
5938 | |
|
5939 | 0 | mp_core_unlock(mpctx); |
5940 | 0 | } |
5941 | | |
5942 | | // Run the given command. Upon command completion, on_completion is called. This |
5943 | | // can happen within the function, or for async commands, some time after the |
5944 | | // function returns (the caller is supposed to be able to handle both cases). In |
5945 | | // both cases, the callback will be called while the core is locked (i.e. you |
5946 | | // can access the core freely). |
5947 | | // If abort is non-NULL, then the caller creates the abort object. It must have |
5948 | | // been allocated with talloc. run_command() will register/unregister/destroy |
5949 | | // it. Must not be set if cmd->def->can_abort==false. |
5950 | | // on_completion_priv is copied to mp_cmd_ctx.on_completion_priv and can be |
5951 | | // accessed from the completion callback. |
5952 | | // The completion callback is invoked exactly once. If it's NULL, it's ignored. |
5953 | | // Ownership of cmd goes to the caller. |
5954 | | void run_command(struct MPContext *mpctx, struct mp_cmd *cmd, |
5955 | | struct mp_abort_entry *abort, |
5956 | | void (*on_completion)(struct mp_cmd_ctx *cmd), |
5957 | | void *on_completion_priv) |
5958 | 601k | { |
5959 | 601k | struct mp_cmd_ctx *ctx = talloc(NULL, struct mp_cmd_ctx); |
5960 | 601k | *ctx = (struct mp_cmd_ctx){ |
5961 | 601k | .mpctx = mpctx, |
5962 | 601k | .cmd = talloc_steal(ctx, cmd), |
5963 | 601k | .args = cmd->args, |
5964 | 601k | .num_args = cmd->nargs, |
5965 | 601k | .priv = cmd->def->priv, |
5966 | 601k | .abort = talloc_steal(ctx, abort), |
5967 | 601k | .success = true, |
5968 | 601k | .completed = true, |
5969 | 601k | .on_completion = on_completion, |
5970 | 601k | .on_completion_priv = on_completion_priv, |
5971 | 601k | }; |
5972 | | |
5973 | 601k | if (!ctx->abort && cmd->def->can_abort) |
5974 | 0 | ctx->abort = talloc_zero(ctx, struct mp_abort_entry); |
5975 | | |
5976 | 601k | mp_assert(cmd->def->can_abort == !!ctx->abort); |
5977 | | |
5978 | 601k | if (ctx->abort) { |
5979 | 0 | ctx->abort->coupled_to_playback |= cmd->def->abort_on_playback_end; |
5980 | 0 | mp_abort_add(mpctx, ctx->abort); |
5981 | 0 | } |
5982 | | |
5983 | 601k | struct MPOpts *opts = mpctx->opts; |
5984 | 601k | ctx->on_osd = cmd->flags & MP_ON_OSD_FLAGS; |
5985 | 601k | bool auto_osd = ctx->on_osd == MP_ON_OSD_AUTO; |
5986 | 601k | ctx->msg_osd = auto_osd || (ctx->on_osd & MP_ON_OSD_MSG); |
5987 | 601k | ctx->bar_osd = auto_osd || (ctx->on_osd & MP_ON_OSD_BAR); |
5988 | 601k | ctx->seek_msg_osd = auto_osd ? opts->osd_on_seek & 2 : ctx->msg_osd; |
5989 | 601k | ctx->seek_bar_osd = auto_osd ? opts->osd_on_seek & 1 : ctx->bar_osd; |
5990 | | |
5991 | 601k | bool noise = cmd->def->is_noisy || cmd->mouse_move; |
5992 | 601k | mp_cmd_dump(mpctx->log, noise ? MSGL_TRACE : MSGL_DEBUG, "Run command:", cmd); |
5993 | | |
5994 | 601k | if (cmd->flags & MP_EXPAND_PROPERTIES) { |
5995 | 0 | for (int n = 0; n < cmd->nargs; n++) { |
5996 | 0 | const m_option_type_t *type = cmd->args[n].type->type; |
5997 | 0 | char **list = NULL; |
5998 | 0 | if (type == CONF_TYPE_STRING) |
5999 | 0 | list = &cmd->args[n].v.s; |
6000 | 0 | else if (type == CONF_TYPE_STRING_LIST || type == CONF_TYPE_KV_LIST) |
6001 | 0 | list = cmd->args[n].v.str_list; |
6002 | 0 | for (; list && *list; list++) { |
6003 | 0 | char *s = mp_property_expand_string(mpctx, *list); |
6004 | 0 | if (!s) { |
6005 | 0 | ctx->success = false; |
6006 | 0 | mp_cmd_ctx_complete(ctx); |
6007 | 0 | return; |
6008 | 0 | } |
6009 | 0 | talloc_free(*list); |
6010 | 0 | *list = s; |
6011 | 0 | if (type == CONF_TYPE_STRING) |
6012 | 0 | break; |
6013 | 0 | } |
6014 | 0 | } |
6015 | 0 | } |
6016 | | |
6017 | 601k | if (cmd->def->spawn_thread) { |
6018 | 0 | mpctx->outstanding_async += 1; // prevent that core disappears |
6019 | 0 | if (!mp_thread_pool_queue(mpctx->thread_pool, |
6020 | 0 | run_command_on_worker_thread, ctx)) |
6021 | 0 | { |
6022 | 0 | mpctx->outstanding_async -= 1; |
6023 | 0 | ctx->success = false; |
6024 | 0 | mp_cmd_ctx_complete(ctx); |
6025 | 0 | } |
6026 | 601k | } else { |
6027 | 601k | bool exec_async = cmd->def->exec_async; |
6028 | 601k | cmd->def->handler(ctx); |
6029 | 601k | if (!exec_async) |
6030 | 601k | mp_cmd_ctx_complete(ctx); |
6031 | 601k | } |
6032 | 601k | } |
6033 | | |
6034 | | // When a command shows a message. status is the level (e.g. MSGL_INFO), and |
6035 | | // msg+vararg is as in printf (don't include a trailing "\n"). |
6036 | | void mp_cmd_msg(struct mp_cmd_ctx *cmd, int status, const char *msg, ...) |
6037 | 0 | { |
6038 | 0 | va_list ap; |
6039 | 0 | char *s; |
6040 | |
|
6041 | 0 | va_start(ap, msg); |
6042 | 0 | s = talloc_vasprintf(NULL, msg, ap); |
6043 | 0 | va_end(ap); |
6044 | |
|
6045 | 0 | MP_MSG(cmd->mpctx, status, "%s\n", s); |
6046 | 0 | if (cmd->msg_osd && status <= MSGL_INFO) |
6047 | 0 | set_osd_msg(cmd->mpctx, 1, cmd->mpctx->opts->osd_duration, "%s", s); |
6048 | |
|
6049 | 0 | talloc_free(s); |
6050 | 0 | } |
6051 | | |
6052 | | static void cmd_seek(void *p) |
6053 | 0 | { |
6054 | 0 | struct mp_cmd_ctx *cmd = p; |
6055 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6056 | |
|
6057 | 0 | double v = cmd->args[0].v.d * cmd->cmd->scale; |
6058 | 0 | int abs = cmd->args[1].v.i & 3; |
6059 | 0 | enum seek_precision precision = MPSEEK_DEFAULT; |
6060 | 0 | switch (((cmd->args[2].v.i | cmd->args[1].v.i) >> 3) & 3) { |
6061 | 0 | case 1: precision = MPSEEK_KEYFRAME; break; |
6062 | 0 | case 2: precision = MPSEEK_EXACT; break; |
6063 | 0 | } |
6064 | 0 | if (!mpctx->playback_initialized) { |
6065 | 0 | cmd->success = false; |
6066 | 0 | return; |
6067 | 0 | } |
6068 | | |
6069 | 0 | mark_seek(mpctx); |
6070 | 0 | switch (abs) { |
6071 | 0 | case 0: { // Relative seek |
6072 | 0 | queue_seek(mpctx, MPSEEK_RELATIVE, v, precision, MPSEEK_FLAG_DELAY); |
6073 | 0 | set_osd_function(mpctx, (v > 0) ? OSD_FFW : OSD_REW); |
6074 | 0 | break; |
6075 | 0 | } |
6076 | 0 | case 1: { // Absolute seek by percentage |
6077 | 0 | double ratio = v / 100.0; |
6078 | 0 | double cur_pos = get_current_pos_ratio(mpctx, false); |
6079 | 0 | queue_seek(mpctx, MPSEEK_FACTOR, ratio, precision, MPSEEK_FLAG_DELAY); |
6080 | 0 | set_osd_function(mpctx, cur_pos < ratio ? OSD_FFW : OSD_REW); |
6081 | 0 | break; |
6082 | 0 | } |
6083 | 0 | case 2: { // Absolute seek to a timestamp in seconds |
6084 | 0 | if (v < 0) { |
6085 | | // Seek from end |
6086 | 0 | double len = get_time_length(mpctx); |
6087 | 0 | if (len < 0) { |
6088 | 0 | cmd->success = false; |
6089 | 0 | return; |
6090 | 0 | } |
6091 | 0 | v = MPMAX(0, len + v); |
6092 | 0 | } |
6093 | 0 | queue_seek(mpctx, MPSEEK_ABSOLUTE, v, precision, MPSEEK_FLAG_DELAY); |
6094 | 0 | set_osd_function(mpctx, |
6095 | 0 | v > get_current_time(mpctx) ? OSD_FFW : OSD_REW); |
6096 | 0 | break; |
6097 | 0 | } |
6098 | 0 | case 3: { // Relative seek by percentage |
6099 | 0 | queue_seek(mpctx, MPSEEK_FACTOR, |
6100 | 0 | get_current_pos_ratio(mpctx, false) + v / 100.0, |
6101 | 0 | precision, MPSEEK_FLAG_DELAY); |
6102 | 0 | set_osd_function(mpctx, v > 0 ? OSD_FFW : OSD_REW); |
6103 | 0 | break; |
6104 | 0 | }} |
6105 | 0 | if (cmd->seek_bar_osd) |
6106 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; |
6107 | 0 | if (cmd->seek_msg_osd) |
6108 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; |
6109 | 0 | } |
6110 | | |
6111 | | static void cmd_revert_seek(void *p) |
6112 | 0 | { |
6113 | 0 | struct mp_cmd_ctx *cmd = p; |
6114 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6115 | 0 | struct command_ctx *cmdctx = mpctx->command_ctx; |
6116 | |
|
6117 | 0 | if (!mpctx->playback_initialized) { |
6118 | 0 | cmd->success = false; |
6119 | 0 | return; |
6120 | 0 | } |
6121 | | |
6122 | 0 | double oldpts = cmdctx->last_seek_pts; |
6123 | 0 | if (cmdctx->marked_pts != MP_NOPTS_VALUE) |
6124 | 0 | oldpts = cmdctx->marked_pts; |
6125 | 0 | if (cmd->args[0].v.i & 3) { |
6126 | 0 | cmdctx->marked_pts = get_current_time(mpctx); |
6127 | 0 | cmdctx->marked_permanent = cmd->args[0].v.i & 1; |
6128 | 0 | } else if (oldpts != MP_NOPTS_VALUE) { |
6129 | 0 | if (!cmdctx->marked_permanent) { |
6130 | 0 | cmdctx->marked_pts = MP_NOPTS_VALUE; |
6131 | 0 | cmdctx->last_seek_pts = get_current_time(mpctx); |
6132 | 0 | } |
6133 | 0 | queue_seek(mpctx, MPSEEK_ABSOLUTE, oldpts, MPSEEK_EXACT, |
6134 | 0 | MPSEEK_FLAG_DELAY); |
6135 | 0 | set_osd_function(mpctx, OSD_REW); |
6136 | 0 | if (cmd->seek_bar_osd) |
6137 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; |
6138 | 0 | if (cmd->seek_msg_osd) |
6139 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; |
6140 | 0 | } else { |
6141 | 0 | cmd->success = false; |
6142 | 0 | } |
6143 | 0 | } |
6144 | | |
6145 | | static void cmd_set(void *p) |
6146 | 0 | { |
6147 | 0 | struct mp_cmd_ctx *cmd = p; |
6148 | |
|
6149 | 0 | change_property_cmd(cmd, cmd->args[0].v.s, |
6150 | 0 | M_PROPERTY_SET_STRING, cmd->args[1].v.s); |
6151 | 0 | } |
6152 | | |
6153 | | static void cmd_del(void *p) |
6154 | 0 | { |
6155 | 0 | struct mp_cmd_ctx *cmd = p; |
6156 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6157 | 0 | const char *name = cmd->args[0].v.s; |
6158 | 0 | int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; |
6159 | 0 | int osd_duration = mpctx->opts->osd_duration; |
6160 | |
|
6161 | 0 | int r = mp_property_do(name, M_PROPERTY_DELETE, NULL, mpctx); |
6162 | |
|
6163 | 0 | if (r == M_PROPERTY_OK) { |
6164 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Deleted property: '%s'", name); |
6165 | 0 | cmd->success = true; |
6166 | 0 | } else if (r == M_PROPERTY_UNKNOWN) { |
6167 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Unknown property: '%s'", name); |
6168 | 0 | cmd->success = false; |
6169 | 0 | } else if (r <= 0) { |
6170 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Failed to set property '%s'", |
6171 | 0 | name); |
6172 | 0 | cmd->success = false; |
6173 | 0 | } |
6174 | 0 | } |
6175 | | |
6176 | | static void cmd_change_list(void *p) |
6177 | 0 | { |
6178 | 0 | struct mp_cmd_ctx *cmd = p; |
6179 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6180 | 0 | char *name = cmd->args[0].v.s; |
6181 | 0 | char *op = cmd->args[1].v.s; |
6182 | 0 | char *value = cmd->args[2].v.s; |
6183 | 0 | int osd_duration = mpctx->opts->osd_duration; |
6184 | 0 | int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; |
6185 | |
|
6186 | 0 | struct m_option prop = {0}; |
6187 | 0 | if (mp_property_do(name, M_PROPERTY_GET_TYPE, &prop, mpctx) <= 0) { |
6188 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Unknown option: '%s'", name); |
6189 | 0 | cmd->success = false; |
6190 | 0 | return; |
6191 | 0 | } |
6192 | | |
6193 | 0 | const struct m_option_type *type = prop.type; |
6194 | 0 | bool found = false; |
6195 | 0 | for (int i = 0; type->actions && type->actions[i].name; i++) { |
6196 | 0 | const struct m_option_action *action = &type->actions[i]; |
6197 | 0 | if (strcmp(action->name, op) == 0) |
6198 | 0 | found = true; |
6199 | 0 | } |
6200 | 0 | if (!found) { |
6201 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Unknown action: '%s'", op); |
6202 | 0 | cmd->success = false; |
6203 | 0 | return; |
6204 | 0 | } |
6205 | | |
6206 | 0 | union m_option_value val = m_option_value_default; |
6207 | 0 | if (mp_property_do(name, M_PROPERTY_GET, &val, mpctx) <= 0) { |
6208 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Could not read: '%s'", name); |
6209 | 0 | cmd->success = false; |
6210 | 0 | return; |
6211 | 0 | } |
6212 | | |
6213 | 0 | char *optname = mp_tprintf(80, "%s-%s", name, op); // the dirty truth |
6214 | 0 | int r = m_option_parse(mpctx->log, &prop, bstr0(optname), bstr0(value), &val); |
6215 | 0 | if (r >= 0 && mp_property_do(name, M_PROPERTY_SET, &val, mpctx) <= 0) |
6216 | 0 | r = -1; |
6217 | 0 | m_option_free(&prop, &val); |
6218 | 0 | if (r < 0) { |
6219 | 0 | set_osd_msg(mpctx, osdl, osd_duration, |
6220 | 0 | "Failed setting option: '%s'", name); |
6221 | 0 | cmd->success = false; |
6222 | 0 | return; |
6223 | 0 | } |
6224 | | |
6225 | 0 | show_property_osd(mpctx, name, cmd->on_osd); |
6226 | 0 | } |
6227 | | |
6228 | | static void cmd_add_cycle(void *p) |
6229 | 0 | { |
6230 | 0 | struct mp_cmd_ctx *cmd = p; |
6231 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6232 | 0 | bool is_cycle = !!cmd->priv; |
6233 | |
|
6234 | 0 | char *property = cmd->args[0].v.s; |
6235 | 0 | if (cmd->cmd->repeated && !check_property_autorepeat(property, mpctx) && |
6236 | 0 | !(cmd->cmd->flags & MP_ALLOW_REPEAT) /* "repeatable" prefix */ ) |
6237 | 0 | { |
6238 | 0 | MP_VERBOSE(mpctx, "Dropping command '%s' from auto-repeated key.\n", |
6239 | 0 | cmd->cmd->original); |
6240 | 0 | return; |
6241 | 0 | } |
6242 | | |
6243 | 0 | double scale = 1; |
6244 | 0 | int scale_units = cmd->cmd->scale_units; |
6245 | 0 | if (check_property_scalable(property, mpctx)) { |
6246 | 0 | scale = cmd->cmd->scale; |
6247 | 0 | scale_units = 1; |
6248 | 0 | } |
6249 | |
|
6250 | 0 | for (int i = 0; i < scale_units; i++) { |
6251 | 0 | struct m_property_switch_arg s = { |
6252 | 0 | .inc = cmd->args[1].v.d * scale, |
6253 | 0 | .wrap = is_cycle, |
6254 | 0 | }; |
6255 | 0 | change_property_cmd(cmd, property, M_PROPERTY_SWITCH, &s); |
6256 | 0 | if (!cmd->success) |
6257 | 0 | return; |
6258 | 0 | } |
6259 | 0 | } |
6260 | | |
6261 | | static void cmd_multiply(void *p) |
6262 | 0 | { |
6263 | 0 | struct mp_cmd_ctx *cmd = p; |
6264 | |
|
6265 | 0 | change_property_cmd(cmd, cmd->args[0].v.s, |
6266 | 0 | M_PROPERTY_MULTIPLY, &cmd->args[1].v.d); |
6267 | 0 | } |
6268 | | |
6269 | | static void cmd_frame_step(void *p) |
6270 | 0 | { |
6271 | 0 | struct mp_cmd_ctx *cmd = p; |
6272 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6273 | 0 | bool backstep = *(bool *)cmd->priv; |
6274 | 0 | int frames = backstep ? -1 : cmd->args[0].v.i; |
6275 | 0 | int flags = backstep ? 1 : cmd->args[1].v.i; |
6276 | |
|
6277 | 0 | if (!mpctx->playback_initialized || frames == 0) { |
6278 | 0 | cmd->success = false; |
6279 | 0 | return; |
6280 | 0 | } |
6281 | | |
6282 | 0 | if (flags == 1) { |
6283 | | // frame-step command has on_updown set so it is called on both down |
6284 | | // and up, but stepping should only be triggered when it matches the |
6285 | | // emit_on_up flag or if the command is repeated. |
6286 | 0 | if (cmd->cmd->repeated || cmd->cmd->is_up == cmd->cmd->emit_on_up) |
6287 | 0 | add_step_frame(mpctx, frames, true); |
6288 | 0 | } else { |
6289 | 0 | if (cmd->cmd->is_up) { |
6290 | 0 | if (mpctx->step_frames < 1) |
6291 | 0 | set_pause_state(mpctx, true); |
6292 | 0 | } else { |
6293 | 0 | if (flags == 2) |
6294 | 0 | step_frame_mute(mpctx, true); |
6295 | 0 | if (cmd->cmd->repeated) { |
6296 | 0 | set_pause_state(mpctx, false); |
6297 | 0 | } else { |
6298 | 0 | add_step_frame(mpctx, frames, false); |
6299 | 0 | } |
6300 | 0 | } |
6301 | 0 | } |
6302 | 0 | } |
6303 | | |
6304 | | static void cmd_quit(void *p) |
6305 | 410k | { |
6306 | 410k | struct mp_cmd_ctx *cmd = p; |
6307 | 410k | struct MPContext *mpctx = cmd->mpctx; |
6308 | 410k | bool write_watch_later = *(bool *)cmd->priv; |
6309 | 410k | if (write_watch_later || mpctx->opts->position_save_on_quit) |
6310 | 5 | mp_write_watch_later_conf(mpctx); |
6311 | 410k | mpctx->stop_play = PT_QUIT; |
6312 | 410k | mpctx->quit_custom_rc = cmd->args[0].v.i; |
6313 | 410k | mpctx->has_quit_custom_rc = true; |
6314 | 410k | mp_wakeup_core(mpctx); |
6315 | 410k | } |
6316 | | |
6317 | | static void cmd_playlist_next_prev(void *p) |
6318 | 0 | { |
6319 | 0 | struct mp_cmd_ctx *cmd = p; |
6320 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6321 | 0 | int dir = *(int *)cmd->priv; |
6322 | 0 | int force = cmd->args[0].v.i; |
6323 | |
|
6324 | 0 | struct playlist_entry *e = mp_next_file(mpctx, dir, force, true); |
6325 | 0 | if (!e && !force) { |
6326 | 0 | cmd->success = false; |
6327 | 0 | return; |
6328 | 0 | } |
6329 | | |
6330 | 0 | mp_set_playlist_entry(mpctx, e); |
6331 | 0 | if (cmd->on_osd & MP_ON_OSD_MSG) |
6332 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; |
6333 | 0 | } |
6334 | | |
6335 | | static void cmd_playlist_next_prev_playlist(void *p) |
6336 | 0 | { |
6337 | 0 | struct mp_cmd_ctx *cmd = p; |
6338 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6339 | 0 | int direction = *(int *)cmd->priv; |
6340 | |
|
6341 | 0 | struct playlist_entry *entry = |
6342 | 0 | playlist_get_first_in_next_playlist(mpctx->playlist, direction); |
6343 | |
|
6344 | 0 | if (!entry && mpctx->opts->loop_times != 1 && mpctx->playlist->current) { |
6345 | 0 | entry = direction > 0 ? playlist_get_first(mpctx->playlist) |
6346 | 0 | : playlist_get_last(mpctx->playlist); |
6347 | |
|
6348 | 0 | if (entry && entry->playlist_path && |
6349 | 0 | mpctx->playlist->current->playlist_path && |
6350 | 0 | strcmp(entry->playlist_path, |
6351 | 0 | mpctx->playlist->current->playlist_path) == 0) |
6352 | 0 | entry = NULL; |
6353 | |
|
6354 | 0 | if (direction > 0 && entry && mpctx->opts->loop_times > 1) { |
6355 | 0 | mpctx->opts->loop_times--; |
6356 | 0 | m_config_notify_change_opt_ptr(mpctx->mconfig, |
6357 | 0 | &mpctx->opts->loop_times); |
6358 | 0 | } |
6359 | |
|
6360 | 0 | if (direction < 0) |
6361 | 0 | entry = playlist_get_first_in_same_playlist( |
6362 | 0 | entry, mpctx->playlist->current->playlist_path); |
6363 | 0 | } |
6364 | |
|
6365 | 0 | if (!entry) { |
6366 | 0 | cmd->success = false; |
6367 | 0 | return; |
6368 | 0 | } |
6369 | | |
6370 | 0 | mp_set_playlist_entry(mpctx, entry); |
6371 | 0 | if (cmd->on_osd & MP_ON_OSD_MSG) |
6372 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; |
6373 | 0 | } |
6374 | | |
6375 | | static void cmd_playlist_play_index(void *p) |
6376 | 0 | { |
6377 | 0 | struct mp_cmd_ctx *cmd = p; |
6378 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6379 | 0 | struct playlist *pl = mpctx->playlist; |
6380 | 0 | int pos = cmd->args[0].v.i; |
6381 | 0 | bool preserve_options = cmd->num_args >= 2 && cmd->args[1].v.b; |
6382 | |
|
6383 | 0 | if (pos == -2) |
6384 | 0 | pos = playlist_entry_to_index(pl, pl->current); |
6385 | |
|
6386 | 0 | if (preserve_options && pl->current && pos == pl->current->pl_index) |
6387 | 0 | pl->current->reloading = true; |
6388 | |
|
6389 | 0 | mp_set_playlist_entry(mpctx, playlist_entry_from_index(pl, pos)); |
6390 | 0 | if (cmd->on_osd & MP_ON_OSD_MSG) |
6391 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_CURRENT_FILE; |
6392 | 0 | } |
6393 | | |
6394 | | static void cmd_sub_step_seek(void *p) |
6395 | 0 | { |
6396 | 0 | struct mp_cmd_ctx *cmd = p; |
6397 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6398 | 0 | bool step = *(bool *)cmd->priv; |
6399 | 0 | int track_ind = cmd->args[1].v.i; |
6400 | |
|
6401 | 0 | if (!mpctx->playback_initialized) { |
6402 | 0 | cmd->success = false; |
6403 | 0 | return; |
6404 | 0 | } |
6405 | | |
6406 | 0 | struct track *track = mpctx->current_track[track_ind][STREAM_SUB]; |
6407 | 0 | struct dec_sub *sub = track ? track->d_sub : NULL; |
6408 | 0 | double refpts = get_current_time(mpctx); |
6409 | 0 | if (sub && refpts != MP_NOPTS_VALUE) { |
6410 | 0 | double a[2]; |
6411 | 0 | a[0] = refpts; |
6412 | 0 | a[1] = cmd->args[0].v.i; |
6413 | 0 | if (sub_control(sub, SD_CTRL_SUB_STEP, a) > 0) { |
6414 | 0 | if (step) { |
6415 | 0 | mpctx->opts->subs_shared->sub_delay[track_ind] -= a[0] - refpts; |
6416 | 0 | m_config_notify_change_opt_ptr_notify(mpctx->mconfig, |
6417 | 0 | &mpctx->opts->subs_shared->sub_delay[track_ind]); |
6418 | 0 | show_property_osd( |
6419 | 0 | mpctx, |
6420 | 0 | track_ind == 0 ? "sub-delay" : "secondary-sub-delay", |
6421 | 0 | cmd->on_osd); |
6422 | 0 | } else { |
6423 | | // We can easily seek/step to the wrong subtitle line (because |
6424 | | // video frame PTS and sub PTS rarely match exactly). |
6425 | | // sub/sd_ass.c adds SUB_SEEK_OFFSET as a workaround, and we |
6426 | | // need an even bigger offset without a video. |
6427 | 0 | if (!mpctx->current_track[0][STREAM_VIDEO] || |
6428 | 0 | mpctx->current_track[0][STREAM_VIDEO]->image) { |
6429 | 0 | a[0] += SUB_SEEK_WITHOUT_VIDEO_OFFSET - SUB_SEEK_OFFSET; |
6430 | 0 | } |
6431 | 0 | mark_seek(mpctx); |
6432 | 0 | queue_seek(mpctx, MPSEEK_ABSOLUTE, a[0], MPSEEK_EXACT, |
6433 | 0 | MPSEEK_FLAG_DELAY); |
6434 | 0 | set_osd_function(mpctx, (a[0] > refpts) ? OSD_FFW : OSD_REW); |
6435 | 0 | if (cmd->seek_bar_osd) |
6436 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_BAR; |
6437 | 0 | if (cmd->seek_msg_osd) |
6438 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT; |
6439 | 0 | } |
6440 | 0 | } |
6441 | 0 | } |
6442 | 0 | } |
6443 | | |
6444 | | static void cmd_print_text(void *p) |
6445 | 0 | { |
6446 | 0 | struct mp_cmd_ctx *cmd = p; |
6447 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6448 | |
|
6449 | 0 | MP_INFO(mpctx, "%s\n", cmd->args[0].v.s); |
6450 | 0 | } |
6451 | | |
6452 | | static void cmd_show_text(void *p) |
6453 | 0 | { |
6454 | 0 | struct mp_cmd_ctx *cmd = p; |
6455 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6456 | 0 | int osd_duration = mpctx->opts->osd_duration; |
6457 | | |
6458 | | // if no argument supplied use default osd_duration, else <arg> ms. |
6459 | 0 | set_osd_msg(mpctx, cmd->args[2].v.i, |
6460 | 0 | (cmd->args[1].v.i < 0 ? osd_duration : cmd->args[1].v.i), |
6461 | 0 | "%s", cmd->args[0].v.s); |
6462 | 0 | } |
6463 | | |
6464 | | static void cmd_expand_text(void *p) |
6465 | 0 | { |
6466 | 0 | struct mp_cmd_ctx *cmd = p; |
6467 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6468 | |
|
6469 | 0 | cmd->result = (mpv_node){ |
6470 | 0 | .format = MPV_FORMAT_STRING, |
6471 | 0 | .u.string = mp_property_expand_string(mpctx, cmd->args[0].v.s) |
6472 | 0 | }; |
6473 | 0 | } |
6474 | | |
6475 | | static void cmd_expand_path(void *p) |
6476 | 0 | { |
6477 | 0 | struct mp_cmd_ctx *cmd = p; |
6478 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6479 | |
|
6480 | 0 | cmd->result = (mpv_node){ |
6481 | 0 | .format = MPV_FORMAT_STRING, |
6482 | 0 | .u.string = mp_get_user_path(NULL, mpctx->global, cmd->args[0].v.s) |
6483 | 0 | }; |
6484 | 0 | } |
6485 | | |
6486 | | static void cmd_normalize_path(void *p) |
6487 | 0 | { |
6488 | 0 | struct mp_cmd_ctx *cmd = p; |
6489 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6490 | |
|
6491 | 0 | char *path = mp_get_user_path(NULL, mpctx->global, cmd->args[0].v.s); |
6492 | 0 | cmd->result = (mpv_node){ |
6493 | 0 | .format = MPV_FORMAT_STRING, |
6494 | 0 | .u.string = mp_normalize_path(NULL, path), |
6495 | 0 | }; |
6496 | |
|
6497 | 0 | talloc_free(path); |
6498 | 0 | } |
6499 | | |
6500 | | static void cmd_escape_ass(void *p) |
6501 | 0 | { |
6502 | 0 | struct mp_cmd_ctx *cmd = p; |
6503 | 0 | bstr dst = {0}; |
6504 | |
|
6505 | 0 | osd_mangle_ass(&dst, cmd->args[0].v.s, true); |
6506 | |
|
6507 | 0 | cmd->result = (mpv_node){ |
6508 | 0 | .format = MPV_FORMAT_STRING, |
6509 | 0 | .u.string = dst.len ? (char *)dst.start : talloc_strdup(NULL, ""), |
6510 | 0 | }; |
6511 | 0 | } |
6512 | | |
6513 | | static struct load_action get_load_action(struct MPContext *mpctx, int action_flag) |
6514 | 171k | { |
6515 | 171k | int type = action_flag & 3; |
6516 | 171k | bool play = (action_flag >> 3) & 1; |
6517 | 171k | switch (type) { |
6518 | 171k | case 0: |
6519 | 171k | return (struct load_action){LOAD_TYPE_REPLACE, .play = play}; |
6520 | 0 | case 1: |
6521 | 0 | return (struct load_action){LOAD_TYPE_APPEND, .play = play}; |
6522 | 0 | case 2: |
6523 | 0 | return (struct load_action){LOAD_TYPE_INSERT_NEXT, .play = play}; |
6524 | 0 | case 3: |
6525 | 0 | return (struct load_action){LOAD_TYPE_INSERT_AT, .play = play}; |
6526 | 0 | default: // default: replace |
6527 | 0 | return (struct load_action){LOAD_TYPE_REPLACE, .play = true}; |
6528 | 171k | } |
6529 | 171k | } |
6530 | | |
6531 | | static struct playlist_entry *get_insert_entry(struct MPContext *mpctx, struct load_action *action, |
6532 | | int insert_at_idx) |
6533 | 171k | { |
6534 | 171k | switch (action->type) { |
6535 | 0 | case LOAD_TYPE_INSERT_NEXT: |
6536 | 0 | return playlist_get_next(mpctx->playlist, +1); |
6537 | 0 | case LOAD_TYPE_INSERT_AT: |
6538 | 0 | return playlist_entry_from_index(mpctx->playlist, insert_at_idx); |
6539 | 171k | case LOAD_TYPE_REPLACE: |
6540 | 171k | case LOAD_TYPE_APPEND: |
6541 | 171k | default: |
6542 | 171k | return NULL; |
6543 | 171k | } |
6544 | 171k | } |
6545 | | |
6546 | | static void cmd_loadfile(void *p) |
6547 | 171k | { |
6548 | 171k | struct mp_cmd_ctx *cmd = p; |
6549 | 171k | struct MPContext *mpctx = cmd->mpctx; |
6550 | 171k | char *filename = cmd->args[0].v.s; |
6551 | 171k | int action_flag = cmd->args[1].v.i; |
6552 | 171k | int insert_at_idx = cmd->args[2].v.i; |
6553 | | |
6554 | 171k | struct load_action action = get_load_action(mpctx, action_flag); |
6555 | | |
6556 | 171k | if (action.type == LOAD_TYPE_REPLACE) |
6557 | 171k | playlist_clear(mpctx->playlist); |
6558 | | |
6559 | 171k | char *path = mp_get_user_path(NULL, mpctx->global, filename); |
6560 | 171k | struct playlist_entry *entry = playlist_entry_new(path); |
6561 | 171k | talloc_free(path); |
6562 | 171k | if (cmd->args[3].v.str_list) { |
6563 | 0 | char **pairs = cmd->args[3].v.str_list; |
6564 | 0 | for (int i = 0; pairs[i] && pairs[i + 1]; i += 2) |
6565 | 0 | playlist_entry_add_param(entry, bstr0(pairs[i]), bstr0(pairs[i + 1])); |
6566 | 0 | } |
6567 | | |
6568 | 171k | struct playlist_entry *at = get_insert_entry(mpctx, &action, insert_at_idx); |
6569 | 171k | playlist_insert_at(mpctx->playlist, entry, at); |
6570 | | |
6571 | 171k | struct mpv_node *res = &cmd->result; |
6572 | 171k | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
6573 | 171k | node_map_add_int64(res, "playlist_entry_id", entry->id); |
6574 | | |
6575 | 171k | if (action.type == LOAD_TYPE_REPLACE || (action.play && !mpctx->playlist->current)) { |
6576 | 171k | if (mpctx->opts->position_save_on_quit) // requested in issue #1148 |
6577 | 1 | mp_write_watch_later_conf(mpctx); |
6578 | 171k | mp_set_playlist_entry(mpctx, entry); |
6579 | 171k | } |
6580 | 171k | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6581 | 171k | mp_wakeup_core(mpctx); |
6582 | 171k | } |
6583 | | |
6584 | | static void cmd_loadlist(void *p) |
6585 | 0 | { |
6586 | 0 | struct mp_cmd_ctx *cmd = p; |
6587 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6588 | 0 | char *filename = cmd->args[0].v.s; |
6589 | 0 | int action_flag = cmd->args[1].v.i; |
6590 | 0 | int insert_at_idx = cmd->args[2].v.i; |
6591 | |
|
6592 | 0 | struct load_action action = get_load_action(mpctx, action_flag); |
6593 | |
|
6594 | 0 | char *path = mp_get_user_path(NULL, mpctx->global, filename); |
6595 | 0 | struct playlist *pl = playlist_parse_file(path, cmd->abort->cancel, |
6596 | 0 | mpctx->global); |
6597 | 0 | talloc_free(path); |
6598 | |
|
6599 | 0 | if (pl) { |
6600 | 0 | prepare_playlist(mpctx, pl, true); |
6601 | 0 | struct playlist_entry *new = pl->current; |
6602 | 0 | if (action.type == LOAD_TYPE_REPLACE) |
6603 | 0 | playlist_clear(mpctx->playlist); |
6604 | 0 | struct playlist_entry *first = playlist_entry_from_index(pl, 0); |
6605 | 0 | int num_entries = pl->num_entries; |
6606 | |
|
6607 | 0 | struct playlist_entry *at = get_insert_entry(mpctx, &action, insert_at_idx); |
6608 | 0 | if (at == NULL) { |
6609 | 0 | playlist_append_entries(mpctx->playlist, pl); |
6610 | 0 | } else { |
6611 | 0 | int at_index = playlist_entry_to_index(mpctx->playlist, at); |
6612 | 0 | playlist_transfer_entries_to(mpctx->playlist, at_index, pl); |
6613 | 0 | } |
6614 | 0 | talloc_free(pl); |
6615 | |
|
6616 | 0 | if (!new) |
6617 | 0 | new = playlist_get_first(mpctx->playlist); |
6618 | |
|
6619 | 0 | if ((action.type == LOAD_TYPE_REPLACE || |
6620 | 0 | (action.play && !mpctx->playlist->current)) && new) { |
6621 | 0 | mp_set_playlist_entry(mpctx, new); |
6622 | 0 | } |
6623 | |
|
6624 | 0 | struct mpv_node *res = &cmd->result; |
6625 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
6626 | 0 | if (num_entries) { |
6627 | 0 | node_map_add_int64(res, "playlist_entry_id", first->id); |
6628 | 0 | node_map_add_int64(res, "num_entries", num_entries); |
6629 | 0 | } |
6630 | |
|
6631 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6632 | 0 | mp_wakeup_core(mpctx); |
6633 | 0 | } else { |
6634 | 0 | MP_ERR(mpctx, "Unable to load playlist %s.\n", filename); |
6635 | 0 | cmd->success = false; |
6636 | 0 | } |
6637 | 0 | } |
6638 | | |
6639 | | static void cmd_playlist_clear(void *p) |
6640 | 0 | { |
6641 | 0 | struct mp_cmd_ctx *cmd = p; |
6642 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6643 | | |
6644 | | // Supposed to clear the playlist, except the currently played item. |
6645 | 0 | if (mpctx->playlist->current_was_replaced) |
6646 | 0 | mpctx->playlist->current = NULL; |
6647 | 0 | playlist_clear_except_current(mpctx->playlist); |
6648 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6649 | 0 | mp_wakeup_core(mpctx); |
6650 | 0 | } |
6651 | | |
6652 | | static void cmd_playlist_remove(void *p) |
6653 | 0 | { |
6654 | 0 | struct mp_cmd_ctx *cmd = p; |
6655 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6656 | |
|
6657 | 0 | struct playlist_entry *e = playlist_entry_from_index(mpctx->playlist, |
6658 | 0 | cmd->args[0].v.i); |
6659 | 0 | if (cmd->args[0].v.i < 0) |
6660 | 0 | e = mpctx->playlist->current; |
6661 | 0 | if (!e) { |
6662 | 0 | cmd->success = false; |
6663 | 0 | return; |
6664 | 0 | } |
6665 | | |
6666 | | // Can't play a removed entry |
6667 | 0 | if (mpctx->playlist->current == e && !mpctx->stop_play) |
6668 | 0 | mpctx->stop_play = PT_NEXT_ENTRY; |
6669 | 0 | playlist_remove(mpctx->playlist, e); |
6670 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6671 | 0 | mp_wakeup_core(mpctx); |
6672 | 0 | } |
6673 | | |
6674 | | static void cmd_playlist_move(void *p) |
6675 | 0 | { |
6676 | 0 | struct mp_cmd_ctx *cmd = p; |
6677 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6678 | |
|
6679 | 0 | struct playlist_entry *e1 = playlist_entry_from_index(mpctx->playlist, |
6680 | 0 | cmd->args[0].v.i); |
6681 | 0 | struct playlist_entry *e2 = playlist_entry_from_index(mpctx->playlist, |
6682 | 0 | cmd->args[1].v.i); |
6683 | 0 | if (!e1) { |
6684 | 0 | cmd->success = false; |
6685 | 0 | return; |
6686 | 0 | } |
6687 | | |
6688 | 0 | playlist_move(mpctx->playlist, e1, e2); |
6689 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6690 | 0 | } |
6691 | | |
6692 | | static void cmd_playlist_shuffle(void *p) |
6693 | 0 | { |
6694 | 0 | struct mp_cmd_ctx *cmd = p; |
6695 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6696 | |
|
6697 | 0 | playlist_shuffle(mpctx->playlist); |
6698 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6699 | 0 | } |
6700 | | |
6701 | | static void cmd_playlist_unshuffle(void *p) |
6702 | 0 | { |
6703 | 0 | struct mp_cmd_ctx *cmd = p; |
6704 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6705 | |
|
6706 | 0 | playlist_unshuffle(mpctx->playlist); |
6707 | 0 | mp_notify(mpctx, MP_EVENT_CHANGE_PLAYLIST, NULL); |
6708 | 0 | } |
6709 | | |
6710 | | static void cmd_stop(void *p) |
6711 | 0 | { |
6712 | 0 | struct mp_cmd_ctx *cmd = p; |
6713 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6714 | 0 | int flags = cmd->args[0].v.i; |
6715 | |
|
6716 | 0 | if (!(flags & 1)) |
6717 | 0 | playlist_clear(mpctx->playlist); |
6718 | |
|
6719 | 0 | if (mpctx->opts->player_idle_mode < 2 && |
6720 | 0 | mpctx->opts->position_save_on_quit) |
6721 | 0 | { |
6722 | 0 | mp_write_watch_later_conf(mpctx); |
6723 | 0 | } |
6724 | |
|
6725 | 0 | if (mpctx->stop_play != PT_QUIT) |
6726 | 0 | mpctx->stop_play = PT_STOP; |
6727 | 0 | mp_wakeup_core(mpctx); |
6728 | 0 | } |
6729 | | |
6730 | | static void cmd_show_progress(void *p) |
6731 | 0 | { |
6732 | 0 | struct mp_cmd_ctx *cmd = p; |
6733 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6734 | |
|
6735 | 0 | mpctx->add_osd_seek_info |= |
6736 | 0 | (cmd->msg_osd ? OSD_SEEK_INFO_TEXT : 0) | |
6737 | 0 | (cmd->bar_osd ? OSD_SEEK_INFO_BAR : 0); |
6738 | | |
6739 | | // If we got neither (i.e. no-osd) force both like osd-auto. |
6740 | 0 | if (!mpctx->add_osd_seek_info) |
6741 | 0 | mpctx->add_osd_seek_info |= OSD_SEEK_INFO_TEXT | OSD_SEEK_INFO_BAR; |
6742 | 0 | mpctx->osd_force_update = true; |
6743 | 0 | mp_wakeup_core(mpctx); |
6744 | 0 | } |
6745 | | |
6746 | | static void cmd_track_add(void *p) |
6747 | 0 | { |
6748 | 0 | struct mp_cmd_ctx *cmd = p; |
6749 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6750 | 0 | int type = *(int *)cmd->priv; |
6751 | 0 | int select = cmd->args[1].v.i & 3; |
6752 | 0 | enum track_flags flags = cmd->args[1].v.i & ~3; |
6753 | 0 | if (type == STREAM_VIDEO && cmd->args[4].v.b) |
6754 | 0 | flags |= TRACK_ATTACHED_PICTURE; |
6755 | |
|
6756 | 0 | if (mpctx->stop_play) { |
6757 | 0 | cmd->success = false; |
6758 | 0 | return; |
6759 | 0 | } |
6760 | | |
6761 | 0 | if (select == 2) { |
6762 | 0 | struct track *t = find_track_with_url(mpctx, type, cmd->args[0].v.s); |
6763 | 0 | if (t) { |
6764 | 0 | if (mpctx->playback_initialized) { |
6765 | 0 | mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); |
6766 | 0 | print_track_list(mpctx, "Track switched:"); |
6767 | 0 | } else { |
6768 | 0 | mark_track_selection(mpctx, 0, t->type, t->user_tid); |
6769 | 0 | } |
6770 | 0 | return; |
6771 | 0 | } |
6772 | 0 | } |
6773 | 0 | int first = mp_add_external_file(mpctx, cmd->args[0].v.s, type, |
6774 | 0 | cmd->abort->cancel, flags); |
6775 | 0 | if (first < 0) { |
6776 | 0 | cmd->success = false; |
6777 | 0 | return; |
6778 | 0 | } |
6779 | | |
6780 | 0 | for (int n = first; n < mpctx->num_tracks; n++) { |
6781 | 0 | struct track *t = mpctx->tracks[n]; |
6782 | 0 | if (select == 1) { |
6783 | 0 | t->no_default = true; |
6784 | 0 | } else if (n == first) { |
6785 | 0 | if (mpctx->playback_initialized) { |
6786 | 0 | mp_switch_track(mpctx, t->type, t, FLAG_MARK_SELECTION); |
6787 | 0 | } else { |
6788 | 0 | mark_track_selection(mpctx, 0, t->type, t->user_tid); |
6789 | 0 | } |
6790 | 0 | } |
6791 | |
|
6792 | 0 | flags = 0; |
6793 | 0 | bstr lang = mp_guess_lang_from_filename(bstr0(t->external_filename), NULL, |
6794 | 0 | &flags); |
6795 | 0 | t->lang = bstrdup0(t, lang); |
6796 | 0 | t->hearing_impaired_track = flags & TRACK_HEARING_IMPAIRED; |
6797 | 0 | t->forced_track = flags & TRACK_FORCED; |
6798 | 0 | t->default_track = flags & TRACK_DEFAULT; |
6799 | |
|
6800 | 0 | char *title = cmd->args[2].v.s; |
6801 | 0 | if (title && title[0]) |
6802 | 0 | t->title = talloc_strdup(t, title); |
6803 | 0 | char *lang_arg = cmd->args[3].v.s; |
6804 | 0 | if (lang_arg && lang_arg[0]) |
6805 | 0 | talloc_replace(t, t->lang, lang_arg); |
6806 | 0 | } |
6807 | |
|
6808 | 0 | if (mpctx->playback_initialized) |
6809 | 0 | print_track_list(mpctx, "Track added:"); |
6810 | 0 | } |
6811 | | |
6812 | | static void cmd_track_remove(void *p) |
6813 | 0 | { |
6814 | 0 | struct mp_cmd_ctx *cmd = p; |
6815 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6816 | 0 | int type = *(int *)cmd->priv; |
6817 | |
|
6818 | 0 | struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); |
6819 | 0 | if (!t) { |
6820 | 0 | cmd->success = false; |
6821 | 0 | return; |
6822 | 0 | } |
6823 | | |
6824 | 0 | mp_remove_track(mpctx, t); |
6825 | 0 | if (mpctx->playback_initialized) |
6826 | 0 | print_track_list(mpctx, "Track removed:"); |
6827 | 0 | } |
6828 | | |
6829 | | static void cmd_track_reload(void *p) |
6830 | 0 | { |
6831 | 0 | struct mp_cmd_ctx *cmd = p; |
6832 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6833 | 0 | int type = *(int *)cmd->priv; |
6834 | |
|
6835 | 0 | if (!mpctx->playback_initialized) { |
6836 | 0 | MP_ERR(mpctx, "Cannot reload while not initialized.\n"); |
6837 | 0 | cmd->success = false; |
6838 | 0 | return; |
6839 | 0 | } |
6840 | | |
6841 | 0 | struct track *t = mp_track_by_tid(mpctx, type, cmd->args[0].v.i); |
6842 | 0 | int nt_num = -1; |
6843 | 0 | char *lang = NULL; |
6844 | |
|
6845 | 0 | if (t && t->is_external && t->external_filename) { |
6846 | 0 | char *filename = talloc_strdup(NULL, t->external_filename); |
6847 | 0 | lang = talloc_steal(NULL, t->lang); |
6848 | 0 | enum track_flags flags = 0; |
6849 | 0 | flags |= t->attached_picture ? TRACK_ATTACHED_PICTURE : 0; |
6850 | 0 | flags |= t->hearing_impaired_track ? TRACK_HEARING_IMPAIRED : 0; |
6851 | 0 | flags |= t->visual_impaired_track ? TRACK_VISUAL_IMPAIRED : 0; |
6852 | 0 | flags |= t->forced_track ? TRACK_FORCED : 0; |
6853 | 0 | flags |= t->default_track ? TRACK_DEFAULT : 0; |
6854 | 0 | flags |= t->commentary_track ? TRACK_COMMENTARY : 0; |
6855 | 0 | mp_remove_track(mpctx, t); |
6856 | 0 | nt_num = mp_add_external_file(mpctx, filename, type, cmd->abort->cancel, |
6857 | 0 | flags); |
6858 | 0 | talloc_free(filename); |
6859 | 0 | } |
6860 | |
|
6861 | 0 | if (nt_num < 0) { |
6862 | 0 | cmd->success = false; |
6863 | 0 | talloc_free(lang); |
6864 | 0 | return; |
6865 | 0 | } |
6866 | | |
6867 | 0 | struct track *nt = mpctx->tracks[nt_num]; |
6868 | |
|
6869 | 0 | if (nt->lang) { |
6870 | 0 | talloc_free(lang); |
6871 | 0 | } else { |
6872 | 0 | nt->lang = talloc_steal(nt, lang); |
6873 | 0 | } |
6874 | |
|
6875 | 0 | mp_switch_track(mpctx, nt->type, nt, 0); |
6876 | 0 | print_track_list(mpctx, "Reloaded:"); |
6877 | 0 | } |
6878 | | |
6879 | | static void cmd_rescan_external_files(void *p) |
6880 | 0 | { |
6881 | 0 | struct mp_cmd_ctx *cmd = p; |
6882 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6883 | |
|
6884 | 0 | if (mpctx->stop_play) { |
6885 | 0 | cmd->success = false; |
6886 | 0 | return; |
6887 | 0 | } |
6888 | | |
6889 | 0 | autoload_external_files(mpctx, cmd->abort->cancel); |
6890 | 0 | if (!cmd->args[0].v.i && mpctx->playback_initialized) { |
6891 | | // somewhat fuzzy and not ideal |
6892 | 0 | struct track *a = select_default_track(mpctx, 0, STREAM_AUDIO); |
6893 | 0 | if (a && a->is_external) |
6894 | 0 | mp_switch_track(mpctx, STREAM_AUDIO, a, 0); |
6895 | 0 | struct track *s = select_default_track(mpctx, 0, STREAM_SUB); |
6896 | 0 | if (s && s->is_external) |
6897 | 0 | mp_switch_track(mpctx, STREAM_SUB, s, 0); |
6898 | |
|
6899 | 0 | print_track_list(mpctx, "Track list:"); |
6900 | 0 | } |
6901 | 0 | } |
6902 | | |
6903 | | static void cmd_run(void *p) |
6904 | 0 | { |
6905 | 0 | struct mp_cmd_ctx *cmd = p; |
6906 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6907 | 0 | char **args = talloc_zero_array(NULL, char *, cmd->num_args + 1); |
6908 | 0 | for (int n = 0; n < cmd->num_args; n++) |
6909 | 0 | args[n] = cmd->args[n].v.s; |
6910 | 0 | mp_msg_flush_status_line(mpctx->log, true); |
6911 | 0 | struct mp_subprocess_opts opts = { |
6912 | 0 | .exe = args[0], |
6913 | 0 | .args = args, |
6914 | 0 | .fds = { {0, .src_fd = 0}, {1, .src_fd = 1}, {2, .src_fd = 2} }, |
6915 | 0 | .num_fds = 3, |
6916 | 0 | .detach = true, |
6917 | 0 | }; |
6918 | 0 | struct mp_subprocess_result res; |
6919 | 0 | mp_subprocess(mpctx->log, &opts, &res); |
6920 | 0 | talloc_free(args); |
6921 | 0 | } |
6922 | | |
6923 | | struct subprocess_fd_ctx { |
6924 | | struct mp_log *log; |
6925 | | void* talloc_ctx; |
6926 | | int64_t max_size; |
6927 | | int msgl; |
6928 | | bool capture; |
6929 | | bstr output; |
6930 | | }; |
6931 | | |
6932 | | static void subprocess_read(void *p, char *data, size_t size) |
6933 | 0 | { |
6934 | 0 | struct subprocess_fd_ctx *ctx = p; |
6935 | 0 | if (ctx->capture) { |
6936 | 0 | if (ctx->output.len < ctx->max_size) |
6937 | 0 | bstr_xappend(ctx->talloc_ctx, &ctx->output, (bstr){data, size}); |
6938 | 0 | } else { |
6939 | 0 | mp_msg(ctx->log, ctx->msgl, "%.*s", (int)size, data); |
6940 | 0 | } |
6941 | 0 | } |
6942 | | |
6943 | | static void subprocess_write(void *p) |
6944 | 0 | { |
6945 | | // Unused; we write a full buffer. |
6946 | 0 | } |
6947 | | |
6948 | | static void cmd_subprocess(void *p) |
6949 | 0 | { |
6950 | 0 | struct mp_cmd_ctx *cmd = p; |
6951 | 0 | struct MPContext *mpctx = cmd->mpctx; |
6952 | 0 | char **args = cmd->args[0].v.str_list; |
6953 | 0 | bool playback_only = cmd->args[1].v.b; |
6954 | 0 | bool detach = cmd->args[5].v.b; |
6955 | 0 | char **env = cmd->args[6].v.str_list; |
6956 | 0 | bstr stdin_data = bstr0(cmd->args[7].v.s); |
6957 | 0 | bool passthrough_stdin = cmd->args[8].v.b; |
6958 | |
|
6959 | 0 | if (env && !env[0]) |
6960 | 0 | env = NULL; // do not actually set an empty environment |
6961 | |
|
6962 | 0 | if (!args || !args[0]) { |
6963 | 0 | MP_ERR(mpctx, "program name missing\n"); |
6964 | 0 | cmd->success = false; |
6965 | 0 | return; |
6966 | 0 | } |
6967 | | |
6968 | 0 | if (stdin_data.len && passthrough_stdin) { |
6969 | 0 | MP_ERR(mpctx, "both stdin_data and passthrough_stdin set\n"); |
6970 | 0 | cmd->success = false; |
6971 | 0 | return; |
6972 | 0 | } |
6973 | | |
6974 | 0 | void *tmp = talloc_new(NULL); |
6975 | |
|
6976 | 0 | struct mp_log *fdlog = mp_log_new(tmp, mpctx->log, cmd->cmd->sender); |
6977 | 0 | struct subprocess_fd_ctx fdctx[3]; |
6978 | 0 | for (int fd = 0; fd < 3; fd++) { |
6979 | 0 | fdctx[fd] = (struct subprocess_fd_ctx) { |
6980 | 0 | .log = fdlog, |
6981 | 0 | .talloc_ctx = tmp, |
6982 | 0 | .max_size = cmd->args[2].v.i, |
6983 | 0 | .msgl = fd == 2 ? MSGL_ERR : MSGL_INFO, |
6984 | 0 | }; |
6985 | 0 | } |
6986 | 0 | fdctx[1].capture = cmd->args[3].v.b; |
6987 | 0 | fdctx[2].capture = cmd->args[4].v.b; |
6988 | |
|
6989 | 0 | mp_mutex_lock(&mpctx->abort_lock); |
6990 | 0 | cmd->abort->coupled_to_playback = playback_only; |
6991 | 0 | mp_abort_recheck_locked(mpctx, cmd->abort); |
6992 | 0 | mp_mutex_unlock(&mpctx->abort_lock); |
6993 | |
|
6994 | 0 | mp_core_unlock(mpctx); |
6995 | |
|
6996 | 0 | struct mp_subprocess_opts opts = { |
6997 | 0 | .exe = args[0], |
6998 | 0 | .args = args, |
6999 | 0 | .env = env, |
7000 | 0 | .cancel = cmd->abort->cancel, |
7001 | 0 | .detach = detach, |
7002 | 0 | .fds = { |
7003 | 0 | { |
7004 | 0 | .fd = 0, // stdin |
7005 | 0 | .src_fd = passthrough_stdin ? 0 : -1, |
7006 | 0 | }, |
7007 | 0 | }, |
7008 | 0 | .num_fds = 1, |
7009 | 0 | }; |
7010 | | |
7011 | | // stdout, stderr |
7012 | 0 | for (int fd = 1; fd < 3; fd++) { |
7013 | 0 | bool capture = fdctx[fd].capture || !detach; |
7014 | 0 | opts.fds[opts.num_fds++] = (struct mp_subprocess_fd){ |
7015 | 0 | .fd = fd, |
7016 | 0 | .src_fd = capture ? -1 : fd, |
7017 | 0 | .on_read = capture ? subprocess_read : NULL, |
7018 | 0 | .on_read_ctx = &fdctx[fd], |
7019 | 0 | }; |
7020 | 0 | } |
7021 | | // stdin |
7022 | 0 | if (stdin_data.len) { |
7023 | 0 | opts.fds[0] = (struct mp_subprocess_fd){ |
7024 | 0 | .fd = 0, |
7025 | 0 | .src_fd = -1, |
7026 | 0 | .on_write = subprocess_write, |
7027 | 0 | .on_write_ctx = &fdctx[0], |
7028 | 0 | .write_buf = &stdin_data, |
7029 | 0 | }; |
7030 | 0 | } |
7031 | |
|
7032 | 0 | struct mp_subprocess_result sres; |
7033 | 0 | mp_subprocess(fdlog, &opts, &sres); |
7034 | 0 | int status = sres.exit_status; |
7035 | 0 | char *error = NULL; |
7036 | 0 | if (sres.error < 0) { |
7037 | 0 | error = (char *)mp_subprocess_err_str(sres.error); |
7038 | 0 | status = sres.error; |
7039 | 0 | } |
7040 | |
|
7041 | 0 | mp_core_lock(mpctx); |
7042 | |
|
7043 | 0 | struct mpv_node *res = &cmd->result; |
7044 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
7045 | 0 | node_map_add_int64(res, "status", status); |
7046 | 0 | node_map_add_flag(res, "killed_by_us", status == MP_SUBPROCESS_EKILLED_BY_US); |
7047 | 0 | node_map_add_string(res, "error_string", error ? error : ""); |
7048 | 0 | const char *sname[] = {NULL, "stdout", "stderr"}; |
7049 | 0 | for (int fd = 1; fd < 3; fd++) { |
7050 | 0 | if (!fdctx[fd].capture) |
7051 | 0 | continue; |
7052 | 0 | struct mpv_byte_array *ba = |
7053 | 0 | node_map_add(res, sname[fd], MPV_FORMAT_BYTE_ARRAY)->u.ba; |
7054 | 0 | *ba = (struct mpv_byte_array){ |
7055 | 0 | .data = talloc_steal(ba, fdctx[fd].output.start), |
7056 | 0 | .size = fdctx[fd].output.len, |
7057 | 0 | }; |
7058 | 0 | } |
7059 | |
|
7060 | 0 | talloc_free(tmp); |
7061 | 0 | } |
7062 | | |
7063 | | static void cmd_enable_input_section(void *p) |
7064 | 0 | { |
7065 | 0 | struct mp_cmd_ctx *cmd = p; |
7066 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7067 | 0 | mp_input_enable_section(mpctx->input, cmd->args[0].v.s, cmd->args[1].v.i); |
7068 | 0 | } |
7069 | | |
7070 | | static void cmd_disable_input_section(void *p) |
7071 | 0 | { |
7072 | 0 | struct mp_cmd_ctx *cmd = p; |
7073 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7074 | 0 | mp_input_disable_section(mpctx->input, cmd->args[0].v.s); |
7075 | 0 | } |
7076 | | |
7077 | | static void cmd_define_input_section(void *p) |
7078 | 0 | { |
7079 | 0 | struct mp_cmd_ctx *cmd = p; |
7080 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7081 | 0 | mp_input_define_section(mpctx->input, cmd->args[0].v.s, "<api>", |
7082 | 0 | cmd->args[1].v.s, !cmd->args[2].v.i, |
7083 | 0 | cmd->cmd->sender); |
7084 | 0 | } |
7085 | | |
7086 | | static void cmd_ab_loop(void *p) |
7087 | 0 | { |
7088 | 0 | struct mp_cmd_ctx *cmd = p; |
7089 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7090 | 0 | int osd_duration = mpctx->opts->osd_duration; |
7091 | 0 | int osdl = cmd->msg_osd ? 1 : OSD_LEVEL_INVISIBLE; |
7092 | |
|
7093 | 0 | double now = get_current_time(mpctx); |
7094 | 0 | if (mpctx->opts->ab_loop[0] == MP_NOPTS_VALUE) { |
7095 | 0 | mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); |
7096 | 0 | show_property_osd(mpctx, "ab-loop-a", cmd->on_osd); |
7097 | 0 | } else if (mpctx->opts->ab_loop[1] == MP_NOPTS_VALUE) { |
7098 | 0 | mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); |
7099 | 0 | show_property_osd(mpctx, "ab-loop-b", cmd->on_osd); |
7100 | 0 | } else { |
7101 | 0 | now = MP_NOPTS_VALUE; |
7102 | 0 | mp_property_do("ab-loop-a", M_PROPERTY_SET, &now, mpctx); |
7103 | 0 | mp_property_do("ab-loop-b", M_PROPERTY_SET, &now, mpctx); |
7104 | 0 | set_osd_msg(mpctx, osdl, osd_duration, "Clear A-B loop"); |
7105 | 0 | } |
7106 | 0 | } |
7107 | | |
7108 | | static void cmd_align_cache_ab(void *p) |
7109 | 0 | { |
7110 | 0 | struct mp_cmd_ctx *cmd = p; |
7111 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7112 | |
|
7113 | 0 | if (!mpctx->demuxer) |
7114 | 0 | return; |
7115 | | |
7116 | 0 | double a = demux_probe_cache_dump_target(mpctx->demuxer, |
7117 | 0 | mpctx->opts->ab_loop[0], false); |
7118 | 0 | double b = demux_probe_cache_dump_target(mpctx->demuxer, |
7119 | 0 | mpctx->opts->ab_loop[1], true); |
7120 | |
|
7121 | 0 | mp_property_do("ab-loop-a", M_PROPERTY_SET, &a, mpctx); |
7122 | 0 | mp_property_do("ab-loop-b", M_PROPERTY_SET, &b, mpctx); |
7123 | | |
7124 | | // Happens to cover both properties. |
7125 | 0 | show_property_osd(mpctx, "ab-loop-b", cmd->on_osd); |
7126 | 0 | } |
7127 | | |
7128 | | static void cmd_drop_buffers(void *p) |
7129 | 0 | { |
7130 | 0 | struct mp_cmd_ctx *cmd = p; |
7131 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7132 | |
|
7133 | 0 | reset_playback_state(mpctx); |
7134 | |
|
7135 | 0 | if (mpctx->demuxer) |
7136 | 0 | demux_flush(mpctx->demuxer); |
7137 | 0 | } |
7138 | | |
7139 | | static void cmd_ao_reload(void *p) |
7140 | 0 | { |
7141 | 0 | struct mp_cmd_ctx *cmd = p; |
7142 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7143 | 0 | reload_audio_output(mpctx); |
7144 | 0 | } |
7145 | | |
7146 | | static void cmd_filter(void *p) |
7147 | 0 | { |
7148 | 0 | struct mp_cmd_ctx *cmd = p; |
7149 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7150 | 0 | int type = *(int *)cmd->priv; |
7151 | 0 | cmd->success = edit_filters_osd(mpctx, type, cmd->args[0].v.s, |
7152 | 0 | cmd->args[1].v.s, cmd->msg_osd) >= 0; |
7153 | 0 | } |
7154 | | |
7155 | | static void cmd_filter_command(void *p) |
7156 | 0 | { |
7157 | 0 | struct mp_cmd_ctx *cmd = p; |
7158 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7159 | 0 | int type = *(int *)cmd->priv; |
7160 | |
|
7161 | 0 | struct mp_output_chain *chain = NULL; |
7162 | 0 | if (type == STREAM_VIDEO) |
7163 | 0 | chain = mpctx->vo_chain ? mpctx->vo_chain->filter : NULL; |
7164 | 0 | if (type == STREAM_AUDIO) |
7165 | 0 | chain = mpctx->ao_chain ? mpctx->ao_chain->filter : NULL; |
7166 | 0 | if (!chain) { |
7167 | 0 | cmd->success = false; |
7168 | 0 | return; |
7169 | 0 | } |
7170 | 0 | struct mp_filter_command filter_cmd = { |
7171 | 0 | .type = MP_FILTER_COMMAND_TEXT, |
7172 | 0 | .target = cmd->args[3].v.s, |
7173 | 0 | .cmd = cmd->args[1].v.s, |
7174 | 0 | .arg = cmd->args[2].v.s, |
7175 | 0 | }; |
7176 | 0 | cmd->success = mp_output_chain_command(chain, cmd->args[0].v.s, &filter_cmd); |
7177 | 0 | } |
7178 | | |
7179 | | static void cmd_script_binding(void *p) |
7180 | 0 | { |
7181 | 0 | struct mp_cmd_ctx *cmd = p; |
7182 | 0 | struct mp_cmd *incmd = cmd->cmd; |
7183 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7184 | |
|
7185 | 0 | mpv_event_client_message event = {0}; |
7186 | 0 | char *name = cmd->args[0].v.s; |
7187 | 0 | if (!name || !name[0]) { |
7188 | 0 | cmd->success = false; |
7189 | 0 | return; |
7190 | 0 | } |
7191 | | |
7192 | 0 | char *sep = strchr(name, '/'); |
7193 | 0 | char *target = NULL; |
7194 | 0 | char space[MAX_CLIENT_NAME]; |
7195 | 0 | if (sep) { |
7196 | 0 | snprintf(space, sizeof(space), "%.*s", (int)(sep - name), name); |
7197 | 0 | target = space; |
7198 | 0 | name = sep + 1; |
7199 | 0 | } |
7200 | 0 | char state[4] = {'p', incmd->is_mouse_button ? 'm' : '-', |
7201 | 0 | incmd->canceled ? 'c' : '-'}; |
7202 | 0 | if (incmd->is_up_down) |
7203 | 0 | state[0] = incmd->repeated ? 'r' : (incmd->is_up ? 'u' : 'd'); |
7204 | |
|
7205 | 0 | double scale = 1; |
7206 | 0 | int scale_units = incmd->scale_units; |
7207 | 0 | if (mp_input_is_scalable_cmd(incmd)) { |
7208 | 0 | scale = incmd->scale; |
7209 | 0 | scale_units = 1; |
7210 | 0 | } |
7211 | 0 | char *scale_s = mp_format_double(NULL, scale, 6, false, false, false); |
7212 | |
|
7213 | 0 | for (int i = 0; i < scale_units; i++) { |
7214 | 0 | event.num_args = 7; |
7215 | 0 | event.args = (const char*[7]){"key-binding", name, state, |
7216 | 0 | incmd->key_name ? incmd->key_name : "", |
7217 | 0 | incmd->key_text ? incmd->key_text : "", |
7218 | 0 | scale_s, cmd->args[1].v.s}; |
7219 | 0 | if (mp_client_send_event_dup(mpctx, target, |
7220 | 0 | MPV_EVENT_CLIENT_MESSAGE, &event) < 0) |
7221 | 0 | { |
7222 | 0 | MP_VERBOSE(mpctx, "Can't find script '%s' when handling input.\n", |
7223 | 0 | target ? target : "-"); |
7224 | 0 | cmd->success = false; |
7225 | 0 | break; |
7226 | 0 | } |
7227 | 0 | } |
7228 | 0 | talloc_free(scale_s); |
7229 | 0 | } |
7230 | | |
7231 | | static void cmd_script_message_to(void *p) |
7232 | 0 | { |
7233 | 0 | struct mp_cmd_ctx *cmd = p; |
7234 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7235 | |
|
7236 | 0 | mpv_event_client_message *event = talloc_ptrtype(NULL, event); |
7237 | 0 | *event = (mpv_event_client_message){0}; |
7238 | 0 | for (int n = 1; n < cmd->num_args; n++) { |
7239 | 0 | MP_TARRAY_APPEND(event, event->args, event->num_args, |
7240 | 0 | talloc_strdup(event, cmd->args[n].v.s)); |
7241 | 0 | } |
7242 | 0 | if (mp_client_send_event(mpctx, cmd->args[0].v.s, 0, |
7243 | 0 | MPV_EVENT_CLIENT_MESSAGE, event) < 0) |
7244 | 0 | { |
7245 | 0 | MP_VERBOSE(mpctx, "Can't find script '%s' to send message to.\n", |
7246 | 0 | cmd->args[0].v.s); |
7247 | 0 | cmd->success = false; |
7248 | 0 | } |
7249 | 0 | } |
7250 | | |
7251 | | static void cmd_script_message(void *p) |
7252 | 0 | { |
7253 | 0 | struct mp_cmd_ctx *cmd = p; |
7254 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7255 | |
|
7256 | 0 | const char **args = talloc_array(NULL, const char *, cmd->num_args); |
7257 | 0 | mpv_event_client_message event = {.args = args}; |
7258 | 0 | for (int n = 0; n < cmd->num_args; n++) |
7259 | 0 | event.args[event.num_args++] = cmd->args[n].v.s; |
7260 | 0 | mp_client_broadcast_event(mpctx, MPV_EVENT_CLIENT_MESSAGE, &event); |
7261 | 0 | talloc_free(args); |
7262 | 0 | } |
7263 | | |
7264 | | static void cmd_ignore(void *p) |
7265 | 0 | { |
7266 | 0 | } |
7267 | | |
7268 | | static void cmd_write_watch_later_config(void *p) |
7269 | 0 | { |
7270 | 0 | struct mp_cmd_ctx *cmd = p; |
7271 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7272 | |
|
7273 | 0 | mp_write_watch_later_conf(mpctx); |
7274 | 0 | } |
7275 | | |
7276 | | static void cmd_delete_watch_later_config(void *p) |
7277 | 0 | { |
7278 | 0 | struct mp_cmd_ctx *cmd = p; |
7279 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7280 | |
|
7281 | 0 | char *filename = cmd->args[0].v.s; |
7282 | 0 | if (filename && !filename[0]) |
7283 | 0 | filename = NULL; |
7284 | 0 | mp_delete_watch_later_conf(mpctx, filename); |
7285 | 0 | } |
7286 | | |
7287 | | static void cmd_mouse(void *p) |
7288 | 0 | { |
7289 | 0 | struct mp_cmd_ctx *cmd = p; |
7290 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7291 | 0 | int pre_key = 0; |
7292 | |
|
7293 | 0 | const int x = cmd->args[0].v.i, y = cmd->args[1].v.i; |
7294 | 0 | int button = cmd->args[2].v.i; |
7295 | |
|
7296 | 0 | if (mpctx->video_out && mpctx->video_out->config_ok) { |
7297 | 0 | int oldx, oldy, oldhover; |
7298 | 0 | mp_input_get_mouse_pos(mpctx->input, &oldx, &oldy, &oldhover); |
7299 | 0 | struct mp_osd_res vo_res = osd_get_vo_res(mpctx->osd); |
7300 | |
|
7301 | 0 | int hover = x >= 0 && y >= 0 && x < vo_res.w && y < vo_res.h; |
7302 | |
|
7303 | 0 | if (vo_res.w && vo_res.h && hover != oldhover) |
7304 | 0 | pre_key = hover ? MP_KEY_MOUSE_ENTER : MP_KEY_MOUSE_LEAVE; |
7305 | 0 | } |
7306 | |
|
7307 | 0 | if (button == -1) {// no button |
7308 | 0 | if (pre_key) |
7309 | 0 | mp_input_put_key_artificial(mpctx->input, pre_key, 1); |
7310 | 0 | mp_input_set_mouse_pos_artificial(mpctx->input, x, y); |
7311 | 0 | return; |
7312 | 0 | } |
7313 | 0 | if (button < 0 || button >= MP_KEY_MOUSE_BTN_COUNT) {// invalid button |
7314 | 0 | MP_ERR(mpctx, "%d is not a valid mouse button number.\n", button); |
7315 | 0 | cmd->success = false; |
7316 | 0 | return; |
7317 | 0 | } |
7318 | 0 | const bool dbc = cmd->args[3].v.i; |
7319 | 0 | if (dbc && button > (MP_MBTN_RIGHT - MP_MBTN_BASE)) { |
7320 | 0 | MP_ERR(mpctx, "%d is not a valid mouse button for double-clicks.\n", |
7321 | 0 | button); |
7322 | 0 | cmd->success = false; |
7323 | 0 | return; |
7324 | 0 | } |
7325 | 0 | button += dbc ? MP_MBTN_DBL_BASE : MP_MBTN_BASE; |
7326 | 0 | if (pre_key) |
7327 | 0 | mp_input_put_key_artificial(mpctx->input, pre_key, 1); |
7328 | 0 | mp_input_set_mouse_pos_artificial(mpctx->input, x, y); |
7329 | 0 | mp_input_put_key_artificial(mpctx->input, button, 1); |
7330 | 0 | } |
7331 | | |
7332 | | static void cmd_key(void *p) |
7333 | 0 | { |
7334 | 0 | struct mp_cmd_ctx *cmd = p; |
7335 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7336 | 0 | int action = *(int *)cmd->priv; |
7337 | |
|
7338 | 0 | const char *key_name = cmd->args[0].v.s; |
7339 | 0 | if ((!key_name || key_name[0] == '\0') && action == MP_KEY_STATE_UP) { |
7340 | 0 | mp_input_put_key_artificial(mpctx->input, MP_INPUT_RELEASE_ALL, 1); |
7341 | 0 | } else { |
7342 | 0 | int code = mp_input_get_key_from_name(key_name); |
7343 | 0 | if (code < 0) { |
7344 | 0 | MP_ERR(mpctx, "'%s' is not a valid input name.\n", key_name); |
7345 | 0 | cmd->success = false; |
7346 | 0 | return; |
7347 | 0 | } |
7348 | 0 | double scale = action == 0 ? cmd->args[1].v.d : 1; |
7349 | 0 | mp_input_put_key_artificial(mpctx->input, code | action, scale); |
7350 | 0 | } |
7351 | 0 | } |
7352 | | |
7353 | | static void cmd_key_bind(void *p) |
7354 | 0 | { |
7355 | 0 | struct mp_cmd_ctx *cmd = p; |
7356 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7357 | |
|
7358 | 0 | const char *key = cmd->args[0].v.s; |
7359 | 0 | const char *target_cmd = cmd->args[1].v.s; |
7360 | 0 | const char *comment = cmd->args[2].v.s; |
7361 | 0 | if (comment && !comment[0]) |
7362 | 0 | comment = NULL; |
7363 | 0 | if (!mp_input_bind_key(mpctx->input, key, bstr0(target_cmd), comment)) { |
7364 | 0 | MP_ERR(mpctx, "'%s' is not a valid input name.\n", key); |
7365 | 0 | cmd->success = false; |
7366 | 0 | } |
7367 | 0 | } |
7368 | | |
7369 | | static void cmd_apply_profile(void *p) |
7370 | 0 | { |
7371 | 0 | struct mp_cmd_ctx *cmd = p; |
7372 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7373 | |
|
7374 | 0 | char *profile = cmd->args[0].v.s; |
7375 | 0 | int mode = cmd->args[1].v.i; |
7376 | 0 | if (mode == 0) { |
7377 | 0 | cmd->success = m_config_set_profile(mpctx->mconfig, profile, 0) >= 0; |
7378 | 0 | } else { |
7379 | 0 | cmd->success = m_config_restore_profile(mpctx->mconfig, profile) >= 0; |
7380 | 0 | } |
7381 | 0 | } |
7382 | | |
7383 | | static void cmd_load_config_file(void *p) |
7384 | 13.0k | { |
7385 | 13.0k | struct mp_cmd_ctx *cmd = p; |
7386 | 13.0k | struct MPContext *mpctx = cmd->mpctx; |
7387 | | |
7388 | 13.0k | void *ctx = talloc_new(NULL); |
7389 | 13.0k | char *config_file = mp_get_user_path(ctx, mpctx->global, cmd->args[0].v.s); |
7390 | 13.0k | int r = m_config_parse_config_file(mpctx->mconfig, mpctx->global, |
7391 | 13.0k | config_file, NULL, 0); |
7392 | 13.0k | talloc_free(ctx); |
7393 | | |
7394 | 13.0k | if (r < 1) { |
7395 | 0 | cmd->success = false; |
7396 | 0 | return; |
7397 | 0 | } |
7398 | | |
7399 | 13.0k | mp_notify_property(mpctx, "profile-list"); |
7400 | 13.0k | } |
7401 | | |
7402 | | static void cmd_load_input_conf(void *p) |
7403 | 6.42k | { |
7404 | 6.42k | struct mp_cmd_ctx *cmd = p; |
7405 | 6.42k | struct MPContext *mpctx = cmd->mpctx; |
7406 | | |
7407 | 6.42k | char *config_file = cmd->args[0].v.s; |
7408 | 6.42k | cmd->success = mp_input_load_config_file(mpctx->input, config_file); |
7409 | 6.42k | } |
7410 | | |
7411 | | static void cmd_load_script(void *p) |
7412 | 0 | { |
7413 | 0 | struct mp_cmd_ctx *cmd = p; |
7414 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7415 | |
|
7416 | 0 | char *script = cmd->args[0].v.s; |
7417 | 0 | int64_t id = mp_load_user_script(mpctx, script); |
7418 | 0 | if (id > 0) { |
7419 | 0 | struct mpv_node *res = &cmd->result; |
7420 | 0 | node_init(res, MPV_FORMAT_NODE_MAP, NULL); |
7421 | 0 | node_map_add_int64(res, "client_id", id); |
7422 | 0 | } else { |
7423 | 0 | cmd->success = false; |
7424 | 0 | } |
7425 | 0 | } |
7426 | | |
7427 | | static void cache_dump_poll(struct MPContext *mpctx) |
7428 | 3.71M | { |
7429 | 3.71M | struct command_ctx *ctx = mpctx->command_ctx; |
7430 | 3.71M | struct mp_cmd_ctx *cmd = ctx->cache_dump_cmd; |
7431 | | |
7432 | 3.71M | if (!cmd) |
7433 | 3.71M | return; |
7434 | | |
7435 | | // Can't close demuxer without stopping dumping. |
7436 | 0 | mp_assert(mpctx->demuxer); |
7437 | | |
7438 | 0 | if (mp_cancel_test(cmd->abort->cancel)) { |
7439 | | // Synchronous abort. In particular, the dump command shall not report |
7440 | | // completion to the user before the dump target file was closed. |
7441 | 0 | demux_cache_dump_set(mpctx->demuxer, 0, 0, NULL); |
7442 | 0 | mp_assert(demux_cache_dump_get_status(mpctx->demuxer) <= 0); |
7443 | 0 | } |
7444 | | |
7445 | 0 | int status = demux_cache_dump_get_status(mpctx->demuxer); |
7446 | 0 | if (status <= 0) { |
7447 | 0 | if (status < 0) { |
7448 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "Cache dumping stopped due to error."); |
7449 | 0 | cmd->success = false; |
7450 | 0 | } else { |
7451 | 0 | mp_cmd_msg(cmd, MSGL_INFO, "Cache dumping successfully ended."); |
7452 | 0 | cmd->success = true; |
7453 | 0 | } |
7454 | 0 | ctx->cache_dump_cmd = NULL; |
7455 | 0 | mp_cmd_ctx_complete(cmd); |
7456 | 0 | } |
7457 | 0 | } |
7458 | | |
7459 | | void mp_abort_cache_dumping(struct MPContext *mpctx) |
7460 | 556k | { |
7461 | 556k | struct command_ctx *ctx = mpctx->command_ctx; |
7462 | | |
7463 | 556k | if (ctx->cache_dump_cmd) |
7464 | 0 | mp_cancel_trigger(ctx->cache_dump_cmd->abort->cancel); |
7465 | 556k | cache_dump_poll(mpctx); |
7466 | 556k | mp_assert(!ctx->cache_dump_cmd); // synchronous abort, must have worked |
7467 | 556k | } |
7468 | | |
7469 | | static void run_dump_cmd(struct mp_cmd_ctx *cmd, double start, double end, |
7470 | | char *filename) |
7471 | 0 | { |
7472 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7473 | 0 | struct command_ctx *ctx = mpctx->command_ctx; |
7474 | |
|
7475 | 0 | mp_abort_cache_dumping(mpctx); |
7476 | |
|
7477 | 0 | if (!mpctx->demuxer) { |
7478 | 0 | mp_cmd_msg(cmd, MSGL_ERR, "No demuxer open."); |
7479 | 0 | cmd->success = false; |
7480 | 0 | mp_cmd_ctx_complete(cmd); |
7481 | 0 | return; |
7482 | 0 | } |
7483 | | |
7484 | 0 | char *path = mp_get_user_path(NULL, mpctx->global, filename); |
7485 | 0 | mp_cmd_msg(cmd, MSGL_INFO, "Cache dumping started."); |
7486 | |
|
7487 | 0 | if (!demux_cache_dump_set(mpctx->demuxer, start, end, path)) { |
7488 | 0 | mp_cmd_msg(cmd, MSGL_INFO, "Cache dumping stopped."); |
7489 | 0 | mp_cmd_ctx_complete(cmd); |
7490 | 0 | talloc_free(path); |
7491 | 0 | return; |
7492 | 0 | } |
7493 | | |
7494 | 0 | ctx->cache_dump_cmd = cmd; |
7495 | 0 | cache_dump_poll(mpctx); |
7496 | 0 | talloc_free(path); |
7497 | 0 | } |
7498 | | |
7499 | | static void cmd_dump_cache(void *p) |
7500 | 0 | { |
7501 | 0 | struct mp_cmd_ctx *cmd = p; |
7502 | |
|
7503 | 0 | run_dump_cmd(cmd, cmd->args[0].v.d, cmd->args[1].v.d, cmd->args[2].v.s); |
7504 | 0 | } |
7505 | | |
7506 | | static void cmd_dump_cache_ab(void *p) |
7507 | 0 | { |
7508 | 0 | struct mp_cmd_ctx *cmd = p; |
7509 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7510 | |
|
7511 | 0 | run_dump_cmd(cmd, mpctx->opts->ab_loop[0], mpctx->opts->ab_loop[1], |
7512 | 0 | cmd->args[0].v.s); |
7513 | 0 | } |
7514 | | |
7515 | | static void cmd_begin_vo_dragging(void *p) |
7516 | 0 | { |
7517 | 0 | struct mp_cmd_ctx *cmd = p; |
7518 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7519 | 0 | struct vo *vo = mpctx->video_out; |
7520 | |
|
7521 | 0 | if (vo) |
7522 | 0 | vo_control(vo, VOCTRL_BEGIN_DRAGGING, NULL); |
7523 | 0 | } |
7524 | | |
7525 | | static void cmd_update_clipboard(void *p) |
7526 | 0 | { |
7527 | 0 | struct mp_cmd_ctx *cmd = p; |
7528 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7529 | 0 | struct clipboard_access_params params = {.target = cmd->args[0].v.i}; |
7530 | 0 | double timeout = cmd->args[1].v.i / 1000.0; |
7531 | 0 | bool success = false; |
7532 | |
|
7533 | 0 | mp_core_unlock(mpctx); |
7534 | 0 | success = mp_clipboard_update_data(mpctx->clipboard, ¶ms, cmd->abort->cancel, timeout); |
7535 | 0 | mp_core_lock(mpctx); |
7536 | 0 | cmd->success = success; |
7537 | 0 | } |
7538 | | |
7539 | | static void cmd_context_menu(void *p) |
7540 | 0 | { |
7541 | 0 | struct mp_cmd_ctx *cmd = p; |
7542 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7543 | 0 | struct vo *vo = mpctx->video_out; |
7544 | |
|
7545 | 0 | if (vo) |
7546 | 0 | vo_control(vo, VOCTRL_SHOW_MENU, NULL); |
7547 | 0 | } |
7548 | | |
7549 | | static void cmd_discnav(void *p) |
7550 | 0 | { |
7551 | 0 | struct mp_cmd_ctx *cmd = p; |
7552 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7553 | 0 | int action = cmd->args[0].v.i; |
7554 | |
|
7555 | 0 | struct stream *s = disc_nav_get_stream(mpctx); |
7556 | 0 | if (!s) { |
7557 | 0 | cmd->success = false; |
7558 | 0 | return; |
7559 | 0 | } |
7560 | | |
7561 | 0 | struct stream_nav_cmd nc = { .action = action }; |
7562 | |
|
7563 | 0 | struct stream_nav_state pre = {0}; |
7564 | 0 | stream_control(s, STREAM_CTRL_GET_NAV_STATE, &pre); |
7565 | |
|
7566 | 0 | if (action == STREAM_NAV_MOUSE_MOVE || action == STREAM_NAV_MOUSE_CLICK) { |
7567 | 0 | double nx = cmd->args[1].v.d; |
7568 | 0 | double ny = cmd->args[2].v.d; |
7569 | 0 | if (nx >= 0 && ny >= 0) { |
7570 | 0 | if (pre.src_w <= 0 || pre.src_h <= 0 || nx > 1 || ny > 1) { |
7571 | 0 | cmd->success = false; |
7572 | 0 | return; |
7573 | 0 | } |
7574 | 0 | nc.x = (int)(nx * pre.src_w); |
7575 | 0 | nc.y = (int)(ny * pre.src_h); |
7576 | 0 | } else if (!disc_nav_mouse_pos_to_src(mpctx, pre.src_w, pre.src_h, |
7577 | 0 | &nc.x, &nc.y)) |
7578 | 0 | { |
7579 | 0 | cmd->success = false; |
7580 | 0 | return; |
7581 | 0 | } |
7582 | 0 | } |
7583 | | |
7584 | 0 | if (stream_control(s, STREAM_CTRL_NAV_CMD, &nc) < 1) { |
7585 | 0 | cmd->success = false; |
7586 | 0 | } else if (mpctx->demuxer && stream_nav_action_activates(action)) { |
7587 | 0 | demux_drive_nav(mpctx->demuxer); |
7588 | 0 | } |
7589 | |
|
7590 | 0 | mp_wakeup_core(mpctx); |
7591 | 0 | } |
7592 | | |
7593 | | static void cmd_flush_status_line(void *p) |
7594 | 0 | { |
7595 | 0 | struct mp_cmd_ctx *cmd = p; |
7596 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7597 | |
|
7598 | 0 | if (!mpctx->log) |
7599 | 0 | return; |
7600 | | |
7601 | 0 | mp_msg_flush_status_line(mpctx->log, cmd->args[0].v.b); |
7602 | 0 | } |
7603 | | |
7604 | | static void cmd_notify_property(void *p) |
7605 | 0 | { |
7606 | 0 | struct mp_cmd_ctx *cmd = p; |
7607 | 0 | struct MPContext *mpctx = cmd->mpctx; |
7608 | |
|
7609 | 0 | mp_notify_property(mpctx, cmd->args[0].v.s); |
7610 | 0 | } |
7611 | | |
7612 | | /* This array defines all known commands. |
7613 | | * The first field the command name used in libmpv and input.conf. |
7614 | | * The second field is the handler function (see mp_cmd_def.handler and |
7615 | | * run_command()). |
7616 | | * Then comes the definition of each argument. They are defined like options, |
7617 | | * except that the result is parsed into mp_cmd.args[] (thus the option variable |
7618 | | * is a field in the mp_cmd_arg union field). Arguments are optional if either |
7619 | | * defval is set (usually via OPTDEF_ macros), or the MP_CMD_OPT_ARG flag is |
7620 | | * set, or if it's the last argument and .vararg is set. If .vararg is set, the |
7621 | | * command has an arbitrary number of arguments, all using the type indicated by |
7622 | | * the last argument (they are appended to mp_cmd.args[] starting at the last |
7623 | | * argument's index). |
7624 | | * Arguments have names, which can be used by named argument functions, e.g. in |
7625 | | * Lua with mp.command_native(). |
7626 | | */ |
7627 | | |
7628 | | // This does not specify the real destination of the command parameter values, |
7629 | | // it just provides a dummy for the OPT_ macros. The real destination is an |
7630 | | // array item in mp_cmd.args[], using the index of the option definition. |
7631 | | #define OPT_BASE_STRUCT struct mp_cmd_arg |
7632 | | |
7633 | | const struct mp_cmd_def mp_cmds[] = { |
7634 | | { "ignore", cmd_ignore, .is_ignore = true, .is_noisy = true, }, |
7635 | | |
7636 | | { "seek", cmd_seek, |
7637 | | { |
7638 | | {"target", OPT_TIME(v.d)}, |
7639 | | {"flags", OPT_FLAGS(v.i, |
7640 | | {"relative", 4|0}, {"-", 4|0}, |
7641 | | {"absolute-percent", 4|1}, |
7642 | | {"absolute", 4|2}, |
7643 | | {"relative-percent", 4|3}, |
7644 | | {"keyframes", 32|8}, |
7645 | | {"exact", 32|16}), |
7646 | | OPTDEF_INT(4|0)}, |
7647 | | // backwards compatibility only |
7648 | | {"legacy", OPT_CHOICE(v.i, |
7649 | | {"unused", 0}, {"default-precise", 0}, |
7650 | | {"keyframes", 32|8}, |
7651 | | {"exact", 32|16}), |
7652 | | .flags = MP_CMD_OPT_ARG}, |
7653 | | }, |
7654 | | .allow_auto_repeat = true, |
7655 | | .scalable = true, |
7656 | | }, |
7657 | | { "revert-seek", cmd_revert_seek, |
7658 | | { {"flags", OPT_FLAGS(v.i, {"mark", 2|0}, {"mark-permanent", 2|1}), |
7659 | | .flags = MP_CMD_OPT_ARG} }, |
7660 | | }, |
7661 | | { "quit", cmd_quit, { {"code", OPT_INT(v.i), .flags = MP_CMD_OPT_ARG} }, |
7662 | | .priv = &(const bool){0} }, |
7663 | | { "quit-watch-later", cmd_quit, { {"code", OPT_INT(v.i), |
7664 | | .flags = MP_CMD_OPT_ARG} }, |
7665 | | .priv = &(const bool){1} }, |
7666 | | { "stop", cmd_stop, |
7667 | | { {"flags", OPT_FLAGS(v.i, {"keep-playlist", 1}), .flags = MP_CMD_OPT_ARG} } |
7668 | | }, |
7669 | | { "frame-step", cmd_frame_step, |
7670 | | { |
7671 | | {"frames", OPT_INT(v.i), OPTDEF_INT(1)}, |
7672 | | {"flags", OPT_CHOICE(v.i, |
7673 | | {"play", 0}, |
7674 | | {"seek", 1}, |
7675 | | {"mute", 2}), |
7676 | | .flags = MP_CMD_OPT_ARG}, |
7677 | | }, |
7678 | | .allow_auto_repeat = true, |
7679 | | .on_updown = true, |
7680 | | .priv = &(const bool){false}, |
7681 | | }, |
7682 | | { "frame-back-step", cmd_frame_step, |
7683 | | .priv = &(const int){true}, |
7684 | | .allow_auto_repeat = true, |
7685 | | }, |
7686 | | { "playlist-next", cmd_playlist_next_prev, |
7687 | | { |
7688 | | {"flags", OPT_CHOICE(v.i, |
7689 | | {"weak", 0}, |
7690 | | {"force", 1}), |
7691 | | .flags = MP_CMD_OPT_ARG}, |
7692 | | }, |
7693 | | .priv = &(const int){1}, |
7694 | | }, |
7695 | | { "playlist-prev", cmd_playlist_next_prev, |
7696 | | { |
7697 | | {"flags", OPT_CHOICE(v.i, |
7698 | | {"weak", 0}, |
7699 | | {"force", 1}), |
7700 | | .flags = MP_CMD_OPT_ARG}, |
7701 | | }, |
7702 | | .priv = &(const int){-1}, |
7703 | | }, |
7704 | | { "playlist-next-playlist", cmd_playlist_next_prev_playlist, |
7705 | | .priv = &(const int){1} }, |
7706 | | { "playlist-prev-playlist", cmd_playlist_next_prev_playlist, |
7707 | | .priv = &(const int){-1} }, |
7708 | | { "playlist-play-index", cmd_playlist_play_index, |
7709 | | { |
7710 | | {"index", OPT_CHOICE(v.i, {"current", -2}, {"none", -1}), |
7711 | | M_RANGE(-1, INT_MAX)}, |
7712 | | {"preserve-options", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7713 | | } |
7714 | | }, |
7715 | | { "playlist-shuffle", cmd_playlist_shuffle, }, |
7716 | | { "playlist-unshuffle", cmd_playlist_unshuffle, }, |
7717 | | { "sub-step", cmd_sub_step_seek, |
7718 | | { |
7719 | | {"skip", OPT_INT(v.i)}, |
7720 | | {"flags", OPT_CHOICE(v.i, |
7721 | | {"primary", 0}, |
7722 | | {"secondary", 1}), |
7723 | | OPTDEF_INT(0)}, |
7724 | | }, |
7725 | | .allow_auto_repeat = true, |
7726 | | .priv = &(const bool){true} |
7727 | | }, |
7728 | | { "sub-seek", cmd_sub_step_seek, |
7729 | | { |
7730 | | {"skip", OPT_INT(v.i)}, |
7731 | | {"flags", OPT_CHOICE(v.i, |
7732 | | {"primary", 0}, |
7733 | | {"secondary", 1}), |
7734 | | OPTDEF_INT(0)}, |
7735 | | }, |
7736 | | .allow_auto_repeat = true, |
7737 | | .priv = &(const bool){false} |
7738 | | }, |
7739 | | { "print-text", cmd_print_text, { {"text", OPT_STRING(v.s)} }, |
7740 | | .is_noisy = true, .allow_auto_repeat = true }, |
7741 | | { "show-text", cmd_show_text, |
7742 | | { |
7743 | | {"text", OPT_STRING(v.s)}, |
7744 | | {"duration", OPT_INT(v.i), OPTDEF_INT(-1)}, |
7745 | | {"level", OPT_INT(v.i), .flags = MP_CMD_OPT_ARG}, |
7746 | | }, |
7747 | | .is_noisy = true, .allow_auto_repeat = true}, |
7748 | | { "expand-text", cmd_expand_text, { {"text", OPT_STRING(v.s)} }, |
7749 | | .is_noisy = true }, |
7750 | | { "expand-path", cmd_expand_path, { {"text", OPT_STRING(v.s)} }, |
7751 | | .is_noisy = true }, |
7752 | | { "normalize-path", cmd_normalize_path, { {"filename", OPT_STRING(v.s)} }}, |
7753 | | { "escape-ass", cmd_escape_ass, { {"text", OPT_STRING(v.s)} }, |
7754 | | .is_noisy = true }, |
7755 | | { "show-progress", cmd_show_progress, .allow_auto_repeat = true, |
7756 | | .is_noisy = true }, |
7757 | | |
7758 | | { "sub-add", cmd_track_add, |
7759 | | { |
7760 | | {"url", OPT_STRING(v.s)}, |
7761 | | {"flags", OPT_FLAGS(v.i, |
7762 | | {"select", 0}, {"auto", 1}, {"cached", 2}, |
7763 | | {"hearing-impaired", TRACK_HEARING_IMPAIRED}, |
7764 | | {"visual-impaired", TRACK_VISUAL_IMPAIRED}, |
7765 | | {"forced", TRACK_FORCED}, |
7766 | | {"default", TRACK_DEFAULT}, |
7767 | | {"commentary", TRACK_COMMENTARY}), |
7768 | | .flags = MP_CMD_OPT_ARG}, |
7769 | | {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7770 | | {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7771 | | }, |
7772 | | .priv = &(const int){STREAM_SUB}, |
7773 | | .spawn_thread = true, |
7774 | | .can_abort = true, |
7775 | | .abort_on_playback_end = true, |
7776 | | }, |
7777 | | { "audio-add", cmd_track_add, |
7778 | | { |
7779 | | {"url", OPT_STRING(v.s)}, |
7780 | | {"flags", OPT_FLAGS(v.i, |
7781 | | {"select", 0}, {"auto", 1}, {"cached", 2}, |
7782 | | {"hearing-impaired", TRACK_HEARING_IMPAIRED}, |
7783 | | {"visual-impaired", TRACK_VISUAL_IMPAIRED}, |
7784 | | {"forced", TRACK_FORCED}, |
7785 | | {"default", TRACK_DEFAULT}, |
7786 | | {"commentary", TRACK_COMMENTARY}), |
7787 | | .flags = MP_CMD_OPT_ARG}, |
7788 | | {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7789 | | {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7790 | | }, |
7791 | | .priv = &(const int){STREAM_AUDIO}, |
7792 | | .spawn_thread = true, |
7793 | | .can_abort = true, |
7794 | | .abort_on_playback_end = true, |
7795 | | }, |
7796 | | { "video-add", cmd_track_add, |
7797 | | { |
7798 | | {"url", OPT_STRING(v.s)}, |
7799 | | {"flags", OPT_FLAGS(v.i, |
7800 | | {"select", 0}, {"auto", 1}, {"cached", 2}, |
7801 | | {"hearing-impaired", TRACK_HEARING_IMPAIRED}, |
7802 | | {"visual-impaired", TRACK_VISUAL_IMPAIRED}, |
7803 | | {"attached-picture", TRACK_ATTACHED_PICTURE}, |
7804 | | {"forced", TRACK_FORCED}, |
7805 | | {"default", TRACK_DEFAULT}, |
7806 | | {"commentary", TRACK_COMMENTARY}), |
7807 | | .flags = MP_CMD_OPT_ARG}, |
7808 | | {"title", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7809 | | {"lang", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7810 | | {"albumart", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7811 | | }, |
7812 | | .priv = &(const int){STREAM_VIDEO}, |
7813 | | .spawn_thread = true, |
7814 | | .can_abort = true, |
7815 | | .abort_on_playback_end = true, |
7816 | | }, |
7817 | | |
7818 | | { "sub-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7819 | | .priv = &(const int){STREAM_SUB}, }, |
7820 | | { "audio-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7821 | | .priv = &(const int){STREAM_AUDIO}, }, |
7822 | | { "video-remove", cmd_track_remove, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7823 | | .priv = &(const int){STREAM_VIDEO}, }, |
7824 | | |
7825 | | { "sub-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7826 | | .priv = &(const int){STREAM_SUB}, |
7827 | | .spawn_thread = true, |
7828 | | .can_abort = true, |
7829 | | .abort_on_playback_end = true, |
7830 | | }, |
7831 | | { "audio-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7832 | | .priv = &(const int){STREAM_AUDIO}, |
7833 | | .spawn_thread = true, |
7834 | | .can_abort = true, |
7835 | | .abort_on_playback_end = true, |
7836 | | }, |
7837 | | { "video-reload", cmd_track_reload, { {"id", OPT_INT(v.i), OPTDEF_INT(-1)} }, |
7838 | | .priv = &(const int){STREAM_VIDEO}, |
7839 | | .spawn_thread = true, |
7840 | | .can_abort = true, |
7841 | | .abort_on_playback_end = true, |
7842 | | }, |
7843 | | |
7844 | | { "rescan-external-files", cmd_rescan_external_files, |
7845 | | { |
7846 | | {"flags", OPT_CHOICE(v.i, |
7847 | | {"keep-selection", 1}, |
7848 | | {"reselect", 0}), |
7849 | | .flags = MP_CMD_OPT_ARG}, |
7850 | | }, |
7851 | | .spawn_thread = true, |
7852 | | .can_abort = true, |
7853 | | .abort_on_playback_end = true, |
7854 | | }, |
7855 | | |
7856 | | { "screenshot", cmd_screenshot, |
7857 | | { |
7858 | | {"flags", OPT_FLAGS(v.i, |
7859 | | {"video", 0}, |
7860 | | {"scaled", 1}, |
7861 | | {"subtitles", 2}, |
7862 | | {"osd", 4}, |
7863 | | {"each-frame", 8}, |
7864 | | {"-", 0}, |
7865 | | {"window", 1|2|4}), |
7866 | | OPTDEF_INT(2)}, |
7867 | | // backwards compatibility |
7868 | | {"legacy", OPT_CHOICE(v.i, |
7869 | | {"unused", 0}, {"single", 0}, |
7870 | | {"each-frame", 8}), |
7871 | | .flags = MP_CMD_OPT_ARG}, |
7872 | | }, |
7873 | | .spawn_thread = true, |
7874 | | }, |
7875 | | { "screenshot-to-file", cmd_screenshot_to_file, |
7876 | | { |
7877 | | {"filename", OPT_STRING(v.s)}, |
7878 | | {"flags", OPT_CHOICE(v.i, |
7879 | | {"video", 0}, |
7880 | | {"scaled", 1}, |
7881 | | {"subtitles", 2}, |
7882 | | {"osd", 4}, |
7883 | | {"window", 1|2|4}), |
7884 | | OPTDEF_INT(2)}, |
7885 | | }, |
7886 | | .spawn_thread = true, |
7887 | | }, |
7888 | | { "screenshot-raw", cmd_screenshot_raw, |
7889 | | { |
7890 | | {"flags", OPT_CHOICE(v.i, |
7891 | | {"video", 0}, |
7892 | | {"scaled", 1}, |
7893 | | {"subtitles", 2}, |
7894 | | {"osd", 4}, |
7895 | | {"window", 1|2|4}), |
7896 | | OPTDEF_INT(2)}, |
7897 | | {"format", OPT_CHOICE(v.i, |
7898 | | {"bgr0", 0}, |
7899 | | {"bgra", 1}, |
7900 | | {"rgba", 2}, |
7901 | | {"rgba64", 3}), |
7902 | | OPTDEF_INT(0)}, |
7903 | | }, |
7904 | | }, |
7905 | | { "loadfile", cmd_loadfile, |
7906 | | { |
7907 | | {"url", OPT_STRING(v.s)}, |
7908 | | {"flags", OPT_FLAGS(v.i, |
7909 | | {"replace", 4|0}, |
7910 | | {"append", 4|1}, |
7911 | | {"insert-next", 4|2}, |
7912 | | {"insert-at", 4|3}, |
7913 | | {"play", 32|8}, |
7914 | | // backwards compatibility |
7915 | | {"append-play", (4|1) + (16|8)}, |
7916 | | {"insert-next-play", (4|2) + (16|8)}, |
7917 | | {"insert-at-play", (4|3) + (16|8)}), |
7918 | | .flags = MP_CMD_OPT_ARG}, |
7919 | | {"index", OPT_INT(v.i), OPTDEF_INT(-1)}, |
7920 | | {"options", OPT_KEYVALUELIST(v.str_list), .flags = MP_CMD_OPT_ARG}, |
7921 | | }, |
7922 | | }, |
7923 | | { "loadlist", cmd_loadlist, |
7924 | | { |
7925 | | {"url", OPT_STRING(v.s)}, |
7926 | | {"flags", OPT_FLAGS(v.i, |
7927 | | {"replace", 4|0}, |
7928 | | {"append", 4|1}, |
7929 | | {"insert-next", 4|2}, |
7930 | | {"insert-at", 4|3}, |
7931 | | {"play", 32|8}, |
7932 | | // backwards compatibility |
7933 | | {"append-play", (4|1) + (16|8)}, |
7934 | | {"insert-next-play", (4|2) + (16|8)}, |
7935 | | {"insert-at-play", (4|3) + (16|8)}), |
7936 | | .flags = MP_CMD_OPT_ARG}, |
7937 | | {"index", OPT_INT(v.i), OPTDEF_INT(-1)}, |
7938 | | }, |
7939 | | .spawn_thread = true, |
7940 | | .can_abort = true, |
7941 | | }, |
7942 | | { "playlist-clear", cmd_playlist_clear }, |
7943 | | { "playlist-remove", cmd_playlist_remove, { |
7944 | | {"index", OPT_CHOICE(v.i, {"current", -1}), |
7945 | | M_RANGE(0, INT_MAX)}, }}, |
7946 | | { "playlist-move", cmd_playlist_move, { {"index1", OPT_INT(v.i)}, |
7947 | | {"index2", OPT_INT(v.i)}, }}, |
7948 | | { "run", cmd_run, { {"command", OPT_STRING(v.s)}, |
7949 | | {"args", OPT_STRING(v.s)}, }, |
7950 | | .vararg = true, |
7951 | | }, |
7952 | | { "subprocess", cmd_subprocess, |
7953 | | { |
7954 | | {"args", OPT_STRINGLIST(v.str_list)}, |
7955 | | {"playback_only", OPT_BOOL(v.b), OPTDEF_INT(1)}, |
7956 | | {"capture_size", OPT_BYTE_SIZE(v.i64), M_RANGE(0, INT_MAX), |
7957 | | OPTDEF_INT64(64 * 1024 * 1024)}, |
7958 | | {"capture_stdout", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7959 | | {"capture_stderr", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7960 | | {"detach", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7961 | | {"env", OPT_STRINGLIST(v.str_list), .flags = MP_CMD_OPT_ARG}, |
7962 | | {"stdin_data", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG}, |
7963 | | {"passthrough_stdin", OPT_BOOL(v.b), .flags = MP_CMD_OPT_ARG}, |
7964 | | }, |
7965 | | .spawn_thread = true, |
7966 | | .can_abort = true, |
7967 | | }, |
7968 | | |
7969 | | { "set", cmd_set, {{"name", OPT_STRING(v.s)}, {"value", OPT_STRING(v.s)}}}, |
7970 | | { "del", cmd_del, {{"name", OPT_STRING(v.s)}}}, |
7971 | | { "change-list", cmd_change_list, { {"name", OPT_STRING(v.s)}, |
7972 | | {"operation", OPT_STRING(v.s)}, |
7973 | | {"value", OPT_STRING(v.s)} }}, |
7974 | | { "add", cmd_add_cycle, { {"name", OPT_STRING(v.s)}, |
7975 | | {"value", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(1)}, }, |
7976 | | .allow_auto_repeat = true, |
7977 | | .scalable = true, |
7978 | | }, |
7979 | | { "cycle", cmd_add_cycle, { {"name", OPT_STRING(v.s)}, |
7980 | | {"value", OPT_CYCLEDIR(v.d), OPTDEF_DOUBLE(1)}, }, |
7981 | | .allow_auto_repeat = true, |
7982 | | .scalable = true, |
7983 | | .priv = "", |
7984 | | }, |
7985 | | { "multiply", cmd_multiply, { {"name", OPT_STRING(v.s)}, |
7986 | | {"value", OPT_DOUBLE(v.d)}}, |
7987 | | .allow_auto_repeat = true}, |
7988 | | |
7989 | | { "cycle-values", cmd_cycle_values, { {"arg0", OPT_STRING(v.s)}, |
7990 | | {"arg1", OPT_STRING(v.s)}, |
7991 | | {"argN", OPT_STRING(v.s)}, }, |
7992 | | .vararg = true}, |
7993 | | |
7994 | | { "enable-section", cmd_enable_input_section, |
7995 | | { |
7996 | | {"name", OPT_STRING(v.s)}, |
7997 | | {"flags", OPT_FLAGS(v.i, |
7998 | | {"default", 0}, |
7999 | | {"exclusive", MP_INPUT_EXCLUSIVE}, |
8000 | | {"allow-hide-cursor", MP_INPUT_ALLOW_HIDE_CURSOR}, |
8001 | | {"allow-vo-dragging", MP_INPUT_ALLOW_VO_DRAGGING}), |
8002 | | .flags = MP_CMD_OPT_ARG}, |
8003 | | } |
8004 | | }, |
8005 | | { "disable-section", cmd_disable_input_section, |
8006 | | {{"name", OPT_STRING(v.s)} }}, |
8007 | | { "define-section", cmd_define_input_section, |
8008 | | { |
8009 | | {"name", OPT_STRING(v.s)}, |
8010 | | {"contents", OPT_STRING(v.s)}, |
8011 | | {"flags", OPT_CHOICE(v.i, {"default", 0}, {"force", 1}), |
8012 | | .flags = MP_CMD_OPT_ARG}, |
8013 | | }, |
8014 | | }, |
8015 | | |
8016 | | { "ab-loop", cmd_ab_loop }, |
8017 | | |
8018 | | { "drop-buffers", cmd_drop_buffers, }, |
8019 | | |
8020 | | { "af", cmd_filter, { {"operation", OPT_STRING(v.s)}, |
8021 | | {"value", OPT_STRING(v.s)}, }, |
8022 | | .priv = &(const int){STREAM_AUDIO} }, |
8023 | | { "vf", cmd_filter, { {"operation", OPT_STRING(v.s)}, |
8024 | | {"value", OPT_STRING(v.s)}, }, |
8025 | | .priv = &(const int){STREAM_VIDEO} }, |
8026 | | |
8027 | | { "af-command", cmd_filter_command, |
8028 | | { |
8029 | | {"label", OPT_STRING(v.s)}, |
8030 | | {"command", OPT_STRING(v.s)}, |
8031 | | {"argument", OPT_STRING(v.s)}, |
8032 | | {"target", OPT_STRING(v.s), OPTDEF_STR("all"), |
8033 | | .flags = MP_CMD_OPT_ARG}, |
8034 | | }, |
8035 | | .priv = &(const int){STREAM_AUDIO} }, |
8036 | | { "vf-command", cmd_filter_command, |
8037 | | { |
8038 | | {"label", OPT_STRING(v.s)}, |
8039 | | {"command", OPT_STRING(v.s)}, |
8040 | | {"argument", OPT_STRING(v.s)}, |
8041 | | {"target", OPT_STRING(v.s), OPTDEF_STR("all"), |
8042 | | .flags = MP_CMD_OPT_ARG}, |
8043 | | }, |
8044 | | .priv = &(const int){STREAM_VIDEO} }, |
8045 | | |
8046 | | { "ao-reload", cmd_ao_reload }, |
8047 | | |
8048 | | { "script-binding", cmd_script_binding, |
8049 | | { |
8050 | | {"name", OPT_STRING(v.s)}, |
8051 | | {"arg", OPT_STRING(v.s), OPTDEF_STR(""), |
8052 | | .flags = MP_CMD_OPT_ARG}, |
8053 | | }, |
8054 | | .allow_auto_repeat = true, .on_updown = true, .scalable = true }, |
8055 | | |
8056 | | { "script-message", cmd_script_message, { {"args", OPT_STRING(v.s)} }, |
8057 | | .vararg = true }, |
8058 | | { "script-message-to", cmd_script_message_to, { {"target", OPT_STRING(v.s)}, |
8059 | | {"args", OPT_STRING(v.s)} }, |
8060 | | .vararg = true }, |
8061 | | |
8062 | | { "overlay-add", cmd_overlay_add, { {"id", OPT_INT(v.i)}, |
8063 | | {"x", OPT_INT(v.i)}, |
8064 | | {"y", OPT_INT(v.i)}, |
8065 | | {"file", OPT_STRING(v.s)}, |
8066 | | {"offset", OPT_INT(v.i)}, |
8067 | | {"fmt", OPT_STRING(v.s)}, |
8068 | | {"w", OPT_INT(v.i)}, |
8069 | | {"h", OPT_INT(v.i)}, |
8070 | | {"stride", OPT_INT(v.i)}, |
8071 | | {"dw", OPT_INT(v.i), OPTDEF_INT(0)}, |
8072 | | {"dh", OPT_INT(v.i), OPTDEF_INT(0)}, |
8073 | | {"video_colorspace", OPT_BOOL(v.b), |
8074 | | OPTDEF_INT(0)}, |
8075 | | {"max_luma", OPT_FLOAT(v.f), |
8076 | | M_RANGE(0, 10000), OPTDEF_FLOAT(0)}, |
8077 | | {"primaries", OPT_CHOICE_C(v.i, pl_csp_prim_names), |
8078 | | OPTDEF_INT(PL_COLOR_PRIM_UNKNOWN)}, |
8079 | | {"transfer", OPT_CHOICE_C(v.i, pl_csp_trc_names), |
8080 | | OPTDEF_INT(PL_COLOR_TRC_UNKNOWN)}, }}, |
8081 | | { "overlay-remove", cmd_overlay_remove, { {"id", OPT_INT(v.i)} } }, |
8082 | | |
8083 | | { "osd-overlay", cmd_osd_overlay, |
8084 | | { |
8085 | | {"id", OPT_INT64(v.i64)}, |
8086 | | {"format", OPT_CHOICE(v.i, {"none", 0}, {"ass-events", 1})}, |
8087 | | {"data", OPT_STRING(v.s)}, |
8088 | | {"res_x", OPT_INT(v.i), OPTDEF_INT(0)}, |
8089 | | {"res_y", OPT_INT(v.i), OPTDEF_INT(720)}, |
8090 | | {"z", OPT_INT(v.i), OPTDEF_INT(0)}, |
8091 | | {"hidden", OPT_BOOL(v.b), OPTDEF_INT(0)}, |
8092 | | {"compute_bounds", OPT_BOOL(v.b), OPTDEF_INT(0)}, |
8093 | | }, |
8094 | | .is_noisy = true, |
8095 | | }, |
8096 | | |
8097 | | { "write-watch-later-config", cmd_write_watch_later_config }, |
8098 | | { "delete-watch-later-config", cmd_delete_watch_later_config, |
8099 | | {{"filename", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG} }}, |
8100 | | |
8101 | | { "mouse", cmd_mouse, { {"x", OPT_INT(v.i)}, |
8102 | | {"y", OPT_INT(v.i)}, |
8103 | | {"button", OPT_INT(v.i), OPTDEF_INT(-1)}, |
8104 | | {"mode", OPT_CHOICE(v.i, |
8105 | | {"single", 0}, {"double", 1}), |
8106 | | .flags = MP_CMD_OPT_ARG}}}, |
8107 | | { "keybind", cmd_key_bind, { {"name", OPT_STRING(v.s)}, |
8108 | | {"cmd", OPT_STRING(v.s)}, |
8109 | | {"comment", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG} }}, |
8110 | | { "keypress", cmd_key, { {"name", OPT_STRING(v.s)}, |
8111 | | {"scale", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(1)} }, |
8112 | | .priv = &(const int){0}}, |
8113 | | { "keydown", cmd_key, { {"name", OPT_STRING(v.s)} }, |
8114 | | .priv = &(const int){MP_KEY_STATE_DOWN}}, |
8115 | | { "keyup", cmd_key, { {"name", OPT_STRING(v.s), .flags = MP_CMD_OPT_ARG} }, |
8116 | | .priv = &(const int){MP_KEY_STATE_UP}}, |
8117 | | |
8118 | | { "apply-profile", cmd_apply_profile, { |
8119 | | {"name", OPT_STRING(v.s)}, |
8120 | | {"mode", OPT_CHOICE(v.i, {"apply", 0}, {"restore", 1}), |
8121 | | .flags = MP_CMD_OPT_ARG}, } |
8122 | | }, |
8123 | | |
8124 | | { "load-config-file", cmd_load_config_file, {{"filename", OPT_STRING(v.s)}} }, |
8125 | | |
8126 | | { "load-input-conf", cmd_load_input_conf, {{"filename", OPT_STRING(v.s)}} }, |
8127 | | |
8128 | | { "load-script", cmd_load_script, {{"filename", OPT_STRING(v.s)}} }, |
8129 | | |
8130 | | { "dump-cache", cmd_dump_cache, { {"start", OPT_TIME(v.d), |
8131 | | .flags = M_OPT_ALLOW_NO}, |
8132 | | {"end", OPT_TIME(v.d), |
8133 | | .flags = M_OPT_ALLOW_NO}, |
8134 | | {"filename", OPT_STRING(v.s)} }, |
8135 | | .exec_async = true, |
8136 | | .can_abort = true, |
8137 | | }, |
8138 | | |
8139 | | { "ab-loop-dump-cache", cmd_dump_cache_ab, { {"filename", OPT_STRING(v.s)} }, |
8140 | | .exec_async = true, |
8141 | | .can_abort = true, |
8142 | | }, |
8143 | | |
8144 | | { "ab-loop-align-cache", cmd_align_cache_ab }, |
8145 | | |
8146 | | { "begin-vo-dragging", cmd_begin_vo_dragging }, |
8147 | | |
8148 | | { "update-clipboard", cmd_update_clipboard, |
8149 | | { {"type", OPT_CHOICE(v.i, |
8150 | | {"text", CLIPBOARD_TARGET_CLIPBOARD}, |
8151 | | {"text-primary", CLIPBOARD_TARGET_PRIMARY_SELECTION})}, |
8152 | | {"timeout", OPT_INT(v.i), OPTDEF_INT(10)}, |
8153 | | }, |
8154 | | .spawn_thread = true, |
8155 | | .can_abort = true, |
8156 | | }, |
8157 | | |
8158 | | { "context-menu", cmd_context_menu }, |
8159 | | |
8160 | | { "discnav", cmd_discnav, |
8161 | | { {"action", OPT_CHOICE(v.i, |
8162 | | {"up", STREAM_NAV_UP}, |
8163 | | {"down", STREAM_NAV_DOWN}, |
8164 | | {"left", STREAM_NAV_LEFT}, |
8165 | | {"right", STREAM_NAV_RIGHT}, |
8166 | | {"select", STREAM_NAV_SELECT}, |
8167 | | {"menu", STREAM_NAV_MENU_ROOT}, |
8168 | | {"title-menu", STREAM_NAV_MENU_TITLE}, |
8169 | | {"popup", STREAM_NAV_MENU_POPUP}, |
8170 | | {"audio-menu", STREAM_NAV_MENU_AUDIO}, |
8171 | | {"sub-menu", STREAM_NAV_MENU_SUBTITLE}, |
8172 | | {"angle-menu", STREAM_NAV_MENU_ANGLE}, |
8173 | | {"up-menu", STREAM_NAV_GO_UP}, |
8174 | | {"prev", STREAM_NAV_PREV_MENU}, |
8175 | | {"red", STREAM_NAV_KEY_RED}, |
8176 | | {"green", STREAM_NAV_KEY_GREEN}, |
8177 | | {"yellow", STREAM_NAV_KEY_YELLOW}, |
8178 | | {"blue", STREAM_NAV_KEY_BLUE}, |
8179 | | {"mouse-move", STREAM_NAV_MOUSE_MOVE}, |
8180 | | {"mouse-click", STREAM_NAV_MOUSE_CLICK})}, |
8181 | | {"x", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)}, |
8182 | | {"y", OPT_DOUBLE(v.d), OPTDEF_DOUBLE(-1)} }, |
8183 | | }, |
8184 | | |
8185 | | { "flush-status-line", cmd_flush_status_line, { {"clear", OPT_BOOL(v.b)} } }, |
8186 | | |
8187 | | { "notify-property", cmd_notify_property, { {"property", OPT_STRING(v.s)} } }, |
8188 | | |
8189 | | {0} |
8190 | | }; |
8191 | | |
8192 | | #undef OPT_BASE_STRUCT |
8193 | | #undef ARG |
8194 | | |
8195 | | void command_uninit(struct MPContext *mpctx) |
8196 | 227k | { |
8197 | 227k | struct command_ctx *ctx = mpctx->command_ctx; |
8198 | | |
8199 | 227k | mp_assert(!ctx->cache_dump_cmd); // closing the demuxer must have aborted it |
8200 | | |
8201 | 227k | overlay_uninit(mpctx); |
8202 | 227k | ao_hotplug_destroy(ctx->hotplug); |
8203 | | |
8204 | 227k | m_option_free(&script_props_type, &ctx->script_props); |
8205 | | |
8206 | 227k | talloc_free(mpctx->command_ctx); |
8207 | 227k | mpctx->command_ctx = NULL; |
8208 | 227k | } |
8209 | | |
8210 | | static int str_compare(const void *a, const void *b) |
8211 | 1.59G | { |
8212 | 1.59G | return strcmp(*(const char **)a, *(const char **)b); |
8213 | 1.59G | } |
8214 | | |
8215 | | void command_init(struct MPContext *mpctx) |
8216 | 227k | { |
8217 | 227k | struct command_ctx *ctx = talloc(NULL, struct command_ctx); |
8218 | 227k | *ctx = (struct command_ctx){ |
8219 | 227k | .last_seek_pts = MP_NOPTS_VALUE, |
8220 | 227k | }; |
8221 | 227k | mpctx->command_ctx = ctx; |
8222 | | |
8223 | 227k | int num_base = MP_ARRAY_SIZE(mp_properties_base); |
8224 | 227k | int num_opts = m_config_get_co_count(mpctx->mconfig); |
8225 | 227k | struct m_property *props = |
8226 | 227k | talloc_zero_array(ctx, struct m_property, num_base + num_opts); |
8227 | 227k | memcpy(props, mp_properties_base, sizeof(mp_properties_base)); |
8228 | | |
8229 | 227k | const char **prop_names = talloc_array(NULL, const char *, num_base); |
8230 | 44.3M | for (int i = 0; i < num_base; ++i) |
8231 | 44.1M | prop_names[i] = mp_properties_base[i].name; |
8232 | 227k | qsort(prop_names, num_base, sizeof(const char *), str_compare); |
8233 | | |
8234 | 227k | int count = num_base; |
8235 | 178M | for (int n = 0; n < num_opts; n++) { |
8236 | 178M | struct m_config_option *co = m_config_get_co_index(mpctx->mconfig, n); |
8237 | 178M | mp_assert(co->name[0]); |
8238 | 178M | if (co->opt->flags & M_OPT_NOPROP) |
8239 | 6.37M | continue; |
8240 | | |
8241 | 171M | struct m_property prop = { |
8242 | 171M | .name = co->name, |
8243 | 171M | .call = mp_property_generic_option, |
8244 | 171M | .priv = co, |
8245 | 171M | .is_option = true, |
8246 | 171M | }; |
8247 | | |
8248 | 171M | if (co->opt->type == &m_option_type_alias) { |
8249 | 5.23M | char buf[M_CONFIG_MAX_OPT_NAME_LEN]; |
8250 | 5.23M | const char *alias = m_config_shadow_get_alias_from_opt(mpctx->mconfig->shadow, co->opt_id, |
8251 | 5.23M | buf, sizeof(buf)); |
8252 | 5.23M | prop.priv = talloc_strdup(ctx, alias); |
8253 | | |
8254 | 5.23M | prop.call = co->opt->deprecation_message ? |
8255 | 3.64M | mp_property_deprecated_alias : mp_property_alias; |
8256 | | |
8257 | | // Check whether this eventually arrives at a real option. If not, |
8258 | | // it's some CLI special handling thing. For example, "nosound" is |
8259 | | // mapped to "no-audio", which has CLI special-handling, and cannot |
8260 | | // be set as property. |
8261 | 5.23M | struct m_config_option *co2 = co; |
8262 | 10.4M | while (co2 && co2->opt->type == &m_option_type_alias) { |
8263 | 5.23M | const char *co2_alias = m_config_shadow_get_alias_from_opt(mpctx->mconfig->shadow, co2->opt_id, |
8264 | 5.23M | buf, sizeof(buf)); |
8265 | 5.23M | co2 = m_config_get_co_raw(mpctx->mconfig, bstr0(co2_alias)); |
8266 | 5.23M | } |
8267 | 5.23M | if (!co2) |
8268 | 0 | continue; |
8269 | 5.23M | } |
8270 | | |
8271 | | // The option might be covered by a manual property already. |
8272 | 171M | if (bsearch(&prop.name, prop_names, num_base, sizeof(const char *), str_compare)) |
8273 | 5.46M | continue; |
8274 | | |
8275 | 166M | props[count++] = prop; |
8276 | 166M | } |
8277 | | |
8278 | 227k | ctx->properties = (struct m_property_list){props, count}; |
8279 | 227k | m_property_list_sort(&ctx->properties); |
8280 | | |
8281 | 227k | node_init(&ctx->mdata, MPV_FORMAT_NODE_ARRAY, NULL); |
8282 | 227k | talloc_steal(ctx, ctx->mdata.u.list); |
8283 | | |
8284 | 227k | node_init(&ctx->udata, MPV_FORMAT_NODE_MAP, NULL); |
8285 | 227k | talloc_steal(ctx, ctx->udata.u.list); |
8286 | 227k | talloc_free(prop_names); |
8287 | 227k | } |
8288 | | |
8289 | | static void command_event(struct MPContext *mpctx, int event, void *arg) |
8290 | 4.82M | { |
8291 | 4.82M | struct command_ctx *ctx = mpctx->command_ctx; |
8292 | | |
8293 | 4.82M | if (event == MPV_EVENT_START_FILE) { |
8294 | 556k | ctx->last_seek_pts = MP_NOPTS_VALUE; |
8295 | 556k | ctx->marked_pts = MP_NOPTS_VALUE; |
8296 | 556k | ctx->marked_permanent = false; |
8297 | 556k | } |
8298 | | |
8299 | 4.82M | if (event == MPV_EVENT_PLAYBACK_RESTART) { |
8300 | 112k | ctx->last_seek_time = mp_time_sec(); |
8301 | 112k | run_command_opts(mpctx); |
8302 | 112k | } |
8303 | | |
8304 | 4.82M | if (event == MPV_EVENT_VIDEO_RECONFIG && ctx->hwdec_osd_mode) { |
8305 | 0 | show_property_osd(mpctx, "hwdec", ctx->hwdec_osd_mode); |
8306 | 0 | ctx->hwdec_osd_mode = 0; |
8307 | 0 | } |
8308 | | |
8309 | 4.82M | if (event == MPV_EVENT_IDLE) |
8310 | 368k | run_command_opts(mpctx); |
8311 | | |
8312 | 4.82M | if (event == MPV_EVENT_END_FILE) |
8313 | 556k | mp_msg_flush_status_line(mpctx->log, false); |
8314 | | |
8315 | 4.82M | if (event == MPV_EVENT_END_FILE || event == MPV_EVENT_FILE_LOADED) { |
8316 | | // Update chapters - does nothing if something else is visible. |
8317 | 669k | set_osd_bar_chapters(mpctx, OSD_BAR_SEEK); |
8318 | 669k | } |
8319 | 4.82M | if (event == MP_EVENT_WIN_STATE2) |
8320 | 0 | ctx->cached_window_scale = 0; |
8321 | | |
8322 | 4.82M | if (event == MP_EVENT_METADATA_UPDATE) { |
8323 | 143k | struct playlist_entry *const pe = mpctx->playing; |
8324 | 143k | if (pe && !pe->title) { |
8325 | 143k | const char *const name = mp_find_non_filename_media_title(mpctx); |
8326 | 143k | if (name && name[0]) { |
8327 | 2.65k | pe->title = talloc_strdup(pe, name); |
8328 | 2.65k | mp_notify_property(mpctx, "playlist"); |
8329 | 2.65k | } |
8330 | 143k | } |
8331 | 143k | } |
8332 | 4.82M | } |
8333 | | |
8334 | | void handle_command_updates(struct MPContext *mpctx) |
8335 | 3.16M | { |
8336 | 3.16M | struct command_ctx *ctx = mpctx->command_ctx; |
8337 | | |
8338 | | // This is a bit messy: ao_hotplug wakes up the player, and then we have |
8339 | | // to recheck the state. Then the client(s) will read the property. |
8340 | 3.16M | if (ctx->hotplug && ao_hotplug_check_update(ctx->hotplug)) |
8341 | 0 | mp_notify_property(mpctx, "audio-device-list"); |
8342 | | |
8343 | | // Depends on polling demuxer wakeup callback notifications. |
8344 | 3.16M | cache_dump_poll(mpctx); |
8345 | 3.16M | } |
8346 | | |
8347 | | void run_command_opts(struct MPContext *mpctx) |
8348 | 481k | { |
8349 | 481k | struct MPOpts *opts = mpctx->opts; |
8350 | 481k | struct command_ctx *ctx = mpctx->command_ctx; |
8351 | | |
8352 | 481k | if (!opts->input_commands || ctx->command_opts_processed) |
8353 | 481k | return; |
8354 | | |
8355 | | // Take easy way out and add these to the input queue. |
8356 | 0 | for (int i = 0; opts->input_commands[i]; i++) { |
8357 | 0 | struct mp_cmd *cmd = mp_input_parse_cmd(mpctx->input, bstr0(opts->input_commands[i]), |
8358 | 0 | "the command line"); |
8359 | 0 | if (cmd) { |
8360 | 0 | cmd->coalesce = true; |
8361 | 0 | mp_input_queue_cmd(mpctx->input, cmd); |
8362 | 0 | } |
8363 | 0 | } |
8364 | 0 | ctx->command_opts_processed = true; |
8365 | 0 | } |
8366 | | |
8367 | | void mp_notify(struct MPContext *mpctx, int event, void *arg) |
8368 | 4.82M | { |
8369 | | // The OSD can implicitly reference some properties. |
8370 | 4.82M | mpctx->osd_idle_update = true; |
8371 | | |
8372 | 4.82M | command_event(mpctx, event, arg); |
8373 | | |
8374 | 4.82M | mp_client_broadcast_event(mpctx, event, arg); |
8375 | 4.82M | } |
8376 | | |
8377 | | static void update_priority(struct MPContext *mpctx) |
8378 | 212k | { |
8379 | | #if HAVE_WIN32_DESKTOP |
8380 | | struct MPOpts *opts = mpctx->opts; |
8381 | | if (opts->w32_priority > 0) |
8382 | | SetPriorityClass(GetCurrentProcess(), opts->w32_priority); |
8383 | | #endif |
8384 | 212k | } |
8385 | | |
8386 | | static void update_track_switch(struct MPContext *mpctx, int order, int type) |
8387 | 158 | { |
8388 | 158 | if (!mpctx->playback_initialized) |
8389 | 158 | return; |
8390 | | |
8391 | 0 | int tid = mpctx->opts->stream_id[order][type]; |
8392 | 0 | struct track *track; |
8393 | 0 | if (tid == -1) { |
8394 | | // If "auto" reset to default track selection |
8395 | 0 | track = select_default_track(mpctx, order, type); |
8396 | 0 | mark_track_selection(mpctx, order, type, -1); |
8397 | 0 | } else { |
8398 | 0 | track = mp_track_by_tid(mpctx, type, tid); |
8399 | 0 | } |
8400 | 0 | mp_switch_track_n(mpctx, order, type, track, (tid == -1) ? 0 : FLAG_MARK_SELECTION); |
8401 | 0 | print_track_list(mpctx, "Track switched:"); |
8402 | 0 | mp_wakeup_core(mpctx); |
8403 | 0 | } |
8404 | | |
8405 | | void mp_option_change_callback(void *ctx, struct m_config_option *co, uint64_t flags, |
8406 | | bool self_update) |
8407 | 486k | { |
8408 | 486k | struct MPContext *mpctx = ctx; |
8409 | 486k | struct MPOpts *opts = mpctx->opts; |
8410 | 486k | void *opt_ptr = !co ? NULL : co->data; // NULL on start |
8411 | | |
8412 | 486k | if (co) |
8413 | 274k | mp_notify_property(mpctx, co->name); |
8414 | 486k | if (opt_ptr == &opts->media_title) |
8415 | 290 | mp_notify(mpctx, MP_EVENT_METADATA_UPDATE, NULL); |
8416 | | |
8417 | 486k | if (self_update) |
8418 | 191 | return; |
8419 | | |
8420 | | // Run these immediately. |
8421 | 486k | if (co && !co->coalesce) { |
8422 | 76.0k | struct mp_option_callback callback = {.co = co, .flags = flags}; |
8423 | 76.0k | mp_option_run_callback(mpctx, &callback); |
8424 | 76.0k | return; |
8425 | 76.0k | } |
8426 | | |
8427 | | // Coalesce redundant updates and only keep the newest one. |
8428 | 486k | bool drop = false; |
8429 | 781k | for (int i = 0; i < mpctx->num_option_callbacks; i++) { |
8430 | 370k | if ((mpctx->option_callbacks[i].co && opt_ptr == mpctx->option_callbacks[i].co->data) || |
8431 | 189k | (flags && flags == mpctx->option_callbacks[i].flags)) |
8432 | 185k | drop = true; |
8433 | 370k | if (!drop) |
8434 | 175k | continue; |
8435 | 194k | if (i < mpctx->num_option_callbacks - 1) |
8436 | 9.39k | mpctx->option_callbacks[i] = mpctx->option_callbacks[i + 1]; |
8437 | 194k | if (i == mpctx->num_option_callbacks - 1) { |
8438 | 185k | mpctx->option_callbacks[i].co = co; |
8439 | 185k | mpctx->option_callbacks[i].flags = flags; |
8440 | 185k | } |
8441 | 194k | } |
8442 | | |
8443 | 410k | if (!drop) { |
8444 | 225k | struct mp_option_callback callback = {.co = co, .flags = flags}; |
8445 | 225k | MP_TARRAY_APPEND(mpctx, mpctx->option_callbacks, mpctx->num_option_callbacks, callback); |
8446 | 225k | } |
8447 | 410k | } |
8448 | | |
8449 | | void mp_option_run_callback(struct MPContext *mpctx, struct mp_option_callback *callback) |
8450 | 301k | { |
8451 | 301k | struct MPOpts *opts = mpctx->opts; |
8452 | 301k | struct m_config_option *co = callback->co; |
8453 | 301k | void *opt_ptr = co ? co->data : NULL; |
8454 | 301k | uint64_t flags = callback->flags; |
8455 | | |
8456 | 301k | if (flags & UPDATE_TERM) |
8457 | 213k | mp_update_logging(mpctx, false); |
8458 | | |
8459 | 301k | if (flags & (UPDATE_OSD | UPDATE_SUB_FILT | UPDATE_SUB_HARD)) { |
8460 | 642k | for (int n = 0; n < num_ptracks[STREAM_SUB]; n++) { |
8461 | 428k | struct track *track = mpctx->current_track[n][STREAM_SUB]; |
8462 | 428k | struct dec_sub *sub = track ? track->d_sub : NULL; |
8463 | 428k | if (sub) { |
8464 | 0 | int ret = sub_control(sub, SD_CTRL_UPDATE_OPTS, &flags); |
8465 | 0 | if (ret == CONTROL_OK && flags & (UPDATE_SUB_FILT | UPDATE_SUB_HARD)) { |
8466 | 0 | sub_redecode_cached_packets(sub); |
8467 | 0 | } |
8468 | 0 | } |
8469 | 428k | } |
8470 | | // For subs on a still image. |
8471 | 214k | redraw_subs(mpctx); |
8472 | 214k | osd_changed(mpctx->osd); |
8473 | 214k | } |
8474 | | |
8475 | 301k | if (flags & UPDATE_BUILTIN_SCRIPTS) |
8476 | 212k | mp_load_builtin_scripts(mpctx); |
8477 | | |
8478 | 301k | if (flags & UPDATE_IMGPAR) { |
8479 | 213k | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
8480 | 213k | if (track && track->dec) { |
8481 | 14 | mp_decoder_wrapper_reset_params(track->dec); |
8482 | 14 | mp_force_video_refresh(mpctx); |
8483 | 14 | } |
8484 | 213k | } |
8485 | | |
8486 | 301k | if (flags & UPDATE_INPUT) |
8487 | 212k | mp_input_update_opts(mpctx->input); |
8488 | | |
8489 | 301k | if (flags & UPDATE_CLIPBOARD) |
8490 | 212k | reinit_clipboard(mpctx); |
8491 | | |
8492 | 301k | if (opt_ptr == &opts->ipc_path || opt_ptr == &opts->ipc_client) { |
8493 | 0 | mp_uninit_ipc(mpctx->ipc_ctx); |
8494 | 0 | mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global); |
8495 | 0 | } |
8496 | | |
8497 | 301k | if (flags & UPDATE_VO && mpctx->video_out) { |
8498 | 730 | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
8499 | 730 | struct mp_pin *filter_src = mpctx->vo_chain ? mpctx->vo_chain->filter_src : NULL; |
8500 | 730 | uninit_video_out(mpctx); |
8501 | 730 | handle_force_window(mpctx, true); |
8502 | | |
8503 | 730 | if (filter_src) { |
8504 | 0 | reinit_video_chain_src(mpctx, NULL); |
8505 | 0 | if (mpctx->vo_chain) { |
8506 | 0 | mpctx->vo_chain->filter_src = filter_src; |
8507 | 0 | mp_pin_connect(mpctx->vo_chain->filter->f->pins[0], filter_src); |
8508 | 0 | } |
8509 | 730 | } else { |
8510 | 730 | reinit_video_chain(mpctx); |
8511 | 730 | } |
8512 | | |
8513 | 730 | if (track || filter_src) |
8514 | 0 | queue_seek(mpctx, MPSEEK_RELATIVE, 0.0, MPSEEK_EXACT, 0); |
8515 | | |
8516 | 730 | mp_wakeup_core(mpctx); |
8517 | 730 | } |
8518 | | |
8519 | 301k | if (flags & UPDATE_AUDIO) |
8520 | 213k | reload_audio_output(mpctx); |
8521 | | |
8522 | 301k | if (flags & UPDATE_PRIORITY) |
8523 | 212k | update_priority(mpctx); |
8524 | | |
8525 | 301k | if (flags & UPDATE_SCREENSAVER) |
8526 | 212k | update_screensaver_state(mpctx); |
8527 | | |
8528 | 301k | if (flags & UPDATE_VOL) |
8529 | 212k | audio_update_volume(mpctx); |
8530 | | |
8531 | 301k | if (flags & UPDATE_LAVFI_COMPLEX) |
8532 | 212k | update_lavfi_complex(mpctx); |
8533 | | |
8534 | 301k | if (flags & UPDATE_VIDEO) { |
8535 | 213k | if (mpctx->video_out) { |
8536 | 11 | vo_control(mpctx->video_out, VOCTRL_UPDATE_RENDER_OPTS, NULL); |
8537 | 11 | mp_wakeup_core(mpctx); |
8538 | 11 | } |
8539 | 213k | } |
8540 | | |
8541 | 301k | if (flags & UPDATE_HWDEC) { |
8542 | 212k | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
8543 | 212k | struct mp_decoder_wrapper *dec = track ? track->dec : NULL; |
8544 | 212k | if (dec) { |
8545 | 0 | mp_decoder_wrapper_control(dec, VDCTRL_REINIT, NULL); |
8546 | | // filter chain might not change (which normally triggers this), but |
8547 | | // hwdec will. |
8548 | 0 | mp_notify(mpctx, MPV_EVENT_VIDEO_RECONFIG, NULL); |
8549 | 0 | double last_pts = mpctx->video_pts; |
8550 | 0 | if (last_pts != MP_NOPTS_VALUE) |
8551 | 0 | queue_seek(mpctx, MPSEEK_ABSOLUTE, last_pts, MPSEEK_EXACT, 0); |
8552 | 0 | } |
8553 | 212k | } |
8554 | | |
8555 | 301k | if (flags & UPDATE_DVB_PROG) { |
8556 | 212k | if (!mpctx->stop_play) |
8557 | 0 | mpctx->stop_play = PT_CURRENT_ENTRY; |
8558 | 212k | } |
8559 | | |
8560 | 301k | if (flags & UPDATE_DEMUXER) |
8561 | 212k | mpctx->demuxer_changed = true; |
8562 | | |
8563 | 301k | if (flags & UPDATE_AD && mpctx->ao_chain) { |
8564 | 0 | uninit_audio_chain(mpctx); |
8565 | 0 | reinit_audio_chain(mpctx); |
8566 | 0 | } |
8567 | | |
8568 | 301k | if (flags & UPDATE_VD && mpctx->vo_chain) { |
8569 | 0 | struct track *track = mpctx->current_track[0][STREAM_VIDEO]; |
8570 | 0 | uninit_video_chain(mpctx); |
8571 | 0 | reinit_video_chain(mpctx); |
8572 | 0 | if (track) |
8573 | 0 | queue_seek(mpctx, MPSEEK_RELATIVE, 0.0, MPSEEK_EXACT, 0); |
8574 | 0 | } |
8575 | | |
8576 | 301k | if (opt_ptr == &opts->vo->android_surface_size || opt_ptr == &opts->vo->d3d11_composition_size) { |
8577 | 0 | if (mpctx->video_out) |
8578 | 0 | vo_control(mpctx->video_out, VOCTRL_EXTERNAL_RESIZE, NULL); |
8579 | 0 | } |
8580 | | |
8581 | 301k | if (opt_ptr == &opts->input_commands) { |
8582 | 0 | mpctx->command_ctx->command_opts_processed = false; |
8583 | 0 | run_command_opts(mpctx); |
8584 | 0 | } |
8585 | | |
8586 | 301k | if (opt_ptr == &opts->playback_speed || opt_ptr == &opts->playback_pitch || |
8587 | 299k | opt_ptr == &opts->pitch_correction) { |
8588 | 1.41k | update_playback_speed(mpctx); |
8589 | 1.41k | mp_wakeup_core(mpctx); |
8590 | 1.41k | } |
8591 | | |
8592 | 301k | if (opt_ptr == &opts->play_dir) { |
8593 | 2 | if (mpctx->play_dir != opts->play_dir) { |
8594 | | // The option must be set before we seek if we're at EOF. |
8595 | 2 | if (mpctx->stop_play == AT_END_OF_FILE) |
8596 | 0 | mpctx->play_dir = opts->play_dir; |
8597 | 2 | queue_seek(mpctx, MPSEEK_ABSOLUTE, get_current_time(mpctx), |
8598 | 2 | MPSEEK_EXACT, 0); |
8599 | 2 | } |
8600 | 2 | } |
8601 | | |
8602 | 301k | if (opt_ptr == &opts->edition_id) { |
8603 | 3 | struct demuxer *demuxer = mpctx->demuxer; |
8604 | 3 | if (mpctx->playback_initialized && demuxer && demuxer->num_editions > 0) { |
8605 | 0 | if (opts->edition_id != demuxer->edition) { |
8606 | 0 | if (demuxer->edition_is_track_mapping) { |
8607 | 0 | int new_ed = opts->edition_id; |
8608 | 0 | if (new_ed < 0 || new_ed >= demuxer->num_editions) |
8609 | 0 | new_ed = 0; |
8610 | 0 | demuxer->edition = new_ed; |
8611 | 0 | for (int t = 0; t < STREAM_TYPE_COUNT; t++) { |
8612 | 0 | for (int i = 0; i < num_ptracks[t]; i++) { |
8613 | 0 | struct track *sel = select_default_track(mpctx, i, t); |
8614 | 0 | if (sel != mpctx->current_track[i][t]) |
8615 | 0 | mp_switch_track_n(mpctx, i, t, sel, 0); |
8616 | 0 | } |
8617 | 0 | } |
8618 | 0 | if (demuxer->ts_resets_possible) { |
8619 | 0 | reset_playback_state(mpctx); |
8620 | 0 | demux_flush(demuxer); |
8621 | 0 | } |
8622 | 0 | mp_notify_property(mpctx, "current-edition"); |
8623 | 0 | print_track_list(mpctx, |
8624 | 0 | mp_tprintf(42, "Selected edition %d:", demuxer->edition)); |
8625 | 0 | } else if (disc_nav_get_stream(mpctx)) { |
8626 | 0 | unsigned new_title = opts->edition_id; |
8627 | 0 | stream_control(demuxer->stream, |
8628 | 0 | STREAM_CTRL_SET_CURRENT_TITLE, &new_title); |
8629 | 0 | } else { |
8630 | 0 | if (!mpctx->stop_play) |
8631 | 0 | mpctx->stop_play = PT_CURRENT_ENTRY; |
8632 | 0 | } |
8633 | 0 | mp_wakeup_core(mpctx); |
8634 | 0 | } |
8635 | 0 | } |
8636 | 3 | } |
8637 | | |
8638 | | #if HAVE_LIBBLURAY |
8639 | | if (opt_ptr == &opts->stream_bluray_opts->angle) { |
8640 | | struct demuxer *demuxer = mpctx->demuxer; |
8641 | | if (mpctx->playback_initialized && demuxer && demuxer->stream && |
8642 | | (!strcmp(demuxer->stream->info->name, "bd") || |
8643 | | !strcmp(demuxer->stream->info->name, "bdmv/bluray"))) { |
8644 | | int angle = opts->stream_bluray_opts->angle; |
8645 | | stream_control(demuxer->stream, STREAM_CTRL_SET_ANGLE, &angle); |
8646 | | } |
8647 | | } |
8648 | | #endif |
8649 | | |
8650 | | #if HAVE_DVDA |
8651 | | if (opt_ptr == &opts->dvda_opts->page) { |
8652 | | struct demuxer *demuxer = mpctx->demuxer; |
8653 | | if (mpctx->playback_initialized && demuxer && demuxer->stream && |
8654 | | (!strcmp(demuxer->stream->info->name, "dvda") || |
8655 | | !strcmp(demuxer->stream->info->name, "ifo_dvda"))) { |
8656 | | int page = opts->dvda_opts->page; |
8657 | | stream_control(demuxer->stream, STREAM_CTRL_SET_STILL_PAGE, &page); |
8658 | | } |
8659 | | } |
8660 | | #endif |
8661 | | |
8662 | 301k | if (opt_ptr == &opts->pause) |
8663 | 114 | set_pause_state(mpctx, opts->pause); |
8664 | | |
8665 | 301k | if (opt_ptr == &opts->audio_delay) { |
8666 | 352 | if (mpctx->ao_chain) { |
8667 | 0 | mpctx->delay += mpctx->opts->audio_delay - mpctx->ao_chain->delay; |
8668 | 0 | mpctx->ao_chain->delay = mpctx->opts->audio_delay; |
8669 | 0 | } |
8670 | 352 | mp_wakeup_core(mpctx); |
8671 | 352 | } |
8672 | | |
8673 | 301k | if (opt_ptr == &opts->vo->window_scale) |
8674 | 3 | update_window_scale(mpctx); |
8675 | | |
8676 | 301k | if (opt_ptr == &opts->vo->hidpi_window_scale) |
8677 | 2 | update_hidpi_window_scale(mpctx, opts->vo->hidpi_window_scale); |
8678 | | |
8679 | 301k | if (opt_ptr == &opts->cursor_autohide_delay) |
8680 | 0 | mpctx->mouse_timer = 0; |
8681 | | |
8682 | 301k | if (opt_ptr == &opts->loop_file) { |
8683 | 15.5k | mpctx->remaining_file_loops = opts->loop_file; |
8684 | 15.5k | mp_notify_property(mpctx, "remaining-file-loops"); |
8685 | 15.5k | } |
8686 | | |
8687 | 301k | if (opt_ptr == &opts->ab_loop[0] || opt_ptr == &opts->ab_loop[1] || |
8688 | 301k | opt_ptr == &opts->ab_loop_count) { |
8689 | 117 | mpctx->remaining_ab_loops = opts->ab_loop_count; |
8690 | 117 | mp_notify_property(mpctx, "remaining-ab-loops"); |
8691 | 117 | } |
8692 | | |
8693 | 301k | if (opt_ptr == &opts->ab_loop[0] || opt_ptr == &opts->ab_loop[1]) { |
8694 | 85 | update_ab_loop_clip(mpctx); |
8695 | | // Update if visible |
8696 | 85 | set_osd_bar_chapters(mpctx, OSD_BAR_SEEK); |
8697 | 85 | mp_wakeup_core(mpctx); |
8698 | 85 | } |
8699 | | |
8700 | 301k | if (opt_ptr == &opts->vf_settings) |
8701 | 6.32k | set_filters(mpctx, STREAM_VIDEO, opts->vf_settings); |
8702 | | |
8703 | 301k | if (opt_ptr == &opts->af_settings) |
8704 | 1.92k | set_filters(mpctx, STREAM_AUDIO, opts->af_settings); |
8705 | | |
8706 | 1.20M | for (int type = 0; type < STREAM_TYPE_COUNT; type++) { |
8707 | 2.10M | for (int order = 0; order < num_ptracks[type]; order++) { |
8708 | 1.20M | if (opt_ptr == &opts->stream_id[order][type]) |
8709 | 158 | update_track_switch(mpctx, order, type); |
8710 | 1.20M | } |
8711 | 903k | } |
8712 | | |
8713 | 301k | if (opt_ptr == &opts->vo->fullscreen && !opts->vo->fullscreen) |
8714 | 14 | mpctx->mouse_event_ts--; // Show mouse cursor |
8715 | | |
8716 | 301k | if (opt_ptr == &opts->vo->taskbar_progress) |
8717 | 0 | update_vo_playback_state(mpctx); |
8718 | | |
8719 | 301k | if (opt_ptr == &opts->force_vo) |
8720 | 2.47k | handle_force_window(mpctx, false); |
8721 | | |
8722 | 301k | if (opt_ptr == &opts->image_display_duration && mpctx->vo_chain |
8723 | 0 | && mpctx->vo_chain->is_sparse && !mpctx->ao_chain |
8724 | 0 | && mpctx->video_status == STATUS_DRAINING) |
8725 | 0 | mpctx->time_frame = opts->image_display_duration / opts->playback_speed; |
8726 | 301k | } |
8727 | | |
8728 | | void mp_notify_property(struct MPContext *mpctx, const char *property) |
8729 | 663k | { |
8730 | 663k | mp_client_property_change(mpctx, property); |
8731 | 663k | } |