From d4bdd0473d6f43132257c9fb3848d829755167a3 Mon Sep 17 00:00:00 2001 From: wm4 Date: Mon, 5 Nov 2012 17:02:04 +0100 Subject: Rename directories, move files (step 1 of 2) (does not compile) Tis drops the silly lib prefixes, and attempts to organize the tree in a more logical way. Make the top-level directory less cluttered as well. Renames the following directories: libaf -> audio/filter libao2 -> audio/out libvo -> video/out libmpdemux -> demux Split libmpcodecs: vf* -> video/filter vd*, dec_video.* -> video/decode mp_image*, img_format*, ... -> video/ ad*, dec_audio.* -> audio/decode libaf/format.* is moved to audio/ - this is similar to how mp_image.* is located in video/. Move most top-level .c/.h files to core. (talloc.c/.h is left on top- level, because it's external.) Park some of the more annoying files in compat/. Some of these are relicts from the time mplayer used ffmpeg internals. sub/ is not split, because it's too much of a mess (subtitle code is mixed with OSD display and rendering). Maybe the organization of core is not ideal: it mixes playback core (like mplayer.c) and utility helpers (like bstr.c/h). Should the need arise, the playback core will be moved somewhere else, while core contains all helper and common code. --- core/m_option.c | 2117 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2117 insertions(+) create mode 100644 core/m_option.c (limited to 'core/m_option.c') diff --git a/core/m_option.c b/core/m_option.c new file mode 100644 index 0000000000..3f300326ca --- /dev/null +++ b/core/m_option.c @@ -0,0 +1,2117 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "talloc.h" +#include "m_option.h" +#include "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); + 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, +}; + +#include "libmpcodecs/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, +}; + +#include "libaf/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, +}; + + +// Time or size (-endpos) + +static int parse_time_size(const m_option_t *opt, struct bstr name, + struct bstr param, void *dst) +{ + m_time_size_t ts; + char unit[4]; + double end_at; + + if (param.len == 0) + return M_OPT_MISSING_PARAM; + + ts.pos = 0; + /* End at size parsing */ + if (bstr_sscanf(param, "%lf%3s", &end_at, unit) == 2) { + ts.type = END_AT_SIZE; + if (!strcasecmp(unit, "b")) + ; + else if (!strcasecmp(unit, "kb")) + end_at *= 1024; + else if (!strcasecmp(unit, "mb")) + end_at *= 1024 * 1024; + else if (!strcasecmp(unit, "gb")) + end_at *= 1024 * 1024 * 1024; + else + ts.type = END_AT_NONE; + + if (ts.type == END_AT_SIZE) { + ts.pos = end_at; + goto out; + } + } + + /* End at time parsing. This has to be last because the parsing accepts + * even a number followed by garbage */ + if (!parse_timestring(param, &end_at, 0)) { + mp_msg(MSGT_CFGPARSER, MSGL_ERR, + "Option %.*s: invalid time or size: '%.*s'\n", + BSTR_P(name), BSTR_P(param)); + return M_OPT_INVALID; + } + + ts.type = END_AT_TIME; + ts.pos = end_at; +out: + if (dst) + *(m_time_size_t *)dst = ts; + return 1; +} + +const m_option_type_t m_option_type_time_size = { + .name = "Time or size", + .size = sizeof(m_time_size_t), + .parse = parse_time_size, + .copy = copy_opt, +}; + + +//// Objects (i.e. filters, etc) settings + +#include "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, +}; -- cgit v1.2.3