Coverage Report

Created: 2026-11-11 06:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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, &current))
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(&params, &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, &params) == 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, &params) == 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, &params);
4670
0
        case M_PROPERTY_SET:
4671
0
            return set_clipboard(mpctx, act->arg, &params);
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, &params, 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
}