Coverage Report

Created: 2025-12-27 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/player/main.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 <stdio.h>
19
#include <stdlib.h>
20
#include <stdbool.h>
21
#include <math.h>
22
#include <assert.h>
23
#include <string.h>
24
#include <locale.h>
25
26
#include "config.h"
27
28
#include <libplacebo/config.h>
29
30
#include "mpv_talloc.h"
31
32
#include "misc/dispatch.h"
33
#include "misc/random.h"
34
#include "misc/thread_pool.h"
35
#include "osdep/io.h"
36
#include "osdep/terminal.h"
37
#include "osdep/threads.h"
38
#include "osdep/timer.h"
39
#include "osdep/main-fn.h"
40
41
#include "common/av_log.h"
42
#include "common/codecs.h"
43
#include "common/encode.h"
44
#include "options/m_config.h"
45
#include "options/m_option.h"
46
#include "options/m_property.h"
47
#include "common/common.h"
48
#include "common/encode_lavc.h"
49
#include "common/msg.h"
50
#include "common/msg_control.h"
51
#include "common/stats.h"
52
#include "common/global.h"
53
#include "filters/f_decoder_wrapper.h"
54
#include "options/parse_configfile.h"
55
#include "options/parse_commandline.h"
56
#include "common/playlist.h"
57
#include "options/options.h"
58
#include "options/path.h"
59
#include "input/input.h"
60
#include "demux/packet_pool.h"
61
62
#include "audio/out/ao.h"
63
#include "misc/thread_tools.h"
64
#include "sub/osd.h"
65
#include "video/out/vo.h"
66
67
#include "core.h"
68
#include "client.h"
69
#include "command.h"
70
#include "screenshot.h"
71
72
static const char def_config[] =
73
#include "etc/builtin.conf.inc"
74
;
75
76
#if HAVE_WIN32_SMTC
77
#include "osdep/win32/smtc.h"
78
#endif
79
80
#if HAVE_WIN32_DESKTOP
81
#include "osdep/w32_register.h"
82
#endif
83
84
#if HAVE_COCOA
85
#include "osdep/mac/app_bridge.h"
86
#endif
87
88
#ifndef FULLCONFIG
89
#define FULLCONFIG "(missing)\n"
90
#endif
91
92
enum exit_reason {
93
    EXIT_NONE,
94
    EXIT_NORMAL,
95
    EXIT_ERROR,
96
};
97
98
const char mp_help_text[] =
99
"Usage:   mpv [options] [url|path/]filename\n"
100
"\n"
101
"Basic options:\n"
102
" --start=<time>    seek to given (percent, seconds, or hh:mm:ss) position\n"
103
" --no-audio        do not play sound\n"
104
" --no-video        do not play video\n"
105
" --fs              fullscreen playback\n"
106
" --sub-file=<file> specify subtitle file to use\n"
107
" --playlist=<file> specify playlist file\n"
108
"\n"
109
" --list-options    list all mpv options\n"
110
" --h=<string>      print options which contain the given string in their name\n";
111
112
static mp_static_mutex terminal_owner_lock = MP_STATIC_MUTEX_INITIALIZER;
113
static struct MPContext *terminal_owner;
114
115
static bool cas_terminal_owner(struct MPContext *old, struct MPContext *new)
116
456k
{
117
456k
    mp_mutex_lock(&terminal_owner_lock);
118
456k
    bool r = terminal_owner == old;
119
456k
    if (r)
120
14.2k
        terminal_owner = new;
121
456k
    mp_mutex_unlock(&terminal_owner_lock);
122
456k
    return r;
123
456k
}
124
125
void mp_update_logging(struct MPContext *mpctx, bool preinit)
126
289k
{
127
289k
    bool had_log_file = mp_msg_has_log_file(mpctx->global);
128
129
289k
    mp_msg_update_msglevels(mpctx->global, mpctx->opts);
130
131
289k
    bool enable = mpctx->opts->use_terminal;
132
289k
    bool enabled = cas_terminal_owner(mpctx, mpctx);
133
289k
    if (enable != enabled) {
134
3.72k
        if (enable && cas_terminal_owner(NULL, mpctx)) {
135
3.72k
            terminal_init();
136
3.72k
            enabled = true;
137
3.72k
        } else if (!enable) {
138
2
            terminal_uninit();
139
2
            cas_terminal_owner(mpctx, NULL);
140
2
        }
141
3.72k
    }
142
143
289k
    if (mp_msg_has_log_file(mpctx->global) && !had_log_file) {
144
        // for log-file=... in config files.
145
        // we did flush earlier messages, but they were in a cyclic buffer, so
146
        // the version might have been overwritten. ensure we have it.
147
0
        mp_print_version(mpctx->log, false);
148
0
    }
149
150
289k
    if (enabled && !preinit && mpctx->opts->consolecontrols)
151
10
        terminal_setup_getch(mpctx->input);
152
153
289k
    if (enabled)
154
6.79k
        encoder_update_log(mpctx->global);
155
289k
}
156
157
void mp_print_version(struct mp_log *log, int always)
158
147k
{
159
147k
    int v = always ? MSGL_INFO : MSGL_V;
160
147k
    mp_msg(log, v, "%s %s\n", mpv_version, mpv_copyright);
161
147k
    if (strcmp(mpv_builddate, "UNKNOWN"))
162
147k
        mp_msg(log, v, " built on %s\n", mpv_builddate);
163
147k
    mp_msg(log, v, "libplacebo version: %s\n", PL_VERSION);
164
147k
    check_library_versions(log, v);
165
    // Only in verbose mode.
166
147k
    if (!always) {
167
146k
        mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n");
168
146k
        mp_msg(log, MSGL_V, "List of enabled features: " FULLCONFIG "\n");
169
        #ifdef NDEBUG
170
            mp_msg(log, MSGL_V, "Built with NDEBUG.\n");
171
        #endif
172
146k
    }
173
147k
}
174
175
void mp_destroy(struct MPContext *mpctx)
176
159k
{
177
159k
    mp_shutdown_clients(mpctx);
178
179
159k
    mp_uninit_ipc(mpctx->ipc_ctx);
180
159k
    mpctx->ipc_ctx = NULL;
181
182
159k
    uninit_audio_out(mpctx);
183
159k
    uninit_video_out(mpctx);
184
185
    // If it's still set here, it's an error.
186
159k
    encode_lavc_free(mpctx->encode_lavc_ctx);
187
159k
    mpctx->encode_lavc_ctx = NULL;
188
189
159k
    command_uninit(mpctx);
190
191
159k
    mp_clients_destroy(mpctx);
192
193
159k
    osd_free(mpctx->osd);
194
195
#if HAVE_COCOA
196
    cocoa_set_input_context(NULL);
197
#endif
198
199
159k
    if (cas_terminal_owner(mpctx, mpctx)) {
200
3.72k
        terminal_uninit();
201
3.72k
        cas_terminal_owner(mpctx, NULL);
202
3.72k
    }
203
204
159k
    mp_input_uninit(mpctx->input);
205
159k
    mp_clipboard_destroy(mpctx->clipboard);
206
207
159k
    uninit_libav(mpctx->global);
208
209
159k
    mp_msg_uninit(mpctx->global);
210
159k
    mp_assert(!mpctx->num_abort_list);
211
159k
    talloc_free(mpctx->abort_list);
212
159k
    mp_mutex_destroy(&mpctx->abort_lock);
213
159k
    talloc_free(mpctx->mconfig); // destroy before dispatch
214
159k
    talloc_free(mpctx);
215
159k
}
216
217
static bool handle_help_options(struct MPContext *mpctx)
218
142k
{
219
142k
    struct MPOpts *opts = mpctx->opts;
220
142k
    struct mp_log *log = mpctx->log;
221
142k
    if (opts->ao_opts->audio_device &&
222
142k
        strcmp(opts->ao_opts->audio_device, "help") == 0)
223
0
    {
224
0
        ao_print_devices(mpctx->global, log, mpctx->ao);
225
0
        return true;
226
0
    }
227
142k
    if (opts->property_print_help) {
228
0
        property_print_help(mpctx);
229
0
        return true;
230
0
    }
231
142k
    if (encode_lavc_showhelp(log, opts->encode_opts))
232
22
        return true;
233
142k
    return false;
234
142k
}
235
236
static int cfg_include(void *ctx, char *filename, int flags)
237
0
{
238
0
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
239
0
    return 1;
240
0
#endif
241
0
    struct MPContext *mpctx = ctx;
242
0
    char *fname = mp_get_user_path(NULL, mpctx->global, filename);
243
0
    int r = m_config_parse_config_file(mpctx->mconfig, mpctx->global, fname, NULL, flags);
244
0
    talloc_free(fname);
245
0
    return r;
246
0
}
247
248
// We mostly care about LC_NUMERIC, and how "." vs. "," is treated,
249
// Other locale stuff might break too, but probably isn't too bad.
250
static bool check_locale(void)
251
159k
{
252
159k
    char *name = setlocale(LC_NUMERIC, NULL);
253
159k
    return !name || strcmp(name, "C") == 0 || strcmp(name, "C.UTF-8") == 0;
254
159k
}
255
256
struct MPContext *mp_create(void)
257
159k
{
258
159k
    if (!check_locale()) {
259
        // Normally, we never print anything (except if the "terminal" option
260
        // is enabled), so this is an exception.
261
0
        fprintf(stderr, "Non-C locale detected. This is not supported.\n"
262
0
                        "Call 'setlocale(LC_NUMERIC, \"C\");' in your code.\n");
263
0
        return NULL;
264
0
    }
265
266
159k
    char *enable_talloc = getenv("MPV_LEAK_REPORT");
267
159k
    if (enable_talloc && strcmp(enable_talloc, "1") == 0)
268
0
        talloc_enable_leak_report();
269
270
159k
    mp_time_init();
271
272
159k
    struct MPContext *mpctx = talloc(NULL, MPContext);
273
159k
    *mpctx = (struct MPContext){
274
159k
        .last_chapter = -2,
275
159k
        .term_osd_contents = talloc_strdup(mpctx, ""),
276
159k
        .osd_progbar = { .type = -1 },
277
159k
        .playlist = talloc_zero(mpctx, struct playlist),
278
159k
        .dispatch = mp_dispatch_create(mpctx),
279
159k
        .playback_abort = mp_cancel_new(mpctx),
280
159k
        .thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30),
281
159k
        .stop_play = PT_NEXT_ENTRY,
282
159k
        .play_dir = 1,
283
159k
    };
284
285
159k
    mp_mutex_init(&mpctx->abort_lock);
286
287
159k
    mpctx->global = talloc_zero(mpctx, struct mpv_global);
288
289
159k
    demux_packet_pool_init(mpctx->global);
290
159k
    stats_global_init(mpctx->global);
291
292
    // Nothing must call mp_msg*() and related before this
293
159k
    mp_msg_init(mpctx->global);
294
159k
    mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer");
295
159k
    mpctx->statusline = mp_log_new(mpctx, mpctx->log, "!statusline");
296
297
159k
    mpctx->stats = stats_ctx_create(mpctx, mpctx->global, "main");
298
299
    // Create the config context and register the options
300
159k
    mpctx->mconfig = m_config_new(mpctx, mpctx->log, &mp_opt_root);
301
159k
    mpctx->opts = mpctx->mconfig->optstruct;
302
159k
    mpctx->global->config = mpctx->mconfig->shadow;
303
159k
    mpctx->mconfig->includefunc = cfg_include;
304
159k
    mpctx->mconfig->includefunc_ctx = mpctx;
305
159k
    mpctx->mconfig->use_profiles = true;
306
159k
    mpctx->mconfig->is_toplevel = true;
307
159k
    mpctx->mconfig->global = mpctx->global;
308
159k
    m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0);
309
310
159k
    mpctx->input = mp_input_init(mpctx->global, mp_wakeup_core_cb, mpctx);
311
159k
    screenshot_init(mpctx);
312
159k
    command_init(mpctx);
313
159k
    init_libav(mpctx->global);
314
159k
    mp_clients_init(mpctx);
315
159k
    mpctx->osd = osd_create(mpctx->global);
316
317
#if HAVE_COCOA
318
    cocoa_set_input_context(mpctx->input);
319
#endif
320
321
159k
    char *verbose_env = getenv("MPV_VERBOSE");
322
159k
    if (verbose_env)
323
0
        mpctx->opts->verbose = atoi(verbose_env);
324
325
159k
    mp_cancel_trigger(mpctx->playback_abort);
326
327
159k
    return mpctx;
328
159k
}
329
330
// Finish mpctx initialization. This must be done after setting up all options.
331
// Some of the initializations depend on the options, and can't be changed or
332
// undone later.
333
// If options is not NULL, apply them as command line player arguments.
334
// Returns: 0 on success, -1 on error, 1 if exiting normally (e.g. help).
335
int mp_initialize(struct MPContext *mpctx, char **options)
336
146k
{
337
146k
    struct MPOpts *opts = mpctx->opts;
338
339
146k
    mp_assert(!mpctx->initialized);
340
341
    // Preparse the command line, so we can init the terminal early.
342
146k
    if (options) {
343
17.0k
        m_config_preparse_command_line(mpctx->mconfig, mpctx->global,
344
17.0k
                                       &opts->verbose, options);
345
17.0k
    }
346
347
146k
    mp_init_paths(mpctx->global, opts);
348
146k
    mp_msg_set_early_logging(mpctx->global, true);
349
146k
    mp_update_logging(mpctx, true);
350
351
146k
    if (options) {
352
17.0k
        MP_VERBOSE(mpctx, "Command line options:");
353
275k
        for (int i = 0; options[i]; i++)
354
258k
            MP_VERBOSE(mpctx, " '%s'", options[i]);
355
17.0k
        MP_VERBOSE(mpctx, "\n");
356
17.0k
    }
357
358
146k
    mp_print_version(mpctx->log, false);
359
360
146k
    mp_parse_cfgfiles(mpctx);
361
362
146k
    if (options) {
363
17.0k
        int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist,
364
17.0k
                                               mpctx->global, options);
365
17.0k
        if (r < 0)
366
4.74k
            return r == M_OPT_EXIT ? 1 : -1;
367
17.0k
    }
368
369
142k
    if (opts->operation_mode == 1) {
370
4
        m_config_set_profile(mpctx->mconfig, "builtin-pseudo-gui",
371
4
                             M_SETOPT_NO_OVERWRITE);
372
4
        m_config_set_profile(mpctx->mconfig, "pseudo-gui", 0);
373
4
    }
374
375
    // Backup the default settings, which should not be stored in the resume
376
    // config files. This explicitly includes values set by config files and
377
    // the command line.
378
142k
    m_config_backup_watch_later_opts(mpctx->mconfig);
379
380
142k
    mp_input_load_config(mpctx->input);
381
382
    // From this point on, all mpctx members are initialized.
383
142k
    mpctx->initialized = true;
384
142k
    mpctx->mconfig->option_change_callback = mp_option_change_callback;
385
142k
    mpctx->mconfig->option_change_callback_ctx = mpctx;
386
142k
    m_config_set_update_dispatch_queue(mpctx->mconfig, mpctx->dispatch);
387
    // Run all update handlers.
388
142k
    mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK, false);
389
142k
    handle_option_callbacks(mpctx);
390
391
142k
    if (handle_help_options(mpctx))
392
22
        return 1; // help
393
394
#if HAVE_WIN32_DESKTOP
395
    if (mp_w32_handle_register(mpctx))
396
        return 1; // register/unregister
397
#endif
398
399
142k
    check_library_versions(mp_null_log, 0);
400
401
142k
    if (!mpctx->playlist->num_entries && !opts->player_idle_mode &&
402
4
        options)
403
4
    {
404
        // nothing to play
405
4
        mp_print_version(mpctx->log, true);
406
4
        MP_INFO(mpctx, "%s", mp_help_text);
407
4
        return 1;
408
4
    }
409
410
142k
    MP_STATS(mpctx, "start init");
411
412
#if HAVE_COCOA
413
    mpv_handle *ctx = mp_new_client(mpctx->clients, "mac");
414
    cocoa_set_mpv_handle(ctx);
415
#endif
416
417
#if HAVE_WIN32_SMTC
418
    if (opts->media_controls)
419
        mp_smtc_init(mp_new_client(mpctx->clients, "SystemMediaTransportControls"));
420
#endif
421
422
142k
    mpctx->ipc_ctx = mp_init_ipc(mpctx->clients, mpctx->global);
423
424
142k
    if (opts->encode_opts->file && opts->encode_opts->file[0]) {
425
250
        mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global);
426
250
        if(!mpctx->encode_lavc_ctx) {
427
249
            MP_INFO(mpctx, "Encoding initialization failed.\n");
428
249
            return -1;
429
249
        }
430
250
    }
431
432
141k
    mp_load_scripts(mpctx);
433
434
141k
    if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0)
435
0
        return -1;
436
437
    // Needed to properly enter _initial_ idle mode if playlist empty.
438
141k
    if (mpctx->opts->player_idle_mode && !mpctx->playlist->num_entries)
439
135k
        mpctx->stop_play = PT_STOP;
440
441
141k
    MP_STATS(mpctx, "end init");
442
443
141k
    return 0;
444
141k
}
445
446
int mpv_main(int argc, char *argv[])
447
0
{
448
0
    struct MPContext *mpctx = mp_create();
449
0
    if (!mpctx)
450
0
        return 1;
451
452
0
    mpctx->is_cli = true;
453
454
0
    char **options = argv && argv[0] ? argv + 1 : NULL; // skips program name
455
0
    int r = mp_initialize(mpctx, options);
456
0
    if (r == 0)
457
0
        mp_play_files(mpctx);
458
459
0
    int rc = 0;
460
0
    const char *reason = NULL;
461
0
    if (r < 0) {
462
0
        reason = "Fatal error";
463
0
        rc = 1;
464
0
    } else if (r > 0) {
465
        // nothing
466
0
    } else if (mpctx->stop_play == PT_QUIT) {
467
0
        reason = "Quit";
468
0
    } else if (mpctx->files_played) {
469
0
        if (mpctx->files_errored || mpctx->files_broken) {
470
0
            reason = "Some errors happened";
471
0
            rc = 3;
472
0
        } else {
473
0
            reason = "End of file";
474
0
        }
475
0
    } else if (mpctx->files_broken && !mpctx->files_errored) {
476
0
        reason = "Errors when loading file";
477
0
        rc = 2;
478
0
    } else if (mpctx->files_errored) {
479
0
        reason = "Interrupted by error";
480
0
        rc = 2;
481
0
    } else {
482
0
        reason = "No files played";
483
0
    }
484
485
0
    if (reason)
486
0
        MP_INFO(mpctx, "Exiting... (%s)\n", reason);
487
0
    if (mpctx->has_quit_custom_rc)
488
0
        rc = mpctx->quit_custom_rc;
489
490
0
    mp_destroy(mpctx);
491
0
    return rc;
492
0
}