mpv/parser-mpcmd.c

351 lines
13 KiB
C
Raw Normal View History

/*
* 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 "playtree.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 inline void add_entry(play_tree_t **last_parentp,
play_tree_t **last_entryp, play_tree_t *entry)
{
if (*last_entryp == NULL)
play_tree_set_child(*last_parentp, entry);
else
play_tree_append_entry(*last_entryp, entry);
*last_entryp = entry;
}
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 = bstr(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 = bstr("no");
return 0;
}
// Parse command line to set up config and playtree
play_tree_t *m_config_parse_mp_command_line(m_config_t *config, int argc,
char **argv)
{
int mode = 0;
bool no_more_opts = false;
bool opt_exit = false; // exit immediately after parsing (help options)
play_tree_t *last_parent, *last_entry = NULL, *root;
struct bstr orig_opt;
assert(config != NULL);
assert(argv != NULL);
assert(argc >= 1);
config->mode = M_COMMAND_LINE;
mode = GLOBAL;
#ifdef CONFIG_MACOSX_FINDER
root = macosx_finder_args(config, argc, argv);
if (root)
return root;
#endif
last_parent = root = play_tree_new();
for (int i = 1; i < argc; i++) {
//next:
struct bstr opt = bstr(argv[i]);
orig_opt = opt;
/* check for -- (no more options id.) except --help! */
if (!bstrcmp0(opt, "--")) {
no_more_opts = true;
continue;
}
if (!bstrcmp0(opt, "{")) {
play_tree_t *entry = play_tree_new();
mode = LOCAL;
if (last_parent->flags & PLAY_TREE_RND)
entry->flags |= PLAY_TREE_RND;
if (last_entry == NULL)
play_tree_set_child(last_parent, entry);
else {
play_tree_append_entry(last_entry, entry);
last_entry = NULL;
}
last_parent = entry;
continue;
}
if (!bstrcmp0(opt, "}")) {
if (!last_parent || !last_parent->parent) {
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too much }-\n");
goto err_out;
}
last_entry = last_parent;
last_parent = last_entry->parent;
continue;
}
struct bstr param = bstr(i+1 < argc ? argv[i+1] : NULL);
bool old_syntax;
if (!no_more_opts && split_opt(&opt, &param, &old_syntax)) {
// Handle some special arguments outside option parser.
// --loop when it applies to a group of files (per-file is option)
if (bstrcasecmp0(opt, "loop") == 0 &&
(!last_entry || last_entry->child)) {
struct bstr rest;
int l = bstrtoll(param, &rest, 0);
if (!param.len || rest.len) {
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"The loop option must be an integer: \"%.*s\"\n",
BSTR_P(param));
goto print_err;
} else {
play_tree_t *pt = last_entry ? last_entry : last_parent;
l = l <= 0 ? -1 : l;
pt->loop = l;
i += old_syntax;
}
} else if (bstrcasecmp0(opt, "shuffle") == 0) {
if (last_entry && last_entry->child)
last_entry->flags |= PLAY_TREE_RND;
else
last_parent->flags |= PLAY_TREE_RND;
} else if (bstrcasecmp0(opt, "noshuffle") == 0 ||
bstrcasecmp0(opt, "no-shuffle") == 0) {
if (last_entry && last_entry->child)
last_entry->flags &= ~PLAY_TREE_RND;
else
last_parent->flags &= ~PLAY_TREE_RND;
} else if (bstrcasecmp0(opt, "playlist") == 0) {
if (param.len <= 0)
goto print_err;
struct play_tree *entry = parse_playlist_file(config, param);
if (!entry)
goto print_err;
add_entry(&last_parent, &last_entry, entry);
if ((last_parent->flags & PLAY_TREE_RND) && entry->child)
entry->flags |= PLAY_TREE_RND;
mode = LOCAL;
i += old_syntax;
} else {
// "normal" options
const struct m_option *mp_opt;
int ok = map_to_option(config, old_syntax, &mp_opt, &opt,
&param);
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;
}
int r;
if (mode == GLOBAL || (mp_opt->flags & M_OPT_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) {
play_tree_t *pt = last_entry ? last_entry : last_parent;
if (r == 0)
param = bstr(NULL); // for old_syntax case
play_tree_set_param(pt, opt, param);
}
}
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;
play_tree_t *entry = play_tree_new();
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++) {
if (j != start_title)
entry = play_tree_new();
char entbuf[15];
snprintf(entbuf, sizeof(entbuf),
is_dvdnav ? "dvdnav://%d" : "dvd://%d", j);
play_tree_add_file(entry, entbuf);
add_entry(&last_parent, &last_entry, entry);
last_entry = entry;
}
} else
mp_tmsg(MSGT_CFGPARSER, MSGL_ERR,
"Invalid play entry %s\n", argv[i]);
} else // dvd:// or dvd://x entry
play_tree_add_file(entry, argv[i]);
} else
play_tree_add_file(entry, argv[i]);
// Lock stdin if it will be used as input
if (strcasecmp(argv[i], "-") == 0)
m_config_set_option0(config, "consolecontrols", "no", false);
add_entry(&last_parent, &last_entry, entry);
mode = LOCAL; // We start entry specific options
}
}
if (opt_exit)
goto err_out;
if (last_parent != root)
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Missing }- ?\n");
return root;
print_err:
mp_tmsg(MSGT_CFGPARSER, MSGL_FATAL,
"Error parsing option on the command line: %.*s\n",
BSTR_P(orig_opt));
err_out:
play_tree_free(root, 1);
return NULL;
}
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 = bstr(argv[i]);
// No more options after --
if (!bstrcmp0(opt, "--"))
break;
struct bstr param = bstr(i+1 < argc ? argv[i+1] : NULL);
bool old_syntax;
if (!split_opt(&opt, &param, &old_syntax))
continue; // Ignore non-option arguments
// Ignore invalid options
if (map_to_option(config, old_syntax, NULL, &opt, &param) < 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;
}