mirror of
https://github.com/mpv-player/mpv
synced 2024-12-25 00:02:13 +00:00
267a889cc2
There were two option syntax variations: "old": -opt value "new": --opt=value "-opt=value" was invalid, and "--opt value" meant "--opt=" followed by a separate option "value" (i.e. interpreted as filename). There isn't really any reason to do this. The "old" syntax used to be ambiguous (you had to call the option parser to know whether the following argument is an option value or a new option), but that has been removed. Further, using "=" in the option string is always unambiguous. Since the distinction between the two option variants is confusing, just remove the difference and allow "--opt value" and "-opt=value". To make this easier, do some other cleanups as well (e.g. avoid having to do a manual lookup of the option just to check for M_OPT_PRE_PARSE, which somehow ended up with finally getting rid of the m_config.mode member). Error reporting is still a mess, and we opt for reporting too many rather than too few errors to the user. There shouldn't be many user-visible changes. The --framedrop and --term-osd options now always require parameters. The --mute option is intentionally made ambiguous: it works like a flag option, but a value can be passed to it explicitly ("--mute=auto"). If the interpretation of the option is ambiguous (like "--mute auto"), the second string is interpreted as separate option or filename. (Normal flag options are actually ambiguous in this way too.)
2401 lines
67 KiB
C
2401 lines
67 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.
|
|
*/
|
|
|
|
/// \file
|
|
/// \ingroup Options
|
|
|
|
#include "config.h"
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <limits.h>
|
|
#include <inttypes.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
|
|
#include <libavutil/common.h>
|
|
#include <libavutil/avstring.h>
|
|
|
|
#include "talloc.h"
|
|
#include "core/m_option.h"
|
|
#include "core/mp_msg.h"
|
|
#include "stream/url.h"
|
|
|
|
char *m_option_strerror(int code)
|
|
{
|
|
switch (code) {
|
|
case M_OPT_UNKNOWN:
|
|
return mp_gtext("option not found");
|
|
case M_OPT_MISSING_PARAM:
|
|
return mp_gtext("option requires parameter");
|
|
case M_OPT_INVALID:
|
|
return mp_gtext("option parameter could not be parsed");
|
|
case M_OPT_OUT_OF_RANGE:
|
|
return mp_gtext("parameter is outside values allowed for option");
|
|
case M_OPT_DISALLOW_PARAM:
|
|
return mp_gtext("option doesn't take a parameter");
|
|
case M_OPT_PARSER_ERR:
|
|
default:
|
|
return mp_gtext("parser error");
|
|
}
|
|
}
|
|
|
|
static const struct m_option *m_option_list_findb(const struct m_option *list,
|
|
struct bstr name)
|
|
{
|
|
for (int i = 0; list[i].name; i++) {
|
|
struct bstr lname = bstr0(list[i].name);
|
|
if ((list[i].type->flags & M_OPT_TYPE_ALLOW_WILDCARD)
|
|
&& bstr_endswith0(lname, "*")) {
|
|
lname.len--;
|
|
if (bstrcasecmp(bstr_splice(name, 0, lname.len), lname) == 0)
|
|
return &list[i];
|
|
} else if (bstrcasecmp(lname, name) == 0)
|
|
return &list[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
const m_option_t *m_option_list_find(const m_option_t *list, const char *name)
|
|
{
|
|
return m_option_list_findb(list, bstr0(name));
|
|
}
|
|
|
|
// Default function that just does a memcpy
|
|
|
|
static void copy_opt(const m_option_t *opt, void *dst, const void *src)
|
|
{
|
|
if (dst && src)
|
|
memcpy(dst, src, opt->type->size);
|
|
}
|
|
|
|
// Flag
|
|
|
|
#define VAL(x) (*(int *)(x))
|
|
|
|
static int clamp_flag(const m_option_t *opt, void *val)
|
|
{
|
|
if (VAL(val) == opt->min || VAL(val) == opt->max)
|
|
return 0;
|
|
VAL(val) = opt->min;
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
static int parse_flag(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len) {
|
|
if (!bstrcasecmp0(param, "yes")) {
|
|
if (dst)
|
|
VAL(dst) = opt->max;
|
|
return 1;
|
|
}
|
|
if (!bstrcasecmp0(param, "no")) {
|
|
if (dst)
|
|
VAL(dst) = opt->min;
|
|
return 1;
|
|
}
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Invalid parameter for %.*s flag: %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
} else {
|
|
if (dst)
|
|
VAL(dst) = opt->max;
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static char *print_flag(const m_option_t *opt, const void *val)
|
|
{
|
|
if (VAL(val) == opt->min)
|
|
return talloc_strdup(NULL, "no");
|
|
else
|
|
return talloc_strdup(NULL, "yes");
|
|
}
|
|
|
|
static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
if (fabs(add) < 0.5)
|
|
return;
|
|
bool state = VAL(val) != opt->min;
|
|
state = wrap ? !state : add > 0;
|
|
VAL(val) = state ? opt->max : opt->min;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_flag = {
|
|
// need yes or no in config files
|
|
.name = "Flag",
|
|
.size = sizeof(int),
|
|
.flags = M_OPT_TYPE_OPTIONAL_PARAM,
|
|
.parse = parse_flag,
|
|
.print = print_flag,
|
|
.copy = copy_opt,
|
|
.add = add_flag,
|
|
.clamp = clamp_flag,
|
|
};
|
|
|
|
// Single-value, write-only flag
|
|
|
|
static int parse_store(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0 || bstrcasecmp0(param, "yes") == 0) {
|
|
if (dst)
|
|
VAL(dst) = opt->max;
|
|
return 0;
|
|
} else {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Invalid parameter for %.*s flag: %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_DISALLOW_PARAM;
|
|
}
|
|
}
|
|
|
|
const m_option_type_t m_option_type_store = {
|
|
// can only be activated
|
|
.name = "Flag",
|
|
.size = sizeof(int),
|
|
.flags = M_OPT_TYPE_OPTIONAL_PARAM,
|
|
.parse = parse_store,
|
|
};
|
|
|
|
// Integer
|
|
|
|
#undef VAL
|
|
|
|
static int clamp_longlong(const m_option_t *opt, void *val)
|
|
{
|
|
long long v = *(long long *)val;
|
|
int r = 0;
|
|
if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
|
|
v = opt->max;
|
|
r = M_OPT_OUT_OF_RANGE;
|
|
}
|
|
if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
|
|
v = opt->min;
|
|
r = M_OPT_OUT_OF_RANGE;
|
|
}
|
|
*(long long *)val = v;
|
|
return r;
|
|
}
|
|
|
|
static int parse_longlong(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
struct bstr rest;
|
|
long long tmp_int = bstrtoll(param, &rest, 10);
|
|
if (rest.len)
|
|
tmp_int = bstrtoll(param, &rest, 0);
|
|
if (rest.len) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be an integer: %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if ((opt->flags & M_OPT_MIN) && (tmp_int < opt->min)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be >= %d: %.*s\n",
|
|
BSTR_P(name), (int) opt->min, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if ((opt->flags & M_OPT_MAX) && (tmp_int > opt->max)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be <= %d: %.*s\n",
|
|
BSTR_P(name), (int) opt->max, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (dst)
|
|
*(long long *)dst = tmp_int;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int clamp_int(const m_option_t *opt, void *val)
|
|
{
|
|
long long tmp = *(int *)val;
|
|
int r = clamp_longlong(opt, &tmp);
|
|
*(int *)val = tmp;
|
|
return r;
|
|
}
|
|
|
|
static int clamp_int64(const m_option_t *opt, void *val)
|
|
{
|
|
long long tmp = *(int64_t *)val;
|
|
int r = clamp_longlong(opt, &tmp);
|
|
*(int64_t *)val = tmp;
|
|
return r;
|
|
}
|
|
|
|
static int parse_int(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
long long tmp;
|
|
int r = parse_longlong(opt, name, param, &tmp);
|
|
if (r >= 0 && dst)
|
|
*(int *)dst = tmp;
|
|
return r;
|
|
}
|
|
|
|
static int parse_int64(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
long long tmp;
|
|
int r = parse_longlong(opt, name, param, &tmp);
|
|
if (r >= 0 && dst)
|
|
*(int64_t *)dst = tmp;
|
|
return r;
|
|
}
|
|
|
|
static char *print_int(const m_option_t *opt, const void *val)
|
|
{
|
|
if (opt->type->size == sizeof(int64_t))
|
|
return talloc_asprintf(NULL, "%"PRId64, *(const int64_t *)val);
|
|
return talloc_asprintf(NULL, "%d", *(const int *)val);
|
|
}
|
|
|
|
static void add_int64(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
int64_t v = *(int64_t *)val;
|
|
|
|
v = v + add;
|
|
|
|
bool is64 = opt->type->size == sizeof(int64_t);
|
|
int64_t nmin = is64 ? INT64_MIN : INT_MIN;
|
|
int64_t nmax = is64 ? INT64_MAX : INT_MAX;
|
|
|
|
int64_t min = (opt->flags & M_OPT_MIN) ? opt->min : nmin;
|
|
int64_t max = (opt->flags & M_OPT_MAX) ? opt->max : nmax;
|
|
|
|
if (v < min)
|
|
v = wrap ? max : min;
|
|
if (v > max)
|
|
v = wrap ? min : max;
|
|
|
|
*(int64_t *)val = v;
|
|
}
|
|
|
|
static void add_int(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
int64_t tmp = *(int *)val;
|
|
add_int64(opt, &tmp, add, wrap);
|
|
*(int *)val = tmp;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_int = {
|
|
.name = "Integer",
|
|
.size = sizeof(int),
|
|
.parse = parse_int,
|
|
.print = print_int,
|
|
.copy = copy_opt,
|
|
.add = add_int,
|
|
.clamp = clamp_int,
|
|
};
|
|
|
|
const m_option_type_t m_option_type_int64 = {
|
|
.name = "Integer64",
|
|
.size = sizeof(int64_t),
|
|
.parse = parse_int64,
|
|
.print = print_int,
|
|
.copy = copy_opt,
|
|
.add = add_int64,
|
|
.clamp = clamp_int64,
|
|
};
|
|
|
|
static int parse_intpair(const struct m_option *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
struct bstr s = param;
|
|
int end = -1;
|
|
int start = bstrtoll(s, &s, 10);
|
|
if (s.len == param.len)
|
|
goto bad;
|
|
if (s.len > 0) {
|
|
if (!bstr_startswith0(s, "-"))
|
|
goto bad;
|
|
s = bstr_cut(s, 1);
|
|
}
|
|
if (s.len > 0)
|
|
end = bstrtoll(s, &s, 10);
|
|
if (s.len > 0)
|
|
goto bad;
|
|
|
|
if (dst) {
|
|
int *p = dst;
|
|
p[0] = start;
|
|
p[1] = end;
|
|
}
|
|
|
|
return 1;
|
|
|
|
bad:
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Invalid integer range "
|
|
"specification for option %.*s: %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
const struct m_option_type m_option_type_intpair = {
|
|
.name = "Int[-Int]",
|
|
.size = sizeof(int[2]),
|
|
.parse = parse_intpair,
|
|
.copy = copy_opt,
|
|
};
|
|
|
|
static int clamp_choice(const m_option_t *opt, void *val)
|
|
{
|
|
int v = *(int *)val;
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
|
if (v >= opt->min && v <= opt->max)
|
|
return 0;
|
|
}
|
|
;
|
|
for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
|
|
if (alt->value == v)
|
|
return 0;
|
|
}
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
static int parse_choice(const struct m_option *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
struct m_opt_choice_alternatives *alt = opt->priv;
|
|
for ( ; alt->name; alt++)
|
|
if (!bstrcasecmp0(param, alt->name))
|
|
break;
|
|
if (!alt->name) {
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
|
long long val;
|
|
if (parse_longlong(opt, name, param, &val) == 1) {
|
|
if (dst)
|
|
*(int *)dst = val;
|
|
return 1;
|
|
}
|
|
}
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Invalid value for option %.*s: %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Valid values are:");
|
|
for (alt = opt->priv; alt->name; alt++)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %s", alt->name);
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX))
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %g-%g", opt->min, opt->max);
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "\n");
|
|
return M_OPT_INVALID;
|
|
}
|
|
if (dst)
|
|
*(int *)dst = alt->value;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static char *print_choice(const m_option_t *opt, const void *val)
|
|
{
|
|
int v = *(int *)val;
|
|
struct m_opt_choice_alternatives *alt;
|
|
for (alt = opt->priv; alt->name; alt++)
|
|
if (alt->value == v)
|
|
return talloc_strdup(NULL, alt->name);
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
|
if (v >= opt->min && v <= opt->max)
|
|
return talloc_asprintf(NULL, "%d", v);
|
|
}
|
|
abort();
|
|
}
|
|
|
|
static void choice_get_min_max(const struct m_option *opt, int *min, int *max)
|
|
{
|
|
assert(opt->type == &m_option_type_choice);
|
|
*min = INT_MAX;
|
|
*max = INT_MIN;
|
|
for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++) {
|
|
*min = FFMIN(*min, alt->value);
|
|
*max = FFMAX(*max, alt->value);
|
|
}
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
|
*min = FFMIN(*min, opt->min);
|
|
*max = FFMAX(*max, opt->max);
|
|
}
|
|
}
|
|
|
|
static void check_choice(int dir, int val, bool *found, int *best, int choice)
|
|
{
|
|
if ((dir == -1 && (!(*found) || choice > (*best)) && choice < val) ||
|
|
(dir == +1 && (!(*found) || choice < (*best)) && choice > val))
|
|
{
|
|
*found = true;
|
|
*best = choice;
|
|
}
|
|
}
|
|
|
|
static void add_choice(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
assert(opt->type == &m_option_type_choice);
|
|
int dir = add > 0 ? +1 : -1;
|
|
bool found = false;
|
|
int ival = *(int *)val;
|
|
int best = 0; // init. value unused
|
|
|
|
if (fabs(add) < 0.5)
|
|
return;
|
|
|
|
if ((opt->flags & M_OPT_MIN) && (opt->flags & M_OPT_MAX)) {
|
|
int newval = ival + add;
|
|
if (ival >= opt->min && ival <= opt->max &&
|
|
newval >= opt->min && newval <= opt->max)
|
|
{
|
|
found = true;
|
|
best = newval;
|
|
} else {
|
|
check_choice(dir, ival, &found, &best, opt->min);
|
|
check_choice(dir, ival, &found, &best, opt->max);
|
|
}
|
|
}
|
|
|
|
for (struct m_opt_choice_alternatives *alt = opt->priv; alt->name; alt++)
|
|
check_choice(dir, ival, &found, &best, alt->value);
|
|
|
|
if (!found) {
|
|
int min, max;
|
|
choice_get_min_max(opt, &min, &max);
|
|
best = (dir == -1) ^ wrap ? min : max;
|
|
}
|
|
|
|
*(int *)val = best;
|
|
}
|
|
|
|
const struct m_option_type m_option_type_choice = {
|
|
.name = "String", // same as arbitrary strings in option list for now
|
|
.size = sizeof(int),
|
|
.parse = parse_choice,
|
|
.print = print_choice,
|
|
.copy = copy_opt,
|
|
.add = add_choice,
|
|
.clamp = clamp_choice,
|
|
};
|
|
|
|
// Float
|
|
|
|
#undef VAL
|
|
#define VAL(x) (*(double *)(x))
|
|
|
|
static int clamp_double(const m_option_t *opt, void *val)
|
|
{
|
|
double v = VAL(val);
|
|
int r = 0;
|
|
if ((opt->flags & M_OPT_MAX) && (v > opt->max)) {
|
|
v = opt->max;
|
|
r = M_OPT_OUT_OF_RANGE;
|
|
}
|
|
if ((opt->flags & M_OPT_MIN) && (v < opt->min)) {
|
|
v = opt->min;
|
|
r = M_OPT_OUT_OF_RANGE;
|
|
}
|
|
VAL(val) = v;
|
|
return r;
|
|
}
|
|
|
|
static int parse_double(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
struct bstr rest;
|
|
double tmp_float = bstrtod(param, &rest);
|
|
|
|
switch (rest.len ? rest.start[0] : 0) {
|
|
case ':':
|
|
case '/':
|
|
tmp_float /= bstrtod(bstr_cut(rest, 1), &rest);
|
|
break;
|
|
case '.':
|
|
case ',':
|
|
/* we also handle floats specified with
|
|
* non-locale decimal point ::atmos
|
|
*/
|
|
rest = bstr_cut(rest, 1);
|
|
if (tmp_float < 0)
|
|
tmp_float -= 1.0 / pow(10, rest.len) * bstrtod(rest, &rest);
|
|
else
|
|
tmp_float += 1.0 / pow(10, rest.len) * bstrtod(rest, &rest);
|
|
break;
|
|
}
|
|
|
|
if (rest.len) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be a floating point number or a "
|
|
"ratio (numerator[:/]denominator): %.*s\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (opt->flags & M_OPT_MIN)
|
|
if (tmp_float < opt->min) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be >= %f: %.*s\n",
|
|
BSTR_P(name), opt->min, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (opt->flags & M_OPT_MAX)
|
|
if (tmp_float > opt->max) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"The %.*s option must be <= %f: %.*s\n",
|
|
BSTR_P(name), opt->max, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (dst)
|
|
VAL(dst) = tmp_float;
|
|
return 1;
|
|
}
|
|
|
|
static char *print_double(const m_option_t *opt, const void *val)
|
|
{
|
|
return talloc_asprintf(NULL, "%f", VAL(val));
|
|
}
|
|
|
|
static char *print_double_f2(const m_option_t *opt, const void *val)
|
|
{
|
|
return talloc_asprintf(NULL, "%.2f", VAL(val));
|
|
}
|
|
|
|
static void add_double(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
double v = VAL(val);
|
|
|
|
v = v + add;
|
|
|
|
double min = (opt->flags & M_OPT_MIN) ? opt->min : -INFINITY;
|
|
double max = (opt->flags & M_OPT_MAX) ? opt->max : +INFINITY;
|
|
|
|
if (v < min)
|
|
v = wrap ? max : min;
|
|
if (v > max)
|
|
v = wrap ? min : max;
|
|
|
|
VAL(val) = v;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_double = {
|
|
// double precision float or ratio (numerator[:/]denominator)
|
|
.name = "Double",
|
|
.size = sizeof(double),
|
|
.parse = parse_double,
|
|
.print = print_double,
|
|
.pretty_print = print_double_f2,
|
|
.copy = copy_opt,
|
|
.clamp = clamp_double,
|
|
};
|
|
|
|
#undef VAL
|
|
#define VAL(x) (*(float *)(x))
|
|
|
|
static int clamp_float(const m_option_t *opt, void *val)
|
|
{
|
|
double tmp = VAL(val);
|
|
int r = clamp_double(opt, &tmp);
|
|
VAL(val) = tmp;
|
|
return r;
|
|
}
|
|
|
|
static int parse_float(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
double tmp;
|
|
int r = parse_double(opt, name, param, &tmp);
|
|
if (r == 1 && dst)
|
|
VAL(dst) = tmp;
|
|
return r;
|
|
}
|
|
|
|
static char *print_float(const m_option_t *opt, const void *val)
|
|
{
|
|
return talloc_asprintf(NULL, "%f", VAL(val));
|
|
}
|
|
|
|
static char *print_float_f2(const m_option_t *opt, const void *val)
|
|
{
|
|
return talloc_asprintf(NULL, "%.2f", VAL(val));
|
|
}
|
|
|
|
static void add_float(const m_option_t *opt, void *val, double add, bool wrap)
|
|
{
|
|
double tmp = VAL(val);
|
|
add_double(opt, &tmp, add, wrap);
|
|
VAL(val) = tmp;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_float = {
|
|
// floating point number or ratio (numerator[:/]denominator)
|
|
.name = "Float",
|
|
.size = sizeof(float),
|
|
.parse = parse_float,
|
|
.print = print_float,
|
|
.pretty_print = print_float_f2,
|
|
.copy = copy_opt,
|
|
.add = add_float,
|
|
.clamp = clamp_float,
|
|
};
|
|
|
|
///////////// String
|
|
|
|
#undef VAL
|
|
#define VAL(x) (*(char **)(x))
|
|
|
|
static int clamp_str(const m_option_t *opt, void *val)
|
|
{
|
|
char *v = VAL(val);
|
|
int len = v ? strlen(v) : 0;
|
|
if ((opt->flags & M_OPT_MIN) && (len < opt->min))
|
|
return M_OPT_OUT_OF_RANGE;
|
|
if ((opt->flags & M_OPT_MAX) && (len > opt->max))
|
|
return M_OPT_OUT_OF_RANGE;
|
|
return 0;
|
|
}
|
|
|
|
static int parse_str(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.start == NULL)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
if ((opt->flags & M_OPT_MIN) && (param.len < opt->min)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Parameter must be >= %d chars: %.*s\n",
|
|
(int) opt->min, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if ((opt->flags & M_OPT_MAX) && (param.len > opt->max)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Parameter must be <= %d chars: %.*s\n",
|
|
(int) opt->max, BSTR_P(param));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
|
|
if (dst) {
|
|
talloc_free(VAL(dst));
|
|
VAL(dst) = bstrdup0(NULL, param);
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
static char *print_str(const m_option_t *opt, const void *val)
|
|
{
|
|
return (val && VAL(val)) ? talloc_strdup(NULL, VAL(val)) : NULL;
|
|
}
|
|
|
|
static void copy_str(const m_option_t *opt, void *dst, const void *src)
|
|
{
|
|
if (dst && src) {
|
|
talloc_free(VAL(dst));
|
|
VAL(dst) = talloc_strdup(NULL, VAL(src));
|
|
}
|
|
}
|
|
|
|
static void free_str(void *src)
|
|
{
|
|
if (src && VAL(src)) {
|
|
talloc_free(VAL(src));
|
|
VAL(src) = NULL;
|
|
}
|
|
}
|
|
|
|
const m_option_type_t m_option_type_string = {
|
|
.name = "String",
|
|
.size = sizeof(char *),
|
|
.flags = M_OPT_TYPE_DYNAMIC,
|
|
.parse = parse_str,
|
|
.print = print_str,
|
|
.copy = copy_str,
|
|
.free = free_str,
|
|
.clamp = clamp_str,
|
|
};
|
|
|
|
//////////// String list
|
|
|
|
#undef VAL
|
|
#define VAL(x) (*(char ***)(x))
|
|
|
|
#define OP_NONE 0
|
|
#define OP_ADD 1
|
|
#define OP_PRE 2
|
|
#define OP_DEL 3
|
|
#define OP_CLR 4
|
|
|
|
static void free_str_list(void *dst)
|
|
{
|
|
char **d;
|
|
int i;
|
|
|
|
if (!dst || !VAL(dst))
|
|
return;
|
|
d = VAL(dst);
|
|
|
|
for (i = 0; d[i] != NULL; i++)
|
|
talloc_free(d[i]);
|
|
talloc_free(d);
|
|
VAL(dst) = NULL;
|
|
}
|
|
|
|
static int str_list_add(char **add, int n, void *dst, int pre)
|
|
{
|
|
if (!dst)
|
|
return M_OPT_PARSER_ERR;
|
|
char **lst = VAL(dst);
|
|
|
|
int ln;
|
|
for (ln = 0; lst && lst[ln]; ln++)
|
|
/**/;
|
|
|
|
lst = talloc_realloc(NULL, lst, char *, n + ln + 1);
|
|
|
|
if (pre) {
|
|
memmove(&lst[n], lst, ln * sizeof(char *));
|
|
memcpy(lst, add, n * sizeof(char *));
|
|
} else
|
|
memcpy(&lst[ln], add, n * sizeof(char *));
|
|
// (re-)add NULL-termination
|
|
lst[ln + n] = NULL;
|
|
|
|
talloc_free(add);
|
|
|
|
VAL(dst) = lst;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int str_list_del(char **del, int n, void *dst)
|
|
{
|
|
char **lst, *ep;
|
|
int i, ln, s;
|
|
long idx;
|
|
|
|
if (!dst)
|
|
return M_OPT_PARSER_ERR;
|
|
lst = VAL(dst);
|
|
|
|
for (ln = 0; lst && lst[ln]; ln++)
|
|
/**/;
|
|
s = ln;
|
|
|
|
for (i = 0; del[i] != NULL; i++) {
|
|
idx = strtol(del[i], &ep, 0);
|
|
if (*ep) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Invalid index: %s\n", del[i]);
|
|
talloc_free(del[i]);
|
|
continue;
|
|
}
|
|
talloc_free(del[i]);
|
|
if (idx < 0 || idx >= ln) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Index %ld is out of range.\n", idx);
|
|
continue;
|
|
} else if (!lst[idx])
|
|
continue;
|
|
talloc_free(lst[idx]);
|
|
lst[idx] = NULL;
|
|
s--;
|
|
}
|
|
talloc_free(del);
|
|
|
|
if (s == 0) {
|
|
talloc_free(lst);
|
|
VAL(dst) = NULL;
|
|
return 1;
|
|
}
|
|
|
|
// Don't bother shrinking the list allocation
|
|
for (i = 0, n = 0; i < ln; i++) {
|
|
if (!lst[i])
|
|
continue;
|
|
lst[n] = lst[i];
|
|
n++;
|
|
}
|
|
lst[s] = NULL;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static struct bstr get_nextsep(struct bstr *ptr, char sep, bool modify)
|
|
{
|
|
struct bstr str = *ptr;
|
|
struct bstr orig = str;
|
|
for (;;) {
|
|
int idx = bstrchr(str, sep);
|
|
if (idx > 0 && str.start[idx - 1] == '\\') {
|
|
if (modify) {
|
|
memmove(str.start + idx - 1, str.start + idx, str.len - idx);
|
|
str.len--;
|
|
str = bstr_cut(str, idx);
|
|
} else
|
|
str = bstr_cut(str, idx + 1);
|
|
} else {
|
|
str = bstr_cut(str, idx < 0 ? str.len : idx);
|
|
break;
|
|
}
|
|
}
|
|
*ptr = str;
|
|
return bstr_splice(orig, 0, str.start - orig.start);
|
|
}
|
|
|
|
static int parse_str_list(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
char **res;
|
|
int op = OP_NONE;
|
|
int len = strlen(opt->name);
|
|
if (opt->name[len - 1] == '*' && (name.len > len - 1)) {
|
|
struct bstr suffix = bstr_cut(name, len - 1);
|
|
if (bstrcasecmp0(suffix, "-add") == 0)
|
|
op = OP_ADD;
|
|
else if (bstrcasecmp0(suffix, "-pre") == 0)
|
|
op = OP_PRE;
|
|
else if (bstrcasecmp0(suffix, "-del") == 0)
|
|
op = OP_DEL;
|
|
else if (bstrcasecmp0(suffix, "-clr") == 0)
|
|
op = OP_CLR;
|
|
else
|
|
return M_OPT_UNKNOWN;
|
|
}
|
|
|
|
// Clear the list ??
|
|
if (op == OP_CLR) {
|
|
if (dst)
|
|
free_str_list(dst);
|
|
return 0;
|
|
}
|
|
|
|
// All other ops need a param
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
// custom type for "profile" calls this but uses ->priv for something else
|
|
char separator = opt->type == &m_option_type_string_list && opt->priv ?
|
|
*(char *)opt->priv : OPTION_LIST_SEPARATOR;
|
|
int n = 0;
|
|
struct bstr str = param;
|
|
while (str.len) {
|
|
get_nextsep(&str, separator, 0);
|
|
str = bstr_cut(str, 1);
|
|
n++;
|
|
}
|
|
if (n == 0)
|
|
return M_OPT_INVALID;
|
|
if (((opt->flags & M_OPT_MIN) && (n < opt->min)) ||
|
|
((opt->flags & M_OPT_MAX) && (n > opt->max)))
|
|
return M_OPT_OUT_OF_RANGE;
|
|
|
|
if (!dst)
|
|
return 1;
|
|
|
|
res = talloc_array(NULL, char *, n + 2);
|
|
str = bstrdup(NULL, param);
|
|
char *ptr = str.start;
|
|
n = 0;
|
|
|
|
while (1) {
|
|
struct bstr el = get_nextsep(&str, separator, 1);
|
|
res[n] = bstrdup0(NULL, el);
|
|
n++;
|
|
if (!str.len)
|
|
break;
|
|
str = bstr_cut(str, 1);
|
|
}
|
|
res[n] = NULL;
|
|
talloc_free(ptr);
|
|
|
|
switch (op) {
|
|
case OP_ADD:
|
|
return str_list_add(res, n, dst, 0);
|
|
case OP_PRE:
|
|
return str_list_add(res, n, dst, 1);
|
|
case OP_DEL:
|
|
return str_list_del(res, n, dst);
|
|
}
|
|
|
|
if (VAL(dst))
|
|
free_str_list(dst);
|
|
VAL(dst) = res;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void copy_str_list(const m_option_t *opt, void *dst, const void *src)
|
|
{
|
|
int n;
|
|
char **d, **s;
|
|
|
|
if (!(dst && src))
|
|
return;
|
|
s = VAL(src);
|
|
|
|
if (VAL(dst))
|
|
free_str_list(dst);
|
|
|
|
if (!s) {
|
|
VAL(dst) = NULL;
|
|
return;
|
|
}
|
|
|
|
for (n = 0; s[n] != NULL; n++)
|
|
/* NOTHING */;
|
|
d = talloc_array(NULL, char *, n + 1);
|
|
for (; n >= 0; n--)
|
|
d[n] = talloc_strdup(NULL, s[n]);
|
|
|
|
VAL(dst) = d;
|
|
}
|
|
|
|
static char *print_str_list(const m_option_t *opt, const void *src)
|
|
{
|
|
char **lst = NULL;
|
|
char *ret = NULL;
|
|
|
|
if (!(src && VAL(src)))
|
|
return NULL;
|
|
lst = VAL(src);
|
|
|
|
for (int i = 0; lst[i]; i++) {
|
|
if (ret)
|
|
ret = talloc_strdup_append_buffer(ret, ",");
|
|
ret = talloc_strdup_append_buffer(ret, lst[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_string_list = {
|
|
/* A list of strings separated by ','.
|
|
* Option with a name ending in '*' permits using the following suffixes:
|
|
* -add: Add the given parameters at the end of the list.
|
|
* -pre: Add the given parameters at the beginning of the list.
|
|
* -del: Remove the entry at the given indices.
|
|
* -clr: Clear the list.
|
|
* e.g: -vf-add flip,mirror -vf-del 2,5
|
|
*/
|
|
.name = "String list",
|
|
.size = sizeof(char **),
|
|
.flags = M_OPT_TYPE_DYNAMIC | M_OPT_TYPE_ALLOW_WILDCARD,
|
|
.parse = parse_str_list,
|
|
.print = print_str_list,
|
|
.copy = copy_str_list,
|
|
.free = free_str_list,
|
|
};
|
|
|
|
|
|
/////////////////// Print
|
|
|
|
static int parse_print(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (opt->type == CONF_TYPE_PRINT) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s", mp_gtext(opt->p));
|
|
} else {
|
|
char *name0 = bstrdup0(NULL, name);
|
|
char *param0 = bstrdup0(NULL, param);
|
|
int r = ((m_opt_func_full_t) opt->p)(opt, name0, param0);
|
|
talloc_free(name0);
|
|
talloc_free(param0);
|
|
return r;
|
|
}
|
|
|
|
if (opt->priv == NULL)
|
|
return M_OPT_EXIT;
|
|
return 0;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_print = {
|
|
.name = "Print",
|
|
.flags = M_OPT_TYPE_OPTIONAL_PARAM,
|
|
.parse = parse_print,
|
|
};
|
|
|
|
const m_option_type_t m_option_type_print_func_param = {
|
|
.name = "Print",
|
|
.flags = M_OPT_TYPE_ALLOW_WILDCARD,
|
|
.parse = parse_print,
|
|
};
|
|
|
|
const m_option_type_t m_option_type_print_func = {
|
|
.name = "Print",
|
|
.flags = M_OPT_TYPE_ALLOW_WILDCARD | M_OPT_TYPE_OPTIONAL_PARAM,
|
|
.parse = parse_print,
|
|
};
|
|
|
|
|
|
/////////////////////// Subconfig
|
|
#undef VAL
|
|
#define VAL(x) (*(char ***)(x))
|
|
|
|
static int parse_subconf(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
int nr = 0;
|
|
char **lst = NULL;
|
|
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
struct bstr p = param;
|
|
|
|
while (p.len) {
|
|
int optlen = bstrcspn(p, ":=");
|
|
struct bstr subopt = bstr_splice(p, 0, optlen);
|
|
struct bstr subparam = bstr0(NULL);
|
|
p = bstr_cut(p, optlen);
|
|
if (bstr_startswith0(p, "=")) {
|
|
p = bstr_cut(p, 1);
|
|
if (bstr_startswith0(p, "\"")) {
|
|
p = bstr_cut(p, 1);
|
|
optlen = bstrcspn(p, "\"");
|
|
subparam = bstr_splice(p, 0, optlen);
|
|
p = bstr_cut(p, optlen);
|
|
if (!bstr_startswith0(p, "\"")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Terminating '\"' missing for '%.*s'\n",
|
|
BSTR_P(subopt));
|
|
return M_OPT_INVALID;
|
|
}
|
|
p = bstr_cut(p, 1);
|
|
} else if (bstr_startswith0(p, "%")) {
|
|
p = bstr_cut(p, 1);
|
|
optlen = bstrtoll(p, &p, 0);
|
|
if (!bstr_startswith0(p, "%") || (optlen > p.len - 1)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Invalid length %d for '%.*s'\n",
|
|
optlen, BSTR_P(subopt));
|
|
return M_OPT_INVALID;
|
|
}
|
|
subparam = bstr_splice(p, 1, optlen + 1);
|
|
p = bstr_cut(p, optlen + 1);
|
|
} else {
|
|
optlen = bstrcspn(p, ":");
|
|
subparam = bstr_splice(p, 0, optlen);
|
|
p = bstr_cut(p, optlen);
|
|
}
|
|
}
|
|
if (bstr_startswith0(p, ":"))
|
|
p = bstr_cut(p, 1);
|
|
else if (p.len > 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Incorrect termination for '%.*s'\n", BSTR_P(subopt));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (dst) {
|
|
lst = talloc_realloc(NULL, lst, char *, 2 * (nr + 2));
|
|
lst[2 * nr] = bstrdup0(lst, subopt);
|
|
lst[2 * nr + 1] = bstrdup0(lst, subparam);
|
|
memset(&lst[2 * (nr + 1)], 0, 2 * sizeof(char *));
|
|
nr++;
|
|
}
|
|
}
|
|
|
|
if (dst)
|
|
VAL(dst) = lst;
|
|
|
|
return 1;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_subconfig = {
|
|
// The syntax is -option opt1=foo:flag:opt2=blah
|
|
.name = "Subconfig",
|
|
.flags = M_OPT_TYPE_HAS_CHILD,
|
|
.parse = parse_subconf,
|
|
};
|
|
|
|
const m_option_type_t m_option_type_subconfig_struct = {
|
|
.name = "Subconfig",
|
|
.flags = M_OPT_TYPE_HAS_CHILD | M_OPT_TYPE_USE_SUBSTRUCT,
|
|
.parse = parse_subconf,
|
|
};
|
|
|
|
static int parse_color(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
bstr val = param;
|
|
struct m_color color = {0};
|
|
|
|
if (bstr_eatstart0(&val, "#")) {
|
|
// #[AA]RRGGBB
|
|
if (val.len != 6 && val.len != 8)
|
|
goto error;
|
|
bool has_alpha = val.len == 8;
|
|
uint32_t c = bstrtoll(val, &val, 16);
|
|
if (val.len)
|
|
goto error;
|
|
color = (struct m_color) {
|
|
(c >> 16) & 0xFF,
|
|
(c >> 8) & 0xFF,
|
|
c & 0xFF,
|
|
has_alpha ? (c >> 24) & 0xFF : 0xFF,
|
|
};
|
|
} else {
|
|
goto error;
|
|
}
|
|
|
|
if (dst)
|
|
*((struct m_color *)dst) = color;
|
|
|
|
return 1;
|
|
|
|
error:
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: invalid color: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Valid colors must be in the form #RRRGGBB or #AARRGGBB (in hex)\n");
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_color = {
|
|
.name = "Color",
|
|
.size = sizeof(struct m_color),
|
|
.parse = parse_color,
|
|
};
|
|
|
|
|
|
// Parse a >=0 number starting at s. Set s to the string following the number.
|
|
// If the number ends with '%', eat that and set *out_per to true, but only
|
|
// if the number is between 0-100; if not, don't eat anything, even the number.
|
|
static bool eat_num_per(bstr *s, int *out_num, bool *out_per)
|
|
{
|
|
bstr rest;
|
|
long long v = bstrtoll(*s, &rest, 10);
|
|
if (s->len == rest.len || v < INT_MIN || v > INT_MAX)
|
|
return false;
|
|
*out_num = v;
|
|
*out_per = false;
|
|
*s = rest;
|
|
if (bstr_eatstart0(&rest, "%") && v >= 0 && v <= 100) {
|
|
*out_per = true;
|
|
*s = rest;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool parse_geometry_str(struct m_geometry *gm, bstr s)
|
|
{
|
|
*gm = (struct m_geometry) { .x = INT_MIN, .y = INT_MIN };
|
|
if (s.len == 0)
|
|
return true;
|
|
// Approximate grammar:
|
|
// [W[xH]][{+-}X{+-}Y] | [X:Y]
|
|
// (meaning: [optional] {one character of} one|alternative)
|
|
// Every number can be followed by '%'
|
|
int num;
|
|
bool per;
|
|
|
|
#define READ_NUM(F, F_PER) do { \
|
|
if (!eat_num_per(&s, &num, &per)) \
|
|
goto error; \
|
|
gm->F = num; \
|
|
gm->F_PER = per; \
|
|
} while(0)
|
|
|
|
#define READ_SIGN(F) do { \
|
|
if (bstr_eatstart0(&s, "+")) { \
|
|
gm->F = false; \
|
|
} else if (bstr_eatstart0(&s, "-")) {\
|
|
gm->F = true; \
|
|
} else goto error; \
|
|
} while(0)
|
|
|
|
if (bstrchr(s, ':') < 0) {
|
|
gm->wh_valid = true;
|
|
if (!bstr_startswith0(s, "+") && !bstr_startswith0(s, "-")) {
|
|
READ_NUM(w, w_per);
|
|
if (bstr_eatstart0(&s, "x"))
|
|
READ_NUM(h, h_per);
|
|
}
|
|
if (s.len > 0) {
|
|
gm->xy_valid = true;
|
|
READ_SIGN(x_sign);
|
|
READ_NUM(x, x_per);
|
|
READ_SIGN(y_sign);
|
|
READ_NUM(y, y_per);
|
|
}
|
|
} else {
|
|
gm->xy_valid = true;
|
|
READ_NUM(x, x_per);
|
|
if (!bstr_eatstart0(&s, ":"))
|
|
goto error;
|
|
READ_NUM(y, y_per);
|
|
}
|
|
|
|
return s.len == 0;
|
|
|
|
error:
|
|
return false;
|
|
}
|
|
|
|
#undef READ_NUM
|
|
#undef READ_SIGN
|
|
|
|
// xpos,ypos: position of the left upper corner
|
|
// widw,widh: width and height of the window
|
|
// scrw,scrh: width and height of the current screen
|
|
// The input parameters should be set to a centered window (default fallbacks).
|
|
void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
|
|
int scrw, int scrh, struct m_geometry *gm)
|
|
{
|
|
if (gm->wh_valid) {
|
|
int prew = *widw, preh = *widh;
|
|
if (gm->w > 0)
|
|
*widw = gm->w_per ? scrw * (gm->w / 100.0) : gm->w;
|
|
if (gm->h > 0)
|
|
*widh = gm->h_per ? scrh * (gm->h / 100.0) : gm->h;
|
|
// keep aspect if the other value is not set
|
|
double asp = (double)prew / preh;
|
|
if (gm->w > 0 && !(gm->h > 0)) {
|
|
*widh = *widw / asp;
|
|
} else if (!(gm->w > 0) && gm->h > 0) {
|
|
*widw = *widh * asp;
|
|
}
|
|
}
|
|
|
|
if (gm->xy_valid) {
|
|
if (gm->x != INT_MIN) {
|
|
*xpos = gm->x;
|
|
if (gm->x_per)
|
|
*xpos = (scrw - *widw) * (*xpos / 100.0);
|
|
if (gm->x_sign)
|
|
*xpos = scrw - *widw - *xpos;
|
|
}
|
|
if (gm->y != INT_MIN) {
|
|
*ypos = gm->y;
|
|
if (gm->y_per)
|
|
*ypos = (scrh - *widh) * (*ypos / 100.0);
|
|
if (gm->y_sign)
|
|
*ypos = scrh - *widh - *ypos;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int parse_geometry(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
struct m_geometry gm;
|
|
if (!parse_geometry_str(&gm, param))
|
|
goto error;
|
|
|
|
if (dst)
|
|
*((struct m_geometry *)dst) = gm;
|
|
|
|
return 1;
|
|
|
|
error:
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid geometry: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_geometry = {
|
|
.name = "Window geometry",
|
|
.size = sizeof(struct m_geometry),
|
|
.parse = parse_geometry,
|
|
};
|
|
|
|
static int parse_size_box(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
struct m_geometry gm;
|
|
if (!parse_geometry_str(&gm, param))
|
|
goto error;
|
|
|
|
if (gm.xy_valid)
|
|
goto error;
|
|
|
|
if (dst)
|
|
*((struct m_geometry *)dst) = gm;
|
|
|
|
return 1;
|
|
|
|
error:
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid size: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Valid format: W[%%][xH[%%]] or empty string\n");
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_size_box = {
|
|
.name = "Window size",
|
|
.size = sizeof(struct m_geometry),
|
|
.parse = parse_size_box,
|
|
};
|
|
|
|
|
|
#include "video/img_format.h"
|
|
|
|
static int parse_imgfmt(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
if (!bstrcmp0(param, "help")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available formats:");
|
|
for (int i = 0; mp_imgfmt_list[i].name; i++)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", mp_imgfmt_list[i].name);
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
|
|
unsigned int fmt = mp_imgfmt_from_name(param, false);
|
|
if (!fmt) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: unknown format name: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (dst)
|
|
*((uint32_t *)dst) = fmt;
|
|
|
|
return 1;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_imgfmt = {
|
|
// Please report any missing colorspaces
|
|
.name = "Image format",
|
|
.size = sizeof(uint32_t),
|
|
.parse = parse_imgfmt,
|
|
.copy = copy_opt,
|
|
};
|
|
|
|
static int parse_fourcc(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
unsigned int value;
|
|
|
|
if (param.len == 4) {
|
|
uint8_t *s = param.start;
|
|
value = s[0] | (s[1] << 8) | (s[2] << 16) | (s[3] << 24);
|
|
} else {
|
|
bstr rest;
|
|
value = bstrtoll(param, &rest, 16);
|
|
if (rest.len != 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: invalid FourCC: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
}
|
|
|
|
if (dst)
|
|
*((unsigned int *)dst) = value;
|
|
|
|
return 1;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_fourcc = {
|
|
.name = "FourCC",
|
|
.size = sizeof(unsigned int),
|
|
.parse = parse_fourcc,
|
|
.copy = copy_opt,
|
|
};
|
|
|
|
#include "audio/format.h"
|
|
|
|
static int parse_afmt(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
if (!bstrcmp0(param, "help")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available formats:");
|
|
for (int i = 0; af_fmtstr_table[i].name; i++)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", af_fmtstr_table[i].name);
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
|
|
int fmt = af_str2fmt_short(param);
|
|
if (fmt == -1) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: unknown format name: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (dst)
|
|
*((uint32_t *)dst) = fmt;
|
|
|
|
return 1;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_afmt = {
|
|
// Please report any missing formats
|
|
.name = "Audio format",
|
|
.size = sizeof(uint32_t),
|
|
.parse = parse_afmt,
|
|
.copy = copy_opt,
|
|
};
|
|
|
|
|
|
static int parse_timestring(struct bstr str, double *time, char endchar)
|
|
{
|
|
int a, b, len;
|
|
double d;
|
|
*time = 0; /* ensure initialization for error cases */
|
|
if (bstr_sscanf(str, "%d:%d:%lf%n", &a, &b, &d, &len) >= 3)
|
|
*time = 3600 * a + 60 * b + d;
|
|
else if (bstr_sscanf(str, "%d:%lf%n", &a, &d, &len) >= 2)
|
|
*time = 60 * a + d;
|
|
else if (bstr_sscanf(str, "%lf%n", &d, &len) >= 1)
|
|
*time = d;
|
|
else
|
|
return 0; /* unsupported time format */
|
|
if (len < str.len && str.start[len] != endchar)
|
|
return 0; /* invalid extra characters at the end */
|
|
return len;
|
|
}
|
|
|
|
|
|
static int parse_time(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
double time;
|
|
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
if (!parse_timestring(param, &time, 0)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid time: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (dst)
|
|
*(double *)dst = time;
|
|
return 1;
|
|
}
|
|
|
|
static char *pretty_print_time(const m_option_t *opt, const void *val)
|
|
{
|
|
return mp_format_time(*(double *)val, false);
|
|
}
|
|
|
|
const m_option_type_t m_option_type_time = {
|
|
.name = "Time",
|
|
.size = sizeof(double),
|
|
.parse = parse_time,
|
|
.print = print_double,
|
|
.pretty_print = pretty_print_time,
|
|
.copy = copy_opt,
|
|
.add = add_double,
|
|
.clamp = clamp_double,
|
|
};
|
|
|
|
|
|
// Relative time
|
|
|
|
static int parse_rel_time(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
struct m_rel_time t = {0};
|
|
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
// Percent pos
|
|
if (bstr_endswith0(param, "%")) {
|
|
double percent = bstrtod(bstr_splice(param, 0, -1), ¶m);
|
|
if (param.len == 0 && percent >= 0 && percent <= 100) {
|
|
t.type = REL_TIME_PERCENT;
|
|
t.pos = percent;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
// Chapter pos
|
|
if (bstr_startswith0(param, "#")) {
|
|
int chapter = bstrtoll(bstr_cut(param, 1), ¶m, 10);
|
|
if (param.len == 0 && chapter >= 1) {
|
|
t.type = REL_TIME_CHAPTER;
|
|
t.pos = chapter - 1;
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
bool sign = bstr_eatstart0(¶m, "-");
|
|
double time;
|
|
if (parse_timestring(param, &time, 0)) {
|
|
t.type = sign ? REL_TIME_NEGATIVE : REL_TIME_ABSOLUTE;
|
|
t.pos = time;
|
|
goto out;
|
|
}
|
|
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: invalid time or position: '%.*s'\n",
|
|
BSTR_P(name), BSTR_P(param));
|
|
return M_OPT_INVALID;
|
|
|
|
out:
|
|
if (dst)
|
|
*(struct m_rel_time *)dst = t;
|
|
return 1;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_rel_time = {
|
|
.name = "Relative time or percent position",
|
|
.size = sizeof(struct m_rel_time),
|
|
.parse = parse_rel_time,
|
|
.copy = copy_opt,
|
|
};
|
|
|
|
|
|
//// Objects (i.e. filters, etc) settings
|
|
|
|
#include "core/m_struct.h"
|
|
|
|
#undef VAL
|
|
#define VAL(x) (*(m_obj_settings_t **)(x))
|
|
|
|
static int find_obj_desc(struct bstr name, const m_obj_list_t *l,
|
|
const m_struct_t **ret)
|
|
{
|
|
int i;
|
|
char *n;
|
|
|
|
for (i = 0; l->list[i]; i++) {
|
|
n = M_ST_MB(char *, l->list[i], l->name_off);
|
|
if (!bstrcmp0(name, n)) {
|
|
*ret = M_ST_MB(m_struct_t *, l->list[i], l->desc_off);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int get_obj_param(struct bstr opt_name, struct bstr obj_name,
|
|
const m_struct_t *desc, struct bstr str, int *nold,
|
|
int oldmax, char **dst)
|
|
{
|
|
const m_option_t *opt;
|
|
int r;
|
|
|
|
int eq = bstrchr(str, '=');
|
|
|
|
if (eq > 0) { // eq == 0 ignored
|
|
struct bstr p = bstr_cut(str, eq + 1);
|
|
str = bstr_splice(str, 0, eq);
|
|
opt = m_option_list_findb(desc->fields, str);
|
|
if (!opt) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: %.*s doesn't have a %.*s parameter.\n",
|
|
BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(str));
|
|
return M_OPT_UNKNOWN;
|
|
}
|
|
r = m_option_parse(opt, str, p, NULL);
|
|
if (r < 0) {
|
|
if (r > M_OPT_EXIT)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: "
|
|
"Error while parsing %.*s parameter %.*s (%.*s)\n",
|
|
BSTR_P(opt_name), BSTR_P(obj_name), BSTR_P(str),
|
|
BSTR_P(p));
|
|
return r;
|
|
}
|
|
if (dst) {
|
|
dst[0] = bstrdup0(NULL, str);
|
|
dst[1] = bstrdup0(NULL, p);
|
|
}
|
|
} else {
|
|
if ((*nold) >= oldmax) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: %.*s has only %d params, so you can't give more than %d unnamed params.\n",
|
|
BSTR_P(opt_name), BSTR_P(obj_name), oldmax, oldmax);
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
opt = &desc->fields[(*nold)];
|
|
r = m_option_parse(opt, bstr0(opt->name), str, NULL);
|
|
if (r < 0) {
|
|
if (r > M_OPT_EXIT)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: "
|
|
"Error while parsing %.*s parameter %s (%.*s)\n",
|
|
BSTR_P(opt_name), BSTR_P(obj_name), opt->name,
|
|
BSTR_P(str));
|
|
return r;
|
|
}
|
|
if (dst) {
|
|
dst[0] = talloc_strdup(NULL, opt->name);
|
|
dst[1] = bstrdup0(NULL, str);
|
|
}
|
|
(*nold)++;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int get_obj_params(struct bstr opt_name, struct bstr name,
|
|
struct bstr params, const m_struct_t *desc,
|
|
char separator, char ***_ret)
|
|
{
|
|
int n = 0, nold = 0, nopts;
|
|
char **ret;
|
|
|
|
if (!bstrcmp0(params, "help")) { // Help
|
|
char min[50], max[50];
|
|
if (!desc->fields) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO,
|
|
"%.*s doesn't have any options.\n\n", BSTR_P(name));
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO,
|
|
"\n Name Type Min Max\n\n");
|
|
for (n = 0; desc->fields[n].name; n++) {
|
|
const m_option_t *opt = &desc->fields[n];
|
|
if (opt->type->flags & M_OPT_TYPE_HAS_CHILD)
|
|
continue;
|
|
if (opt->flags & M_OPT_MIN)
|
|
sprintf(min, "%-8.0f", opt->min);
|
|
else
|
|
strcpy(min, "No");
|
|
if (opt->flags & M_OPT_MAX)
|
|
sprintf(max, "%-8.0f", opt->max);
|
|
else
|
|
strcpy(max, "No");
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO,
|
|
" %-20.20s %-15.15s %-10.10s %-10.10s\n",
|
|
opt->name, opt->type->name, min, max);
|
|
}
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
|
|
for (nopts = 0; desc->fields[nopts].name; nopts++)
|
|
/* NOP */;
|
|
|
|
// TODO : Check that each opt can be parsed
|
|
struct bstr s = params;
|
|
while (1) {
|
|
bool end = false;
|
|
int idx = bstrchr(s, separator);
|
|
if (idx < 0) {
|
|
idx = s.len;
|
|
end = true;
|
|
}
|
|
struct bstr field = bstr_splice(s, 0, idx);
|
|
s = bstr_cut(s, idx + 1);
|
|
if (field.len == 0) { // Empty field, count it and go on
|
|
nold++;
|
|
} else {
|
|
int r = get_obj_param(opt_name, name, desc, field, &nold, nopts,
|
|
NULL);
|
|
if (r < 0)
|
|
return r;
|
|
n++;
|
|
}
|
|
if (end)
|
|
break;
|
|
}
|
|
if (nold > nopts) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Too many options for %.*s\n",
|
|
BSTR_P(name));
|
|
return M_OPT_OUT_OF_RANGE;
|
|
}
|
|
if (!_ret) // Just test
|
|
return 1;
|
|
if (n == 0) // No options or only empty options
|
|
return 1;
|
|
|
|
ret = talloc_array(NULL, char *, (n + 2) * 2);
|
|
n = nold = 0;
|
|
s = params;
|
|
|
|
while (s.len > 0) {
|
|
int idx = bstrchr(s, separator);
|
|
if (idx < 0)
|
|
idx = s.len;
|
|
struct bstr field = bstr_splice(s, 0, idx);
|
|
s = bstr_cut(s, idx + 1);
|
|
if (field.len == 0) { // Empty field, count it and go on
|
|
nold++;
|
|
} else {
|
|
get_obj_param(opt_name, name, desc, field, &nold, nopts,
|
|
&ret[n * 2]);
|
|
n++;
|
|
}
|
|
}
|
|
ret[n * 2] = ret[n * 2 + 1] = NULL;
|
|
*_ret = ret;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int parse_obj_params(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
char **opts;
|
|
int r;
|
|
m_obj_params_t *p = opt->priv;
|
|
const m_struct_t *desc;
|
|
|
|
// We need the object desc
|
|
if (!p)
|
|
return M_OPT_INVALID;
|
|
|
|
desc = p->desc;
|
|
r = get_obj_params(name, bstr0(desc->name), param, desc, p->separator,
|
|
dst ? &opts : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
if (!dst)
|
|
return 1;
|
|
if (!opts) // no arguments given
|
|
return 1;
|
|
|
|
for (r = 0; opts[r]; r += 2)
|
|
m_struct_set(desc, dst, opts[r], bstr0(opts[r + 1]));
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
const m_option_type_t m_option_type_obj_params = {
|
|
.name = "Object params",
|
|
.parse = parse_obj_params,
|
|
};
|
|
|
|
/// Some predefined types as a definition would be quite lengthy
|
|
|
|
/// Span arguments
|
|
static const m_span_t m_span_params_dflts = {
|
|
-1, -1
|
|
};
|
|
static const m_option_t m_span_params_fields[] = {
|
|
{"start", M_ST_OFF(m_span_t, start), CONF_TYPE_INT, M_OPT_MIN, 1, 0, NULL},
|
|
{"end", M_ST_OFF(m_span_t, end), CONF_TYPE_INT, M_OPT_MIN, 1, 0, NULL},
|
|
{ NULL, NULL, 0, 0, 0, 0, NULL }
|
|
};
|
|
static const struct m_struct_st m_span_opts = {
|
|
"m_span",
|
|
sizeof(m_span_t),
|
|
&m_span_params_dflts,
|
|
m_span_params_fields
|
|
};
|
|
const m_obj_params_t m_span_params_def = {
|
|
&m_span_opts,
|
|
'-'
|
|
};
|
|
|
|
static int parse_obj_settings(struct bstr opt, struct bstr str,
|
|
const m_obj_list_t *list,
|
|
m_obj_settings_t **_ret, int ret_n)
|
|
{
|
|
int r;
|
|
char **plist = NULL;
|
|
const m_struct_t *desc;
|
|
m_obj_settings_t *ret = _ret ? *_ret : NULL;
|
|
|
|
struct bstr param = bstr0(NULL);
|
|
int idx = bstrchr(str, '=');
|
|
if (idx >= 0) {
|
|
param = bstr_cut(str, idx + 1);
|
|
str = bstr_splice(str, 0, idx);
|
|
}
|
|
|
|
if (!find_obj_desc(str, list, &desc)) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: %.*s doesn't exist.\n",
|
|
BSTR_P(opt), BSTR_P(str));
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (param.start) {
|
|
if (!desc && _ret) {
|
|
if (!bstrcmp0(param, "help")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO,
|
|
"Option %.*s: %.*s have no option description.\n",
|
|
BSTR_P(opt), BSTR_P(str));
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
plist = talloc_zero_array(NULL, char *, 4);
|
|
plist[0] = talloc_strdup(NULL, "_oldargs_");
|
|
plist[1] = bstrdup0(NULL, param);
|
|
} else if (desc) {
|
|
r = get_obj_params(opt, str, param, desc, ':',
|
|
_ret ? &plist : NULL);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
}
|
|
if (!_ret)
|
|
return 1;
|
|
|
|
ret = talloc_realloc(NULL, ret, struct m_obj_settings, ret_n + 2);
|
|
memset(&ret[ret_n], 0, 2 * sizeof(m_obj_settings_t));
|
|
ret[ret_n].name = bstrdup0(NULL, str);
|
|
ret[ret_n].attribs = plist;
|
|
|
|
*_ret = ret;
|
|
return 1;
|
|
}
|
|
|
|
static int obj_settings_list_del(struct bstr opt_name, struct bstr param,
|
|
void *dst)
|
|
{
|
|
char **str_list = NULL;
|
|
int r, i, idx_max = 0;
|
|
char *rem_id = "_removed_marker_";
|
|
char name[100];
|
|
assert(opt_name.len < 100);
|
|
memcpy(name, opt_name.start, opt_name.len);
|
|
name[opt_name.len] = 0;
|
|
const m_option_t list_opt = {
|
|
name, NULL, CONF_TYPE_STRING_LIST,
|
|
0, 0, 0, NULL
|
|
};
|
|
m_obj_settings_t *obj_list = dst ? VAL(dst) : NULL;
|
|
|
|
if (dst && !obj_list) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Option %.*s: the list is empty.\n",
|
|
BSTR_P(opt_name));
|
|
return 1;
|
|
} else if (obj_list) {
|
|
for (idx_max = 0; obj_list[idx_max].name != NULL; idx_max++)
|
|
/* NOP */;
|
|
}
|
|
|
|
r = m_option_parse(&list_opt, opt_name, param, &str_list);
|
|
if (r < 0 || !str_list)
|
|
return r;
|
|
|
|
for (r = 0; str_list[r]; r++) {
|
|
int id;
|
|
char *endptr;
|
|
id = strtol(str_list[r], &endptr, 0);
|
|
if (endptr == str_list[r]) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid parameter. We need a list of integers which are the indices of the elements to remove.\n", BSTR_P(opt_name));
|
|
m_option_free(&list_opt, &str_list);
|
|
return M_OPT_INVALID;
|
|
}
|
|
if (!obj_list)
|
|
continue;
|
|
if (id >= idx_max || id < -idx_max) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: Index %d is out of range.\n",
|
|
BSTR_P(opt_name), id);
|
|
continue;
|
|
}
|
|
if (id < 0)
|
|
id = idx_max + id;
|
|
talloc_free(obj_list[id].name);
|
|
free_str_list(&(obj_list[id].attribs));
|
|
obj_list[id].name = rem_id;
|
|
}
|
|
|
|
if (!dst) {
|
|
m_option_free(&list_opt, &str_list);
|
|
return 1;
|
|
}
|
|
|
|
for (i = 0; obj_list[i].name; i++) {
|
|
while (obj_list[i].name == rem_id) {
|
|
memmove(&obj_list[i], &obj_list[i + 1],
|
|
sizeof(m_obj_settings_t) * (idx_max - i));
|
|
idx_max--;
|
|
}
|
|
}
|
|
obj_list = talloc_realloc(NULL, obj_list, struct m_obj_settings,
|
|
idx_max + 1);
|
|
VAL(dst) = obj_list;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void free_obj_settings_list(void *dst)
|
|
{
|
|
int n;
|
|
m_obj_settings_t *d;
|
|
|
|
if (!dst || !VAL(dst))
|
|
return;
|
|
|
|
d = VAL(dst);
|
|
for (n = 0; d[n].name; n++) {
|
|
talloc_free(d[n].name);
|
|
free_str_list(&(d[n].attribs));
|
|
}
|
|
talloc_free(d);
|
|
VAL(dst) = NULL;
|
|
}
|
|
|
|
static int parse_obj_settings_list(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
int len = strlen(opt->name);
|
|
m_obj_settings_t *res = NULL, *queue = NULL, *head = NULL;
|
|
int op = OP_NONE;
|
|
|
|
// We need the objects list
|
|
if (!opt->priv)
|
|
return M_OPT_INVALID;
|
|
|
|
if (opt->name[len - 1] == '*' && (name.len > len - 1)) {
|
|
struct bstr suffix = bstr_cut(name, len - 1);
|
|
if (bstrcasecmp0(suffix, "-add") == 0)
|
|
op = OP_ADD;
|
|
else if (bstrcasecmp0(suffix, "-pre") == 0)
|
|
op = OP_PRE;
|
|
else if (bstrcasecmp0(suffix, "-del") == 0)
|
|
op = OP_DEL;
|
|
else if (bstrcasecmp0(suffix, "-clr") == 0)
|
|
op = OP_CLR;
|
|
else {
|
|
char prefix[len];
|
|
strncpy(prefix, opt->name, len - 1);
|
|
prefix[len - 1] = '\0';
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: unknown postfix %.*s\n"
|
|
"Supported postfixes are:\n"
|
|
" %s-add\n"
|
|
" Append the given list to the current list\n\n"
|
|
" %s-pre\n"
|
|
" Prepend the given list to the current list\n\n"
|
|
" %s-del x,y,...\n"
|
|
" Remove the given elements. Take the list element index (starting from 0).\n"
|
|
" Negative index can be used (i.e. -1 is the last element)\n\n"
|
|
" %s-clr\n"
|
|
" Clear the current list.\n",
|
|
BSTR_P(name), BSTR_P(suffix), prefix, prefix, prefix, prefix);
|
|
|
|
return M_OPT_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
// Clear the list ??
|
|
if (op == OP_CLR) {
|
|
if (dst)
|
|
free_obj_settings_list(dst);
|
|
return 0;
|
|
}
|
|
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
switch (op) {
|
|
case OP_ADD:
|
|
if (dst)
|
|
head = VAL(dst);
|
|
break;
|
|
case OP_PRE:
|
|
if (dst)
|
|
queue = VAL(dst);
|
|
break;
|
|
case OP_DEL:
|
|
return obj_settings_list_del(name, param, dst);
|
|
case OP_NONE:
|
|
if (dst && VAL(dst))
|
|
free_obj_settings_list(dst);
|
|
break;
|
|
default:
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: FIXME\n", BSTR_P(name));
|
|
return M_OPT_UNKNOWN;
|
|
}
|
|
|
|
if (!bstrcmp0(param, "help")) {
|
|
m_obj_list_t *ol = opt->priv;
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available video filters:\n");
|
|
for (int n = 0; ol->list[n]; n++)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %-15s: %s\n",
|
|
M_ST_MB(char *, ol->list[n], ol->name_off),
|
|
M_ST_MB(char *, ol->list[n], ol->info_off));
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
|
|
struct bstr s = bstrdup(NULL, param);
|
|
char *allocptr = s.start;
|
|
int n = 0;
|
|
while (s.len > 0) {
|
|
struct bstr el = get_nextsep(&s, OPTION_LIST_SEPARATOR, 1);
|
|
int r = parse_obj_settings(name, el, opt->priv, dst ? &res : NULL, n);
|
|
if (r < 0) {
|
|
talloc_free(allocptr);
|
|
return r;
|
|
}
|
|
s = bstr_cut(s, 1);
|
|
n++;
|
|
}
|
|
talloc_free(allocptr);
|
|
if (n == 0)
|
|
return M_OPT_INVALID;
|
|
|
|
if (((opt->flags & M_OPT_MIN) && (n < opt->min)) ||
|
|
((opt->flags & M_OPT_MAX) && (n > opt->max)))
|
|
return M_OPT_OUT_OF_RANGE;
|
|
|
|
if (dst) {
|
|
if (queue) {
|
|
int qsize;
|
|
for (qsize = 0; queue[qsize].name; qsize++)
|
|
/* NOP */;
|
|
res = talloc_realloc(NULL, res, struct m_obj_settings,
|
|
qsize + n + 1);
|
|
memcpy(&res[n], queue, (qsize + 1) * sizeof(m_obj_settings_t));
|
|
n += qsize;
|
|
talloc_free(queue);
|
|
}
|
|
if (head) {
|
|
int hsize;
|
|
for (hsize = 0; head[hsize].name; hsize++)
|
|
/* NOP */;
|
|
head = talloc_realloc(NULL, head, struct m_obj_settings,
|
|
hsize + n + 1);
|
|
memcpy(&head[hsize], res, (n + 1) * sizeof(m_obj_settings_t));
|
|
talloc_free(res);
|
|
res = head;
|
|
}
|
|
VAL(dst) = res;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void copy_obj_settings_list(const m_option_t *opt, void *dst,
|
|
const void *src)
|
|
{
|
|
m_obj_settings_t *d, *s;
|
|
int n;
|
|
|
|
if (!(dst && src))
|
|
return;
|
|
|
|
s = VAL(src);
|
|
|
|
if (VAL(dst))
|
|
free_obj_settings_list(dst);
|
|
if (!s)
|
|
return;
|
|
|
|
|
|
|
|
for (n = 0; s[n].name; n++)
|
|
/* NOP */;
|
|
d = talloc_array(NULL, struct m_obj_settings, n + 1);
|
|
for (n = 0; s[n].name; n++) {
|
|
d[n].name = talloc_strdup(NULL, s[n].name);
|
|
d[n].attribs = NULL;
|
|
copy_str_list(NULL, &(d[n].attribs), &(s[n].attribs));
|
|
}
|
|
d[n].name = NULL;
|
|
d[n].attribs = NULL;
|
|
VAL(dst) = d;
|
|
}
|
|
|
|
const m_option_type_t m_option_type_obj_settings_list = {
|
|
.name = "Object settings list",
|
|
.size = sizeof(m_obj_settings_t *),
|
|
.flags = M_OPT_TYPE_DYNAMIC | M_OPT_TYPE_ALLOW_WILDCARD,
|
|
.parse = parse_obj_settings_list,
|
|
.copy = copy_obj_settings_list,
|
|
.free = free_obj_settings_list,
|
|
};
|
|
|
|
|
|
|
|
static int parse_obj_presets(const m_option_t *opt, struct bstr name,
|
|
struct bstr param, void *dst)
|
|
{
|
|
m_obj_presets_t *obj_p = (m_obj_presets_t *)opt->priv;
|
|
const m_struct_t *in_desc;
|
|
const m_struct_t *out_desc;
|
|
int s, i;
|
|
const unsigned char *pre;
|
|
char *pre_name = NULL;
|
|
|
|
if (!obj_p) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: Presets need a "
|
|
"pointer to a m_obj_presets_t in the priv field.\n",
|
|
BSTR_P(name));
|
|
return M_OPT_PARSER_ERR;
|
|
}
|
|
|
|
if (param.len == 0)
|
|
return M_OPT_MISSING_PARAM;
|
|
|
|
pre = obj_p->presets;
|
|
in_desc = obj_p->in_desc;
|
|
out_desc = obj_p->out_desc ? obj_p->out_desc : obj_p->in_desc;
|
|
s = in_desc->size;
|
|
|
|
if (!bstrcmp0(param, "help")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available presets for %s->%.*s:",
|
|
out_desc->name, BSTR_P(name));
|
|
for (pre = obj_p->presets;
|
|
(pre_name = M_ST_MB(char *, pre, obj_p->name_off)); pre += s)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %s", pre_name);
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "\n");
|
|
return M_OPT_EXIT - 1;
|
|
}
|
|
|
|
for (pre_name = M_ST_MB(char *, pre, obj_p->name_off); pre_name;
|
|
pre += s, pre_name = M_ST_MB(char *, pre, obj_p->name_off))
|
|
if (!bstrcmp0(param, pre_name))
|
|
break;
|
|
if (!pre_name) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: There is no preset named %.*s\n"
|
|
"Available presets are:", BSTR_P(name), BSTR_P(param));
|
|
for (pre = obj_p->presets;
|
|
(pre_name = M_ST_MB(char *, pre, obj_p->name_off)); pre += s)
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, " %s", pre_name);
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "\n");
|
|
return M_OPT_INVALID;
|
|
}
|
|
|
|
if (!dst)
|
|
return 1;
|
|
|
|
for (i = 0; in_desc->fields[i].name; i++) {
|
|
const m_option_t *out_opt = m_option_list_find(out_desc->fields,
|
|
in_desc->fields[i].name);
|
|
if (!out_opt) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Unable to find the target option for field %s.\n"
|
|
"Please report this to the developers.\n",
|
|
BSTR_P(name), in_desc->fields[i].name);
|
|
return M_OPT_PARSER_ERR;
|
|
}
|
|
m_option_copy(out_opt, M_ST_MB_P(dst, out_opt->p),
|
|
M_ST_MB_P(pre, in_desc->fields[i].p));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
|
|
const m_option_type_t m_option_type_obj_presets = {
|
|
.name = "Object presets",
|
|
.parse = parse_obj_presets,
|
|
};
|
|
|
|
static int parse_custom_url(const m_option_t *opt, struct bstr name,
|
|
struct bstr url, void *dst)
|
|
{
|
|
int r;
|
|
m_struct_t *desc = opt->priv;
|
|
|
|
if (!desc) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: Custom URL needs "
|
|
"a pointer to a m_struct_t in the priv field.\n", BSTR_P(name));
|
|
return M_OPT_PARSER_ERR;
|
|
}
|
|
|
|
// extract the protocol
|
|
int idx = bstr_find0(url, "://");
|
|
if (idx < 0) {
|
|
// Filename only
|
|
if (m_option_list_find(desc->fields, "filename")) {
|
|
m_struct_set(desc, dst, "filename", url);
|
|
return 1;
|
|
}
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: URL doesn't have a valid protocol!\n",
|
|
BSTR_P(name));
|
|
return M_OPT_INVALID;
|
|
}
|
|
struct bstr ptr1 = bstr_cut(url, idx + 3);
|
|
if (m_option_list_find(desc->fields, "string")) {
|
|
if (ptr1.len > 0) {
|
|
m_struct_set(desc, dst, "string", ptr1);
|
|
return 1;
|
|
}
|
|
}
|
|
if (dst && m_option_list_find(desc->fields, "protocol")) {
|
|
r = m_struct_set(desc, dst, "protocol", bstr_splice(url, 0, idx));
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting protocol.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
|
|
// check if a username:password is given
|
|
idx = bstrchr(ptr1, '/');
|
|
if (idx < 0)
|
|
idx = ptr1.len;
|
|
struct bstr hostpart = bstr_splice(ptr1, 0, idx);
|
|
struct bstr path = bstr_cut(ptr1, idx);
|
|
idx = bstrchr(hostpart, '@');
|
|
if (idx >= 0) {
|
|
struct bstr userpass = bstr_splice(hostpart, 0, idx);
|
|
hostpart = bstr_cut(hostpart, idx + 1);
|
|
// We got something, at least a username...
|
|
if (!m_option_list_find(desc->fields, "username")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: This URL doesn't have a username part.\n",
|
|
BSTR_P(name));
|
|
// skip
|
|
} else {
|
|
idx = bstrchr(userpass, ':');
|
|
if (idx >= 0) {
|
|
// We also have a password
|
|
if (!m_option_list_find(desc->fields, "password")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: This URL doesn't have a password part.\n",
|
|
BSTR_P(name));
|
|
// skip
|
|
} else { // Username and password
|
|
if (dst) {
|
|
r = m_struct_set(desc, dst, "username",
|
|
bstr_splice(userpass, 0, idx));
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting username.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
r = m_struct_set(desc, dst, "password",
|
|
bstr_splice(userpass, idx+1,
|
|
userpass.len));
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting password.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
} else { // User name only
|
|
r = m_struct_set(desc, dst, "username", userpass);
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting username.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Before looking for a port number check if we have an IPv6 type
|
|
// numeric address.
|
|
// In an IPv6 URL the numeric address should be inside square braces.
|
|
int idx1 = bstrchr(hostpart, '[');
|
|
int idx2 = bstrchr(hostpart, ']');
|
|
struct bstr portstr = hostpart;
|
|
bool v6addr = false;
|
|
if (idx1 >= 0 && idx2 >= 0 && idx1 < idx2) {
|
|
// we have an IPv6 numeric address
|
|
portstr = bstr_cut(hostpart, idx2);
|
|
hostpart = bstr_splice(hostpart, idx1 + 1, idx2);
|
|
v6addr = true;
|
|
}
|
|
|
|
idx = bstrchr(portstr, ':');
|
|
if (idx >= 0) {
|
|
if (!v6addr)
|
|
hostpart = bstr_splice(hostpart, 0, idx);
|
|
// We have an URL beginning like http://www.hostname.com:1212
|
|
// Get the port number
|
|
if (!m_option_list_find(desc->fields, "port")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: This URL doesn't have a port part.\n",
|
|
BSTR_P(name));
|
|
// skip
|
|
} else {
|
|
if (dst) {
|
|
int p = bstrtoll(bstr_cut(portstr, idx + 1), NULL, 0);
|
|
char tmp[100];
|
|
snprintf(tmp, 99, "%d", p);
|
|
r = m_struct_set(desc, dst, "port", bstr0(tmp));
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting port.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Get the hostname
|
|
if (hostpart.len > 0) {
|
|
if (!m_option_list_find(desc->fields, "hostname")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: This URL doesn't have a hostname part.\n",
|
|
BSTR_P(name));
|
|
// skip
|
|
} else {
|
|
r = m_struct_set(desc, dst, "hostname", hostpart);
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting hostname.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
// Look if a path is given
|
|
if (path.len > 1) { // not just "/"
|
|
// copy the path/filename in the URL container
|
|
if (!m_option_list_find(desc->fields, "filename")) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_WARN,
|
|
"Option %.*s: This URL doesn't have a filename part.\n",
|
|
BSTR_P(name));
|
|
// skip
|
|
} else {
|
|
if (dst) {
|
|
char *fname = bstrdup0(NULL, bstr_cut(path, 1));
|
|
url_unescape_string(fname, fname);
|
|
r = m_struct_set(desc, dst, "filename", bstr0(fname));
|
|
talloc_free(fname);
|
|
if (r < 0) {
|
|
mp_msg(MSGT_CFGPARSER, MSGL_ERR,
|
|
"Option %.*s: Error while setting filename.\n",
|
|
BSTR_P(name));
|
|
return r;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/// TODO : Write the other needed funcs for 'normal' options
|
|
const m_option_type_t m_option_type_custom_url = {
|
|
.name = "Custom URL",
|
|
.parse = parse_custom_url,
|
|
};
|