mirror of https://github.com/mpv-player/mpv
341 lines
12 KiB
C
341 lines
12 KiB
C
/*
|
|
* This file is part of MPlayer.
|
|
*
|
|
* MPlayer is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* MPlayer 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with MPlayer; if not, write to the Free Software Foundation, Inc.,
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <stdbool.h>
|
|
|
|
#include "mp_msg.h"
|
|
#include "m_option.h"
|
|
#include "m_config.h"
|
|
#include "playlist.h"
|
|
#include "playlist_parser.h"
|
|
#include "parser-mpcmd.h"
|
|
#include "osdep/macosx_finder_args.h"
|
|
|
|
#define GLOBAL 0
|
|
#define LOCAL 1
|
|
|
|
#define dvd_range(a) (a > 0 && a < 256)
|
|
|
|
|
|
static bool split_opt(struct bstr *opt, struct bstr *param, bool *old_syntax)
|
|
{
|
|
if (!bstr_startswith0(*opt, "-") || opt->len == 1)
|
|
return false;
|
|
if (bstr_startswith0(*opt, "--")) {
|
|
*old_syntax = false;
|
|
*opt = bstr_cut(*opt, 2);
|
|
*param = bstr0(NULL);
|
|
int idx = bstrchr(*opt, '=');
|
|
if (idx > 0) {
|
|
*param = bstr_cut(*opt, idx + 1);
|
|
*opt = bstr_splice(*opt, 0, idx);
|
|
}
|
|
} else {
|
|
*old_syntax = true;
|
|
*opt = bstr_cut(*opt, 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int map_to_option(struct m_config *config, bool old_syntax,
|
|
const struct m_option **mp_opt,
|
|
struct bstr *optname, struct bstr *param)
|
|
{
|
|
if (!mp_opt)
|
|
mp_opt = &(const struct m_option *){0};
|
|
*mp_opt = m_config_get_option(config, *optname);
|
|
if (*mp_opt)
|
|
return 0;
|
|
if (!bstr_startswith0(*optname, "no-"))
|
|
return -1;
|
|
struct bstr s = bstr_cut(*optname, 3);
|
|
*mp_opt = m_config_get_option(config, s);
|
|
if (!*mp_opt || (*mp_opt)->type != &m_option_type_flag)
|
|
return -1;
|
|
if (param->len)
|
|
return -2;
|
|
if (old_syntax)
|
|
return -3;
|
|
*optname = s;
|
|
*param = bstr0("no");
|
|
return 0;
|
|
}
|
|
|
|
bool m_config_parse_mp_command_line(m_config_t *config, struct playlist *files,
|
|
int argc, char **argv)
|
|
{
|
|
int mode = 0;
|
|
bool no_more_opts = false;
|
|
bool opt_exit = false; // exit immediately after parsing (help options)
|
|
struct playlist_entry *local_start = NULL;
|
|
struct bstr orig_opt;
|
|
bool shuffle = false;
|
|
|
|
int local_params_count = 0;
|
|
struct playlist_param *local_params = 0;
|
|
|
|
assert(config != NULL);
|
|
assert(argv != NULL);
|
|
assert(argc >= 1);
|
|
|
|
config->mode = M_COMMAND_LINE;
|
|
mode = GLOBAL;
|
|
#ifdef CONFIG_MACOSX_FINDER
|
|
if (macosx_finder_args(config, files, argc, argv))
|
|
return true;
|
|
#endif
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
//next:
|
|
struct bstr opt = bstr0(argv[i]);
|
|
orig_opt = opt;
|
|
/* check for -- (no more options id.) except --help! */
|
|
if (!bstrcmp0(opt, "--")) {
|
|
no_more_opts = true;
|
|
continue;
|
|
}
|
|
if (!bstrcmp0(opt, "--{")) {
|
|
if (mode != GLOBAL) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "'--{' can not be nested\n");
|
|
goto err_out;
|
|
}
|
|
mode = LOCAL;
|
|
assert(!local_start);
|
|
local_start = files->last;
|
|
continue;
|
|
}
|
|
|
|
if (!bstrcmp0(opt, "--}")) {
|
|
if (mode != LOCAL) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too many closing '--}'\n");
|
|
goto err_out;
|
|
}
|
|
if (local_params_count) {
|
|
// The files added between '{' and '}' are the entries from the
|
|
// entry _after_ local_start, until the end of the list. If
|
|
// local_start is NULL, the list was empty on '{', and we want
|
|
// all files in the list.
|
|
struct playlist_entry *cur
|
|
= local_start ? local_start->next : files->first;
|
|
if (!cur)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN, "ignored options\n");
|
|
while (cur) {
|
|
playlist_entry_add_params(cur, local_params,
|
|
local_params_count);
|
|
cur = cur->next;
|
|
}
|
|
}
|
|
local_params_count = 0;
|
|
mode = GLOBAL;
|
|
local_start = NULL;
|
|
continue;
|
|
}
|
|
|
|
struct bstr param = bstr0(i+1 < argc ? argv[i+1] : NULL);
|
|
bool old_syntax;
|
|
if (!no_more_opts && split_opt(&opt, ¶m, &old_syntax)) {
|
|
const struct m_option *mp_opt;
|
|
int ok = map_to_option(config, old_syntax, &mp_opt, &opt, ¶m);
|
|
if (ok < 0) {
|
|
if (ok == -3)
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option --%.*s can't be used with single-dash "
|
|
"syntax\n", BSTR_P(opt));
|
|
else if (ok == -2)
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"A --no-* option can't take parameters: "
|
|
"--%.*s=%.*s\n", BSTR_P(opt), BSTR_P(param));
|
|
else
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Unknown option on the command line: --%.*s\n",
|
|
BSTR_P(opt));
|
|
goto print_err;
|
|
}
|
|
if (mode == LOCAL && (mp_opt->flags & M_OPT_GLOBAL)) {
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option --%.*s is global and can't be set per-file\n",
|
|
BSTR_P(opt));
|
|
goto print_err;
|
|
}
|
|
// Handle some special arguments outside option parser.
|
|
// --loop when it applies to a group of files (per-file is option)
|
|
if (bstrcasecmp0(opt, "shuffle") == 0) {
|
|
shuffle = true;
|
|
} else if (bstrcasecmp0(opt, "no-shuffle") == 0) {
|
|
shuffle = false;
|
|
} else if (bstrcasecmp0(opt, "playlist") == 0) {
|
|
if (param.len <= 0)
|
|
goto print_err;
|
|
// append the playlist to the local args
|
|
char *param0 = bstrdup0(NULL, param);
|
|
struct playlist *pl = playlist_parse_file(param0);
|
|
talloc_free(param0);
|
|
if (!pl)
|
|
goto print_err;
|
|
playlist_transfer_entries(files, pl);
|
|
talloc_free(pl);
|
|
i += old_syntax;
|
|
} else {
|
|
// "normal" options
|
|
int r;
|
|
if (mode == GLOBAL) {
|
|
r = m_config_set_option(config, opt, param, old_syntax);
|
|
} else {
|
|
r = m_config_check_option(config, opt, param, old_syntax);
|
|
if (r >= 0) {
|
|
if (r == 0)
|
|
param = bstr0(NULL); // for old_syntax case
|
|
struct playlist_param p = {opt, param};
|
|
MP_TARRAY_APPEND(NULL, local_params,
|
|
local_params_count, p);
|
|
}
|
|
}
|
|
if (r <= M_OPT_EXIT) {
|
|
opt_exit = true;
|
|
r = M_OPT_EXIT - r;
|
|
} else if (r < 0) {
|
|
char *msg = m_option_strerror(r);
|
|
if (!msg)
|
|
goto print_err;
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
|
|
"Error parsing commandline option \"%.*s\": %s\n",
|
|
BSTR_P(orig_opt), msg);
|
|
goto err_out;
|
|
}
|
|
if (old_syntax)
|
|
i += r;
|
|
}
|
|
} else { /* filename */
|
|
int is_dvdnav = strstr(argv[i], "dvdnav://") != NULL;
|
|
mp_msg(MSGT_CFGPARSER, MSGL_DBG2, "Adding file %s\n", argv[i]);
|
|
// expand DVD filename entries like dvd://1-3 into component titles
|
|
if (strstr(argv[i], "dvd://") != NULL || is_dvdnav) {
|
|
int offset = is_dvdnav ? 9 : 6;
|
|
char *splitpos = strstr(argv[i] + offset, "-");
|
|
if (splitpos != NULL) {
|
|
int start_title = strtol(argv[i] + offset, NULL, 10);
|
|
int end_title;
|
|
//entries like dvd://-2 imply start at title 1
|
|
if (start_title < 0) {
|
|
end_title = abs(start_title);
|
|
start_title = 1;
|
|
} else
|
|
end_title = strtol(splitpos + 1, NULL, 10);
|
|
|
|
if (dvd_range(start_title) && dvd_range(end_title)
|
|
&& (start_title < end_title)) {
|
|
for (int j = start_title; j <= end_title; j++) {
|
|
char entbuf[15];
|
|
snprintf(entbuf, sizeof(entbuf),
|
|
is_dvdnav ? "dvdnav://%d" : "dvd://%d", j);
|
|
playlist_add_file(files, entbuf);
|
|
}
|
|
} else
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Invalid play entry %s\n", argv[i]);
|
|
|
|
} else // dvd:// or dvd://x entry
|
|
playlist_add_file(files, argv[i]);
|
|
} else
|
|
playlist_add_file(files, argv[i]);
|
|
|
|
// Lock stdin if it will be used as input
|
|
if (strcasecmp(argv[i], "-") == 0)
|
|
m_config_set_option0(config, "consolecontrols", "no", false);
|
|
}
|
|
}
|
|
|
|
if (opt_exit)
|
|
goto err_out;
|
|
|
|
if (mode != GLOBAL) {
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Missing closing --} on command line.\n");
|
|
goto err_out;
|
|
}
|
|
|
|
if (shuffle)
|
|
playlist_shuffle(files);
|
|
|
|
talloc_free(local_params);
|
|
return true;
|
|
|
|
print_err:
|
|
mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
|
|
"Error parsing option on the command line: %.*s\n",
|
|
BSTR_P(orig_opt));
|
|
err_out:
|
|
talloc_free(local_params);
|
|
return false;
|
|
}
|
|
|
|
extern int mp_msg_levels[];
|
|
|
|
/* Parse some command line options early before main parsing.
|
|
* --noconfig prevents reading configuration files (otherwise done before
|
|
* command line parsing), and --really-quiet suppresses messages printed
|
|
* during normal options parsing.
|
|
*/
|
|
int m_config_preparse_command_line(m_config_t *config, int argc, char **argv,
|
|
int *verbose)
|
|
{
|
|
int ret = 0;
|
|
|
|
// Hack to shut up parser error messages
|
|
int msg_lvl_backup = mp_msg_levels[MSGT_CFGPARSER];
|
|
mp_msg_levels[MSGT_CFGPARSER] = -11;
|
|
|
|
config->mode = M_COMMAND_LINE_PRE_PARSE;
|
|
|
|
for (int i = 1 ; i < argc ; i++) {
|
|
struct bstr opt = bstr0(argv[i]);
|
|
// No more options after --
|
|
if (!bstrcmp0(opt, "--"))
|
|
break;
|
|
struct bstr param = bstr0(i+1 < argc ? argv[i+1] : NULL);
|
|
bool old_syntax;
|
|
if (!split_opt(&opt, ¶m, &old_syntax))
|
|
continue; // Ignore non-option arguments
|
|
// Ignore invalid options
|
|
if (map_to_option(config, old_syntax, NULL, &opt, ¶m) < 0)
|
|
continue;
|
|
// "-v" is handled here
|
|
if (!bstrcmp0(opt, "v")) {
|
|
(*verbose)++;
|
|
continue;
|
|
}
|
|
// Set, non-pre-parse options will be ignored
|
|
int r = m_config_set_option(config, opt, param, old_syntax);
|
|
if (r < 0)
|
|
ret = r;
|
|
else if (old_syntax)
|
|
i += r;
|
|
}
|
|
|
|
mp_msg_levels[MSGT_CFGPARSER] = msg_lvl_backup;
|
|
|
|
return ret;
|
|
}
|