1
0
mirror of https://github.com/mpv-player/mpv synced 2024-12-29 18:42:09 +00:00
mpv/core/m_option.c
wm4 8751a0e261 video: decouple internal pixel formats from FourCCs
mplayer's video chain traditionally used FourCCs for pixel formats. For
example, it used IMGFMT_YV12 for 4:2:0 YUV, which was defined to the
string 'YV12' interpreted as unsigned int. Additionally, it used to
encode information into the numeric values of some formats. The RGB
formats had their bit depth and endian encoded into the least
significant byte. Extended planar formats (420P10 etc.) had chroma
shift, endian, and component bit depth encoded. (This has been removed
in recent commits.)

Replace the FourCC mess with a simple enum. Remove all the redundant
formats like YV12/I420/IYUV. Replace some image format names by
something more intuitive, most importantly IMGFMT_YV12 -> IMGFMT_420P.

Add img_fourcc.h, which contains the old IDs for code that actually uses
FourCCs. Change the way demuxers, that output raw video, identify the
video format: they set either MP_FOURCC_RAWVIDEO or MP_FOURCC_IMGFMT to
request the rawvideo decoder, and sh_video->imgfmt specifies the pixel
format. Like the previous hack, this is supposed to avoid the need for
a complete codecs.cfg entry per format, or other lookup tables. (Note
that the RGB raw video FourCCs mostly rely on ffmpeg's mappings for NUT
raw video, but this is still considered better than adding a raw video
decoder - even if trivial, it would be full of annoying lookup tables.)

The TV code has not been tested.

Some corrective changes regarding endian and other image format flags
creep in.
2013-01-13 20:04:11 +01:00

2201 lines
61 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("Unrecognized option name");
case M_OPT_MISSING_PARAM:
return mp_gtext("Required parameter for option missing");
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_PARSER_ERR:
return mp_gtext("Parser error");
default:
return NULL;
}
}
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_OLD_SYNTAX_NO_PARAM,
.parse = parse_flag,
.print = print_flag,
.copy = copy_opt,
.add = add_flag,
.clamp = clamp_flag,
};
// 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_OLD_SYNTAX_NO_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_OLD_SYNTAX_NO_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,
};
#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), &param);
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), &param, 10);
if (param.len == 0 && chapter >= 1) {
t.type = REL_TIME_CHAPTER;
t.pos = chapter - 1;
goto out;
}
}
bool sign = bstr_eatstart0(&param, "-");
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,
};