Something went wrong. Try again.
🎥 Command line media player
Something went wrong. Try again.
C
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see <http://www.gnu.org/licenses/>. */
#include <stdio.h>#include <stdlib.h>#include <stdbool.h>#include <math.h>#include <assert.h>#include <string.h>#include <pthread.h>#include <locale.h>
#include "config.h"#include "mpv_talloc.h"
#include "misc/dispatch.h"#include "misc/thread_pool.h"#include "osdep/io.h"#include "osdep/terminal.h"#include "osdep/timer.h"#include "osdep/main-fn.h"
#include "common/av_log.h"#include "common/codecs.h"#include "common/encode.h"#include "options/m_config.h"#include "options/m_option.h"#include "options/m_property.h"#include "common/common.h"#include "common/msg.h"#include "common/msg_control.h"#include "common/global.h"#include "filters/f_decoder_wrapper.h"#include "options/parse_configfile.h"#include "options/parse_commandline.h"#include "common/playlist.h"#include "options/options.h"#include "options/path.h"#include "input/input.h"
#include "audio/out/ao.h"#include "demux/demux.h"#include "misc/thread_tools.h"#include "sub/osd.h"#include "test/tests.h"#include "video/out/vo.h"
#include "core.h"#include "client.h"#include "command.h"#include "screenshot.h"
static const char def_config[] =#include "player/builtin_conf.inc";
#if HAVE_COCOA#include "osdep/macosx_events.h"#endif
#ifndef FULLCONFIG#define FULLCONFIG "(missing)\n"#endif
#if !HAVE_STDATOMICpthread_mutex_t mp_atomic_mutex = PTHREAD_MUTEX_INITIALIZER;#endif
enum exit_reason { EXIT_NONE, EXIT_NORMAL, EXIT_ERROR,};
const char mp_help_text[] ="Usage: mpv [options] [url|path/]filename\n""\n""Basic options:\n"" --start=<time> seek to given (percent, seconds, or hh:mm:ss) position\n"" --no-audio do not play sound\n"" --no-video do not play video\n"" --fs fullscreen playback\n"" --sub-file=<file> specify subtitle file to use\n"" --playlist=<file> specify playlist file\n""\n"" --list-options list all mpv options\n"" --h=<string> print options which contain the given string in their name\n""\n";
static pthread_mutex_t terminal_owner_lock = PTHREAD_MUTEX_INITIALIZER;static struct MPContext *terminal_owner;
static bool cas_terminal_owner(struct MPContext *old, struct MPContext *new){ pthread_mutex_lock(&terminal_owner_lock); bool r = terminal_owner == old; if (r) terminal_owner = new; pthread_mutex_unlock(&terminal_owner_lock); return r;}
void mp_update_logging(struct MPContext *mpctx, bool preinit){ bool had_log_file = mp_msg_has_log_file(mpctx->global);
mp_msg_update_msglevels(mpctx->global, mpctx->opts);
bool enable = mpctx->opts->use_terminal; bool enabled = cas_terminal_owner(mpctx, mpctx); if (enable != enabled) { if (enable && cas_terminal_owner(NULL, mpctx)) { terminal_init(); enabled = true; } else if (!enable) { terminal_uninit(); cas_terminal_owner(mpctx, NULL); } }
if (mp_msg_has_log_file(mpctx->global) && !had_log_file) mp_print_version(mpctx->log, false); // for log-file=... in config files
if (enabled && !preinit && mpctx->opts->consolecontrols) terminal_setup_getch(mpctx->input);}
void mp_print_version(struct mp_log *log, int always){ int v = always ? MSGL_INFO : MSGL_V; mp_msg(log, v, "%s %s\n built on %s\n", mpv_version, mpv_copyright, mpv_builddate); print_libav_versions(log, v); mp_msg(log, v, "\n"); // Only in verbose mode. if (!always) { mp_msg(log, MSGL_V, "Configuration: " CONFIGURATION "\n"); mp_msg(log, MSGL_V, "List of enabled features: %s\n", FULLCONFIG); #ifdef NDEBUG mp_msg(log, MSGL_V, "Built with NDEBUG.\n"); #endif }}
void mp_destroy(struct MPContext *mpctx){ mp_shutdown_clients(mpctx);
mp_uninit_ipc(mpctx->ipc_ctx); mpctx->ipc_ctx = NULL;
uninit_audio_out(mpctx); uninit_video_out(mpctx);
// If it's still set here, it's an error. encode_lavc_free(mpctx->encode_lavc_ctx); mpctx->encode_lavc_ctx = NULL;
command_uninit(mpctx);
mp_clients_destroy(mpctx);
osd_free(mpctx->osd);
#if HAVE_COCOA cocoa_set_input_context(NULL);#endif
if (cas_terminal_owner(mpctx, mpctx)) { terminal_uninit(); cas_terminal_owner(mpctx, NULL); }
mp_input_uninit(mpctx->input);
uninit_libav(mpctx->global);
mp_msg_uninit(mpctx->global); assert(!mpctx->num_abort_list); talloc_free(mpctx->abort_list); pthread_mutex_destroy(&mpctx->abort_lock); talloc_free(mpctx->mconfig); // destroy before dispatch talloc_free(mpctx);}
static bool handle_help_options(struct MPContext *mpctx){ struct MPOpts *opts = mpctx->opts; struct mp_log *log = mpctx->log; if (opts->audio_decoders && strcmp(opts->audio_decoders, "help") == 0) { struct mp_decoder_list *list = audio_decoder_list(); mp_print_decoders(log, MSGL_INFO, "Audio decoders:", list); talloc_free(list); return true; } if (opts->audio_spdif && strcmp(opts->audio_spdif, "help") == 0) { MP_INFO(mpctx, "Choices: ac3,dts-hd,dts (and possibly more)\n"); return true; } if (opts->video_decoders && strcmp(opts->video_decoders, "help") == 0) { struct mp_decoder_list *list = video_decoder_list(); mp_print_decoders(log, MSGL_INFO, "Video decoders:", list); talloc_free(list); return true; } if ((opts->demuxer_name && strcmp(opts->demuxer_name, "help") == 0) || (opts->audio_demuxer_name && strcmp(opts->audio_demuxer_name, "help") == 0) || (opts->sub_demuxer_name && strcmp(opts->sub_demuxer_name, "help") == 0)) { demuxer_help(log); MP_INFO(mpctx, "\n"); return true; } if (opts->ao_opts->audio_device && strcmp(opts->ao_opts->audio_device, "help") == 0) { ao_print_devices(mpctx->global, log); return true; } if (opts->property_print_help) { property_print_help(mpctx); return true; } if (encode_lavc_showhelp(log, opts->encode_opts)) return true; return false;}
static int cfg_include(void *ctx, char *filename, int flags){ struct MPContext *mpctx = ctx; char *fname = mp_get_user_path(NULL, mpctx->global, filename); int r = m_config_parse_config_file(mpctx->mconfig, fname, NULL, flags); talloc_free(fname); return r;}
// We mostly care about LC_NUMERIC, and how "." vs. "," is treated,// Other locale stuff might break too, but probably isn't too bad.static bool check_locale(void){ char *name = setlocale(LC_NUMERIC, NULL); return !name || strcmp(name, "C") == 0 || strcmp(name, "C.UTF-8") == 0;}
struct MPContext *mp_create(void){ if (!check_locale()) { // Normally, we never print anything (except if the "terminal" option // is enabled), so this is an exception. fprintf(stderr, "Non-C locale detected. This is not supported.\n" "Call 'setlocale(LC_NUMERIC, \"C\");' in your code.\n"); return NULL; }
char *enable_talloc = getenv("MPV_LEAK_REPORT"); if (!enable_talloc) enable_talloc = HAVE_TA_LEAK_REPORT ? "1" : "0"; if (strcmp(enable_talloc, "1") == 0) talloc_enable_leak_report();
mp_time_init();
struct MPContext *mpctx = talloc(NULL, MPContext); *mpctx = (struct MPContext){ .last_chapter = -2, .term_osd_contents = talloc_strdup(mpctx, ""), .osd_progbar = { .type = -1 }, .playlist = talloc_struct(mpctx, struct playlist, {0}), .dispatch = mp_dispatch_create(mpctx), .playback_abort = mp_cancel_new(mpctx), .thread_pool = mp_thread_pool_create(mpctx, 0, 1, 30), .stop_play = PT_STOP, .play_dir = 1, };
pthread_mutex_init(&mpctx->abort_lock, NULL);
mpctx->global = talloc_zero(mpctx, struct mpv_global);
// Nothing must call mp_msg*() and related before this mp_msg_init(mpctx->global); mpctx->log = mp_log_new(mpctx, mpctx->global->log, "!cplayer"); mpctx->statusline = mp_log_new(mpctx, mpctx->log, "!statusline");
// Create the config context and register the options mpctx->mconfig = m_config_new(mpctx, mpctx->log, &mp_opt_root); mpctx->opts = mpctx->mconfig->optstruct; mpctx->global->config = mpctx->mconfig->shadow; mpctx->mconfig->includefunc = cfg_include; mpctx->mconfig->includefunc_ctx = mpctx; mpctx->mconfig->use_profiles = true; mpctx->mconfig->is_toplevel = true; mpctx->mconfig->global = mpctx->global; m_config_parse(mpctx->mconfig, "", bstr0(def_config), NULL, 0);
mpctx->input = mp_input_init(mpctx->global, mp_wakeup_core_cb, mpctx); screenshot_init(mpctx); command_init(mpctx); init_libav(mpctx->global); mp_clients_init(mpctx); mpctx->osd = osd_create(mpctx->global);
#if HAVE_COCOA cocoa_set_input_context(mpctx->input);#endif
char *verbose_env = getenv("MPV_VERBOSE"); if (verbose_env) mpctx->opts->verbose = atoi(verbose_env);
mp_cancel_trigger(mpctx->playback_abort);
return mpctx;}
// Finish mpctx initialization. This must be done after setting up all options.// Some of the initializations depend on the options, and can't be changed or// undone later.// If options is not NULL, apply them as command line player arguments.// Returns: 0 on success, -1 on error, 1 if exiting normally (e.g. help).int mp_initialize(struct MPContext *mpctx, char **options){ struct MPOpts *opts = mpctx->opts;
assert(!mpctx->initialized);
// Preparse the command line, so we can init the terminal early. if (options) { m_config_preparse_command_line(mpctx->mconfig, mpctx->global, &opts->verbose, options); }
mp_init_paths(mpctx->global, opts); mp_msg_set_early_logging(mpctx->global, true); mp_update_logging(mpctx, true);
if (options) { MP_VERBOSE(mpctx, "Command line options:"); for (int i = 0; options[i]; i++) MP_VERBOSE(mpctx, " '%s'", options[i]); MP_VERBOSE(mpctx, "\n"); }
mp_print_version(mpctx->log, false);
mp_parse_cfgfiles(mpctx);
if (options) { int r = m_config_parse_mp_command_line(mpctx->mconfig, mpctx->playlist, mpctx->global, options); if (r < 0) return r == M_OPT_EXIT ? 1 : -1; }
if (opts->operation_mode == 1) { m_config_set_profile(mpctx->mconfig, "builtin-pseudo-gui", M_SETOPT_NO_OVERWRITE); m_config_set_profile(mpctx->mconfig, "pseudo-gui", 0); }
mp_get_resume_defaults(mpctx);
mp_input_load_config(mpctx->input);
// From this point on, all mpctx members are initialized. mpctx->initialized = true; mpctx->mconfig->option_change_callback = mp_option_change_callback; mpctx->mconfig->option_change_callback_ctx = mpctx; m_config_set_update_dispatch_queue(mpctx->mconfig, mpctx->dispatch); // Run all update handlers. mp_option_change_callback(mpctx, NULL, UPDATE_OPTS_MASK, false);
if (handle_help_options(mpctx)) return 1; // help
if (!print_libav_versions(mp_null_log, 0)) { // This happens only if the runtime FFmpeg version is lower than the // build version, which will not work according to FFmpeg's ABI rules. // This does not happen if runtime FFmpeg is newer, which is compatible. print_libav_versions(mpctx->log, MSGL_FATAL); MP_FATAL(mpctx, "\nmpv was compiled against an incompatible version of " "FFmpeg/Libav than the shared\nlibrary it is linked against. " "This is most likely a broken build and could\nresult in " "misbehavior and crashes.\n\nThis is a broken build.\n"); return -1; }
#if HAVE_TESTS if (opts->test_mode && opts->test_mode[0]) return run_tests(mpctx) ? 1 : -1;#endif
if (!mpctx->playlist->num_entries && !opts->player_idle_mode) { // nothing to play mp_print_version(mpctx->log, true); MP_INFO(mpctx, "%s", mp_help_text); return 1; }
MP_STATS(mpctx, "start init");
#if HAVE_COCOA mpv_handle *ctx = mp_new_client(mpctx->clients, "osx"); cocoa_set_mpv_handle(ctx);#endif
if (opts->encode_opts->file && opts->encode_opts->file[0]) { mpctx->encode_lavc_ctx = encode_lavc_init(mpctx->global); if(!mpctx->encode_lavc_ctx) { MP_INFO(mpctx, "Encoding initialization failed.\n"); return -1; } m_config_set_profile(mpctx->mconfig, "encoding", 0); mp_input_enable_section(mpctx->input, "encode", MP_INPUT_EXCLUSIVE); }
#if !HAVE_LIBASS MP_WARN(mpctx, "Compiled without libass.\n"); MP_WARN(mpctx, "There will be no OSD and no text subtitles.\n");#endif
mp_load_scripts(mpctx);
if (opts->force_vo == 2 && handle_force_window(mpctx, false) < 0) return -1;
MP_STATS(mpctx, "end init");
return 0;}
int mpv_main(int argc, char *argv[]){ struct MPContext *mpctx = mp_create(); if (!mpctx) return 1;
mpctx->is_cli = true;
char **options = argv && argv[0] ? argv + 1 : NULL; // skips program name int r = mp_initialize(mpctx, options); if (r == 0) mp_play_files(mpctx);
int rc = 0; const char *reason = NULL; if (r < 0) { reason = "Fatal error"; rc = 1; } else if (r > 0) { // nothing } else if (mpctx->stop_play == PT_QUIT) { reason = "Quit"; } else if (mpctx->files_played) { if (mpctx->files_errored || mpctx->files_broken) { reason = "Some errors happened"; rc = 3; } else { reason = "End of file"; } } else if (mpctx->files_broken && !mpctx->files_errored) { reason = "Errors when loading file"; rc = 2; } else if (mpctx->files_errored) { reason = "Interrupted by error"; rc = 2; } else { reason = "No files played"; }
if (reason) MP_INFO(mpctx, "\nExiting... (%s)\n", reason); if (mpctx->has_quit_custom_rc) rc = mpctx->quit_custom_rc;
mp_destroy(mpctx); return rc;}