aboutsummaryrefslogtreecommitdiffhomepage
path: root/options
diff options
context:
space:
mode:
authorGravatar wm4 <wm4@nowhere>2013-12-17 02:02:25 +0100
committerGravatar wm4 <wm4@nowhere>2013-12-17 02:07:57 +0100
commiteb15151705d47d23da844449126cc6b4879f110e (patch)
tree661866ee9e73bc893c7963660aae01db503e99aa /options
parent793f85945fc07db905e54390c065ae51b9eeef9b (diff)
Move options/config related files from mpvcore/ to options/
Since m_option.h and options.h are extremely often included, a lot of files have to be changed. Moving path.c/h to options/ is a bit questionable, but since this is mainly about access to config files (which are also handled in options/), it's probably ok.
Diffstat (limited to 'options')
-rw-r--r--options/m_config.c755
-rw-r--r--options/m_config.h216
-rw-r--r--options/m_option.c2477
-rw-r--r--options/m_option.h648
-rw-r--r--options/m_property.c382
-rw-r--r--options/m_property.h142
-rw-r--r--options/options.c886
-rw-r--r--options/options.h297
-rw-r--r--options/parse_commandline.c294
-rw-r--r--options/parse_commandline.h33
-rw-r--r--options/parse_configfile.c277
-rw-r--r--options/parse_configfile.h27
-rw-r--r--options/path.c238
-rw-r--r--options/path.h75
14 files changed, 6747 insertions, 0 deletions
diff --git a/options/m_config.c b/options/m_config.c
new file mode 100644
index 0000000000..5040482c84
--- /dev/null
+++ b/options/m_config.c
@@ -0,0 +1,755 @@
+/*
+ * 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 Config
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "talloc.h"
+
+#include "m_config.h"
+#include "options/m_option.h"
+#include "mpvcore/mp_msg.h"
+
+static const union m_option_value default_value;
+
+// Profiles allow to predefine some sets of options that can then
+// be applied later on with the internal -profile option.
+#define MAX_PROFILE_DEPTH 20
+
+struct m_profile {
+ struct m_profile *next;
+ char *name;
+ char *desc;
+ int num_opts;
+ // Option/value pair array.
+ char **opts;
+};
+
+// In the file local case, this contains the old global value.
+struct m_opt_backup {
+ struct m_opt_backup *next;
+ struct m_config_option *co;
+ void *backup;
+};
+
+static int parse_include(struct m_config *config, struct bstr param, bool set,
+ int flags)
+{
+ if (param.len == 0)
+ return M_OPT_MISSING_PARAM;
+ if (!set)
+ return 1;
+ char *filename = bstrdup0(NULL, param);
+ config->includefunc(config, filename, flags);
+ talloc_free(filename);
+ return 1;
+}
+
+static int parse_profile(struct m_config *config, const struct m_option *opt,
+ struct bstr name, struct bstr param, bool set, int flags)
+{
+ if (!bstrcmp0(param, "help")) {
+ struct m_profile *p;
+ if (!config->profiles) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO,
+ "No profiles have been defined.\n");
+ return M_OPT_EXIT - 1;
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available profiles:\n");
+ for (p = config->profiles; p; p = p->next)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\t%s\t%s\n", p->name,
+ p->desc ? p->desc : "");
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ char **list = NULL;
+ int r = m_option_type_string_list.parse(opt, name, param, &list);
+ if (r < 0)
+ return r;
+ if (!list || !list[0])
+ return M_OPT_INVALID;
+ for (int i = 0; list[i]; i++) {
+ struct m_profile *p = m_config_get_profile0(config, list[i]);
+ if (!p) {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Unknown profile '%s'.\n",
+ list[i]);
+ r = M_OPT_INVALID;
+ } else if (set)
+ m_config_set_profile(config, p, flags);
+ }
+ m_option_free(opt, &list);
+ return r;
+}
+
+static int show_profile(struct m_config *config, bstr param)
+{
+ struct m_profile *p;
+ int i, j;
+ if (!param.len)
+ return M_OPT_MISSING_PARAM;
+ if (!(p = m_config_get_profile(config, param))) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Unknown profile '%.*s'.\n",
+ BSTR_P(param));
+ return M_OPT_EXIT - 1;
+ }
+ if (!config->profile_depth)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Profile %s: %s\n", p->name,
+ p->desc ? p->desc : "");
+ config->profile_depth++;
+ for (i = 0; i < p->num_opts; i++) {
+ char spc[config->profile_depth + 1];
+ for (j = 0; j < config->profile_depth; j++)
+ spc[j] = ' ';
+ spc[config->profile_depth] = '\0';
+
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s%s=%s\n", spc,
+ p->opts[2 * i], p->opts[2 * i + 1]);
+
+ if (config->profile_depth < MAX_PROFILE_DEPTH
+ && !strcmp(p->opts[2*i], "profile")) {
+ char *e, *list = p->opts[2 * i + 1];
+ while ((e = strchr(list, ','))) {
+ int l = e - list;
+ char tmp[l+1];
+ if (!l)
+ continue;
+ memcpy(tmp, list, l);
+ tmp[l] = '\0';
+ show_profile(config, bstr0(tmp));
+ list = e + 1;
+ }
+ if (list[0] != '\0')
+ show_profile(config, bstr0(list));
+ }
+ }
+ config->profile_depth--;
+ if (!config->profile_depth)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+}
+
+static int list_options(struct m_config *config)
+{
+ m_config_print_option_list(config);
+ return M_OPT_EXIT;
+}
+
+// The memcpys are supposed to work around the strict aliasing violation,
+// that would result if we just dereferenced a void** (where the void** is
+// actually casted from struct some_type* ).
+static void *substruct_read_ptr(const void *ptr)
+{
+ void *res;
+ memcpy(&res, ptr, sizeof(void*));
+ return res;
+}
+static void substruct_write_ptr(void *ptr, void *val)
+{
+ memcpy(ptr, &val, sizeof(void*));
+}
+
+static void add_options(struct m_config *config,
+ const char *parent_name,
+ void *optstruct,
+ const void *optstruct_def,
+ const struct m_option *defs);
+
+static void config_destroy(void *p)
+{
+ struct m_config *config = p;
+ m_config_restore_backups(config);
+ for (int n = 0; n < config->num_opts; n++)
+ m_option_free(config->opts[n].opt, config->opts[n].data);
+}
+
+struct m_config *m_config_new(void *talloc_ctx, size_t size,
+ const void *defaults,
+ const struct m_option *options)
+{
+ struct m_config *config = talloc(talloc_ctx, struct m_config);
+ talloc_set_destructor(config, config_destroy);
+ *config = (struct m_config) {0};
+ // size==0 means a dummy object is created
+ if (size) {
+ config->optstruct = talloc_zero_size(config, size);
+ if (defaults)
+ memcpy(config->optstruct, defaults, size);
+ }
+ if (options)
+ add_options(config, "", config->optstruct, defaults, options);
+ return config;
+}
+
+struct m_config *m_config_from_obj_desc(void *talloc_ctx,
+ struct m_obj_desc *desc)
+{
+ return m_config_new(talloc_ctx, desc->priv_size, desc->priv_defaults,
+ desc->options);
+}
+
+// Like m_config_from_obj_desc(), but don't allocate option struct.
+struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
+ struct m_obj_desc *desc)
+{
+ return m_config_new(talloc_ctx, 0, desc->priv_defaults, desc->options);
+}
+
+int m_config_set_obj_params(struct m_config *conf, char **args)
+{
+ for (int n = 0; args && args[n * 2 + 0]; n++) {
+ int r = m_config_set_option(conf, bstr0(args[n * 2 + 0]),
+ bstr0(args[n * 2 + 1]));
+ if (r < 0)
+ return r;
+ }
+ return 0;
+}
+
+int m_config_apply_defaults(struct m_config *config, const char *name,
+ struct m_obj_settings *defaults)
+{
+ int r = 0;
+ for (int n = 0; defaults && defaults[n].name; n++) {
+ struct m_obj_settings *entry = &defaults[n];
+ if (name && strcmp(entry->name, name) == 0) {
+ r = m_config_set_obj_params(config, entry->attribs);
+ break;
+ }
+ }
+ return r;
+}
+
+static void ensure_backup(struct m_config *config, struct m_config_option *co)
+{
+ if (co->opt->type->flags & M_OPT_TYPE_HAS_CHILD)
+ return;
+ if (co->opt->flags & M_OPT_GLOBAL)
+ return;
+ if (!co->data)
+ return;
+ for (struct m_opt_backup *cur = config->backup_opts; cur; cur = cur->next) {
+ if (cur->co->data == co->data) // comparing data ptr catches aliases
+ return;
+ }
+ struct m_opt_backup *bc = talloc_ptrtype(NULL, bc);
+ *bc = (struct m_opt_backup) {
+ .co = co,
+ .backup = talloc_zero_size(bc, co->opt->type->size),
+ };
+ m_option_copy(co->opt, bc->backup, co->data);
+ bc->next = config->backup_opts;
+ config->backup_opts = bc;
+}
+
+void m_config_restore_backups(struct m_config *config)
+{
+ while (config->backup_opts) {
+ struct m_opt_backup *bc = config->backup_opts;
+ config->backup_opts = bc->next;
+
+ m_option_copy(bc->co->opt, bc->co->data, bc->backup);
+ m_option_free(bc->co->opt, bc->backup);
+ talloc_free(bc);
+ }
+}
+
+void m_config_backup_opt(struct m_config *config, const char *opt)
+{
+ struct m_config_option *co = m_config_get_co(config, bstr0(opt));
+ if (co) {
+ ensure_backup(config, co);
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %s not found.\n", opt);
+ }
+}
+
+void m_config_backup_all_opts(struct m_config *config)
+{
+ for (int n = 0; n < config->num_opts; n++)
+ ensure_backup(config, &config->opts[n]);
+}
+
+// Given an option --opt, add --no-opt (if applicable).
+static void add_negation_option(struct m_config *config,
+ struct m_config_option *orig,
+ const char *parent_name)
+{
+ const struct m_option *opt = orig->opt;
+ int value;
+ if (opt->type == CONF_TYPE_FLAG) {
+ value = opt->min;
+ } else if (opt->type == CONF_TYPE_CHOICE) {
+ // Find out whether there's a "no" choice.
+ // m_option_parse() should be used for this, but it prints
+ // unsilenceable error messages.
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for ( ; alt->name; alt++) {
+ if (strcmp(alt->name, "no") == 0)
+ break;
+ }
+ if (!alt->name)
+ return;
+ value = alt->value;
+ } else {
+ return;
+ }
+ struct m_option *no_opt = talloc_ptrtype(config, no_opt);
+ *no_opt = (struct m_option) {
+ .name = opt->name,
+ .type = CONF_TYPE_STORE,
+ .flags = opt->flags & (M_OPT_NOCFG | M_OPT_GLOBAL | M_OPT_PRE_PARSE),
+ .is_new_option = opt->is_new_option,
+ .p = opt->p,
+ .offset = opt->offset,
+ .max = value,
+ };
+ // Add --no-sub-opt
+ struct m_config_option co = *orig;
+ co.name = talloc_asprintf(config, "no-%s", orig->name);
+ co.opt = no_opt;
+ co.is_generated = true;
+ MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
+ // Add --sub-no-opt (unfortunately needed for: "--sub=...:no-opt")
+ if (parent_name[0]) {
+ co.name = talloc_asprintf(config, "%s-no-%s", parent_name, opt->name);
+ MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
+ }
+}
+
+static void m_config_add_option(struct m_config *config,
+ const char *parent_name,
+ void *optstruct,
+ const void *optstruct_def,
+ const struct m_option *arg);
+
+static void add_options(struct m_config *config,
+ const char *parent_name,
+ void *optstruct,
+ const void *optstruct_def,
+ const struct m_option *defs)
+{
+ for (int i = 0; defs && defs[i].name; i++)
+ m_config_add_option(config, parent_name, optstruct, optstruct_def, &defs[i]);
+}
+
+static void m_config_add_option(struct m_config *config,
+ const char *parent_name,
+ void *optstruct,
+ const void *optstruct_def,
+ const struct m_option *arg)
+{
+ assert(config != NULL);
+ assert(arg != NULL);
+
+ struct m_config_option co = {
+ .opt = arg,
+ .name = arg->name,
+ };
+
+ if (arg->is_new_option) {
+ if (optstruct)
+ co.data = (char *)optstruct + arg->offset;
+ if (optstruct_def)
+ co.default_data = (char *)optstruct_def + arg->offset;
+ } else {
+ co.data = arg->p;
+ co.default_data = arg->p;
+ }
+
+ if (arg->defval)
+ co.default_data = arg->defval;
+
+ if (!co.default_data)
+ co.default_data = &default_value;
+
+ // Fill in the full name
+ if (!co.name[0]) {
+ co.name = parent_name;
+ } else if (parent_name[0]) {
+ co.name = talloc_asprintf(config, "%s-%s", parent_name, co.name);
+ }
+
+ // Option with children -> add them
+ if (arg->type->flags & M_OPT_TYPE_HAS_CHILD) {
+ if (arg->type->flags & M_OPT_TYPE_USE_SUBSTRUCT) {
+ const struct m_sub_options *subopts = arg->priv;
+
+ void *new_optstruct = NULL;
+ if (co.data) {
+ new_optstruct = m_config_alloc_struct(config, subopts);
+ substruct_write_ptr(co.data, new_optstruct);
+ }
+
+ const void *new_optstruct_def = substruct_read_ptr(co.default_data);
+ if (!new_optstruct_def)
+ new_optstruct_def = subopts->defaults;
+
+ add_options(config, co.name, new_optstruct,
+ new_optstruct_def, subopts->opts);
+ } else {
+ const struct m_option *sub = arg->p;
+ add_options(config, co.name, optstruct, optstruct_def, sub);
+ }
+ } else {
+ // Initialize options
+ if (co.data && co.default_data) {
+ if (arg->type->flags & M_OPT_TYPE_DYNAMIC) {
+ // Would leak memory by overwriting *co.data repeatedly.
+ for (int i = 0; i < config->num_opts; i++) {
+ if (co.data == config->opts[i].data)
+ assert(0);
+ }
+ }
+ // In case this is dynamic data, it has to be allocated and copied.
+ union m_option_value temp = {0};
+ memcpy(&temp, co.default_data, arg->type->size);
+ memset(co.data, 0, arg->type->size);
+ m_option_copy(arg, co.data, &temp);
+ }
+ }
+
+ if (arg->name[0]) // no own name -> hidden
+ MP_TARRAY_APPEND(config, config->opts, config->num_opts, co);
+
+ add_negation_option(config, &co, parent_name);
+}
+
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name)
+{
+
+ for (int n = 0; n < config->num_opts; n++) {
+ struct m_config_option *co = &config->opts[n];
+ struct bstr coname = bstr0(co->name);
+ if ((co->opt->type->flags & M_OPT_TYPE_ALLOW_WILDCARD)
+ && bstr_endswith0(coname, "*")) {
+ coname.len--;
+ if (bstrcmp(bstr_splice(name, 0, coname.len), coname) == 0)
+ return co;
+ } else if (bstrcmp(coname, name) == 0)
+ return co;
+ }
+ return NULL;
+}
+
+const char *m_config_get_positional_option(const struct m_config *config, int p)
+{
+ int pos = 0;
+ for (int n = 0; n < config->num_opts; n++) {
+ struct m_config_option *co = &config->opts[n];
+ if (!co->is_generated) {
+ if (pos == p)
+ return co->name;
+ pos++;
+ }
+ }
+ return NULL;
+}
+
+static int parse_subopts(struct m_config *config, char *name, char *prefix,
+ struct bstr param, int flags);
+
+static int m_config_parse_option(struct m_config *config, struct bstr name,
+ struct bstr param, int flags)
+{
+ assert(config != NULL);
+ assert(name.len != 0);
+ bool set = !(flags & M_SETOPT_CHECK_ONLY);
+
+ struct m_config_option *co = m_config_get_co(config, name);
+ if (!co)
+ return M_OPT_UNKNOWN;
+
+ // This is the only mandatory function
+ assert(co->opt->type->parse);
+
+ if ((flags & M_SETOPT_PRE_PARSE_ONLY) && !(co->opt->flags & M_OPT_PRE_PARSE))
+ return 0;
+
+ if ((flags & M_SETOPT_PRESERVE_CMDLINE) && co->is_set_from_cmdline)
+ set = false;
+
+ // Check if this option isn't forbidden in the current mode
+ if ((flags & M_SETOPT_FROM_CONFIG_FILE) && (co->opt->flags & M_OPT_NOCFG)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option can't be used in a config file.\n",
+ BSTR_P(name));
+ return M_OPT_INVALID;
+ }
+ if (flags & M_SETOPT_BACKUP) {
+ if (co->opt->flags & M_OPT_GLOBAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option is global and can't be set per-file.\n",
+ BSTR_P(name));
+ return M_OPT_INVALID;
+ }
+ if (set)
+ ensure_backup(config, co);
+ }
+
+ if (config->includefunc && bstr_equals0(name, "include"))
+ return parse_include(config, param, set, flags);
+ if (config->use_profiles && bstr_equals0(name, "profile"))
+ return parse_profile(config, co->opt, name, param, set, flags);
+ if (config->use_profiles && bstr_equals0(name, "show-profile"))
+ return show_profile(config, param);
+ if (bstr_equals0(name, "list-options"))
+ return list_options(config);
+
+ // Option with children are a bit different to parse
+ if (co->opt->type->flags & M_OPT_TYPE_HAS_CHILD) {
+ char prefix[110];
+ assert(strlen(co->name) < 100);
+ sprintf(prefix, "%s-", co->name);
+ return parse_subopts(config, (char *)co->name, prefix, param, flags);
+ }
+
+ int r = m_option_parse(co->opt, name, param, set ? co->data : NULL);
+
+ if (r >= 0 && set && (flags & M_SETOPT_FROM_CMDLINE)) {
+ co->is_set_from_cmdline = true;
+ // Mark aliases too
+ if (co->data) {
+ for (int n = 0; n < config->num_opts; n++) {
+ struct m_config_option *co2 = &config->opts[n];
+ if (co2->data == co->data)
+ co2->is_set_from_cmdline = true;
+ }
+ }
+ }
+
+ return r;
+}
+
+static int parse_subopts(struct m_config *config, char *name, char *prefix,
+ struct bstr param, int flags)
+{
+ char **lst = NULL;
+ // Split the argument into child options
+ int r = m_option_type_subconfig.parse(NULL, bstr0(""), param, &lst);
+ if (r < 0)
+ return r;
+ // Parse the child options
+ for (int i = 0; lst && lst[2 * i]; i++) {
+ // Build the full name
+ char n[110];
+ if (snprintf(n, 110, "%s%s", prefix, lst[2 * i]) > 100)
+ abort();
+ r = m_config_parse_option(config,bstr0(n), bstr0(lst[2 * i + 1]), flags);
+ if (r < 0) {
+ if (r > M_OPT_EXIT) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Error parsing suboption %s/%s (%s)\n",
+ name, lst[2 * i], m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ break;
+ }
+ }
+ talloc_free(lst);
+ return r;
+}
+
+int m_config_parse_suboptions(struct m_config *config, char *name,
+ char *subopts)
+{
+ if (!subopts || !*subopts)
+ return 0;
+ int r = parse_subopts(config, name, "", bstr0(subopts), 0);
+ if (r < 0 && r > M_OPT_EXIT) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Error parsing suboption %s (%s)\n",
+ name, m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ return r;
+}
+
+int m_config_set_option_ext(struct m_config *config, struct bstr name,
+ struct bstr param, int flags)
+{
+ int r = m_config_parse_option(config, name, param, flags);
+ if (r < 0 && r > M_OPT_EXIT) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Error parsing option %.*s (%s)\n",
+ BSTR_P(name), m_option_strerror(r));
+ r = M_OPT_INVALID;
+ }
+ return r;
+}
+
+int m_config_set_option(struct m_config *config, struct bstr name,
+ struct bstr param)
+{
+ return m_config_set_option_ext(config, name, param, 0);
+}
+
+const struct m_option *m_config_get_option(const struct m_config *config,
+ struct bstr name)
+{
+ assert(config != NULL);
+
+ struct m_config_option *co = m_config_get_co(config, name);
+ return co ? co->opt : NULL;
+}
+
+int m_config_option_requires_param(struct m_config *config, bstr name)
+{
+ const struct m_option *opt = m_config_get_option(config, name);
+ if (opt) {
+ if (bstr_endswith0(name, "-clr"))
+ return 0;
+ return m_option_required_params(opt);
+ }
+ return M_OPT_UNKNOWN;
+}
+
+void m_config_print_option_list(const struct m_config *config)
+{
+ char min[50], max[50];
+ int count = 0;
+ const char *prefix = config->is_toplevel ? "--" : "";
+
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Options:\n\n");
+ for (int i = 0; i < config->num_opts; i++) {
+ struct m_config_option *co = &config->opts[i];
+ const struct m_option *opt = co->opt;
+ if (opt->type->flags & M_OPT_TYPE_HAS_CHILD)
+ continue;
+ if (co->is_generated)
+ continue;
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s%-30.30s", prefix, co->name);
+ if (opt->type == &m_option_type_choice) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " Choices:");
+ struct m_opt_choice_alternatives *alt = opt->priv;
+ for (int n = 0; alt[n].name; n++)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", alt[n].name);
+ if (opt->flags & (M_OPT_MIN | M_OPT_MAX))
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (or an integer)");
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %s", co->opt->type->name);
+ }
+ if (opt->flags & (M_OPT_MIN | M_OPT_MAX)) {
+ snprintf(min, sizeof(min), "any");
+ snprintf(max, sizeof(max), "any");
+ if (opt->flags & M_OPT_MIN)
+ snprintf(min, sizeof(min), "%.14g", opt->min);
+ if (opt->flags & M_OPT_MAX)
+ snprintf(max, sizeof(max), "%.14g", opt->max);
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (%s to %s)", min, max);
+ }
+ char *def = NULL;
+ if (co->default_data)
+ def = m_option_print(co->opt, co->default_data);
+ if (def) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " (default: %s)", def);
+ talloc_free(def);
+ }
+ if (opt->flags & CONF_GLOBAL)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " [global]");
+ if (opt->flags & CONF_NOCFG)
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " [nocfg]");
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ count++;
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\nTotal: %d options\n", count);
+}
+
+struct m_profile *m_config_get_profile(const struct m_config *config, bstr name)
+{
+ for (struct m_profile *p = config->profiles; p; p = p->next) {
+ if (bstr_equals0(name, p->name))
+ return p;
+ }
+ return NULL;
+}
+
+struct m_profile *m_config_get_profile0(const struct m_config *config,
+ char *name)
+{
+ return m_config_get_profile(config, bstr0(name));
+}
+
+struct m_profile *m_config_add_profile(struct m_config *config, char *name)
+{
+ struct m_profile *p = m_config_get_profile0(config, name);
+ if (p)
+ return p;
+ p = talloc_zero(config, struct m_profile);
+ p->name = talloc_strdup(p, name);
+ p->next = config->profiles;
+ config->profiles = p;
+ return p;
+}
+
+void m_profile_set_desc(struct m_profile *p, bstr desc)
+{
+ talloc_free(p->desc);
+ p->desc = bstrdup0(p, desc);
+}
+
+int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
+ bstr name, bstr val)
+{
+ int i = m_config_set_option_ext(config, name, val,
+ M_SETOPT_CHECK_ONLY |
+ M_SETOPT_FROM_CONFIG_FILE);
+ if (i < 0)
+ return i;
+ p->opts = talloc_realloc(p, p->opts, char *, 2 * (p->num_opts + 2));
+ p->opts[p->num_opts * 2] = bstrdup0(p, name);
+ p->opts[p->num_opts * 2 + 1] = bstrdup0(p, val);
+ p->num_opts++;
+ p->opts[p->num_opts * 2] = p->opts[p->num_opts * 2 + 1] = NULL;
+ return 1;
+}
+
+void m_config_set_profile(struct m_config *config, struct m_profile *p,
+ int flags)
+{
+ if (config->profile_depth > MAX_PROFILE_DEPTH) {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "WARNING: Profile inclusion too deep.\n");
+ return;
+ }
+ config->profile_depth++;
+ for (int i = 0; i < p->num_opts; i++) {
+ m_config_set_option_ext(config,
+ bstr0(p->opts[2 * i]),
+ bstr0(p->opts[2 * i + 1]),
+ flags | M_SETOPT_FROM_CONFIG_FILE);
+ }
+ config->profile_depth--;
+}
+
+void *m_config_alloc_struct(void *talloc_ctx,
+ const struct m_sub_options *subopts)
+{
+ void *substruct = talloc_zero_size(talloc_ctx, subopts->size);
+ if (subopts->defaults)
+ memcpy(substruct, subopts->defaults, subopts->size);
+ return substruct;
+}
diff --git a/options/m_config.h b/options/m_config.h
new file mode 100644
index 0000000000..8a8865d68e
--- /dev/null
+++ b/options/m_config.h
@@ -0,0 +1,216 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_CONFIG_H
+#define MPLAYER_M_CONFIG_H
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "mpvcore/bstr.h"
+
+// m_config provides an API to manipulate the config variables in MPlayer.
+// It makes use of the Options API to provide a context stack that
+// allows saving and later restoring the state of all variables.
+
+typedef struct m_profile m_profile_t;
+struct m_option;
+struct m_option_type;
+struct m_sub_options;
+struct m_obj_desc;
+struct m_obj_settings;
+
+// Config option
+struct m_config_option {
+ bool is_generated : 1; // Automatically added ("no-" options)
+ bool is_set_from_cmdline : 1; // Set by user from command line
+ const char *name; // Full name (ie option-subopt)
+ const struct m_option *opt; // Option description
+ void *data; // Raw value of the option
+ const void *default_data; // Raw default value
+};
+
+// Config object
+/** \ingroup Config */
+typedef struct m_config {
+ // Registered options.
+ struct m_config_option *opts; // all options, even suboptions
+ int num_opts;
+
+ // List of defined profiles.
+ struct m_profile *profiles;
+ // Depth when recursively including profiles.
+ int profile_depth;
+
+ struct m_opt_backup *backup_opts;
+
+ bool use_profiles;
+ bool is_toplevel;
+ int (*includefunc)(struct m_config *conf, char *filename, int flags);
+
+ void *optstruct; // struct mpopts or other
+} m_config_t;
+
+// Create a new config object.
+// talloc_ctx: talloc parent context for the m_config allocation
+// size: size of the optstruct (where option values are stored)
+// size==0 creates a config object with no option struct allocated
+// defaults: if not NULL, points to a struct of same type as optstruct, which
+// contains default values for all options
+// options: list of options. Each option defines a member of the optstruct
+// and a corresponding option switch or sub-option field.
+// suboptinit: if not NULL, initialize the suboption string (used for presets)
+// Note that the m_config object will keep pointers to defaults and options.
+struct m_config *m_config_new(void *talloc_ctx, size_t size,
+ const void *defaults,
+ const struct m_option *options);
+
+struct m_config *m_config_from_obj_desc(void *talloc_ctx,
+ struct m_obj_desc *desc);
+
+struct m_config *m_config_from_obj_desc_noalloc(void *talloc_ctx,
+ struct m_obj_desc *desc);
+
+int m_config_set_obj_params(struct m_config *conf, char **args);
+
+// Search for the object with the given name in the defaults list, and apply
+// its parameters.
+int m_config_apply_defaults(struct m_config *config, const char *name,
+ struct m_obj_settings *defaults);
+
+// Make sure the option is backed up. If it's already backed up, do nothing.
+// All backed up options can be restored with m_config_restore_backups().
+void m_config_backup_opt(struct m_config *config, const char *opt);
+
+// Call m_config_backup_opt() on all options.
+void m_config_backup_all_opts(struct m_config *config);
+
+// Restore all options backed up with m_config_backup_opt(), and delete the
+// backups afterwards.
+void m_config_restore_backups(struct m_config *config);
+
+enum {
+ M_SETOPT_PRE_PARSE_ONLY = 1, // Silently ignore non-M_OPT_PRE_PARSE opt.
+ M_SETOPT_CHECK_ONLY = 2, // Don't set, just check name/value
+ M_SETOPT_FROM_CONFIG_FILE = 4, // Reject M_OPT_NOCFG opt. (print error)
+ M_SETOPT_FROM_CMDLINE = 8, // Mark as set by command line
+ M_SETOPT_BACKUP = 16, // Call m_config_backup_opt() before
+ M_SETOPT_PRESERVE_CMDLINE = 32, // Don't set if already marked as FROM_CMDLINE
+};
+
+// Set the named option to the given string.
+// flags: combination of M_SETOPT_* flags (0 for normal operation)
+// Returns >= 0 on success, otherwise see OptionParserReturn.
+int m_config_set_option_ext(struct m_config *config, struct bstr name,
+ struct bstr param, int flags);
+
+/* Set an option. (Like: m_config_set_option_ext(config, name, param, 0))
+ * \param config The config object.
+ * \param name The option's name.
+ * \param param The value of the option, can be NULL.
+ * \return See \ref OptionParserReturn.
+ */
+int m_config_set_option(struct m_config *config, struct bstr name,
+ struct bstr param);
+
+static inline int m_config_set_option0(struct m_config *config,
+ const char *name, const char *param)
+{
+ return m_config_set_option(config, bstr0(name), bstr0(param));
+}
+
+int m_config_parse_suboptions(struct m_config *config, char *name,
+ char *subopts);
+
+
+/* Get the option matching the given name.
+ * \param config The config object.
+ * \param name The option's name.
+ */
+const struct m_option *m_config_get_option(const struct m_config *config,
+ struct bstr name);
+
+struct m_config_option *m_config_get_co(const struct m_config *config,
+ struct bstr name);
+
+// Return the n-th option by position. n==0 is the first option. If there are
+// less than (n + 1) options, return NULL.
+const char *m_config_get_positional_option(const struct m_config *config, int n);
+
+// Return a hint to the option parser whether a parameter is/may be required.
+// The option may still accept empty/non-empty parameters independent from
+// this, and this function is useful only for handling ambiguous options like
+// flags (e.g. "--a" is ok, "--a=yes" is also ok).
+// Returns: error code (<0), or number of expected params (0, 1)
+int m_config_option_requires_param(struct m_config *config, bstr name);
+
+/* Print a list of all registered options.
+ * \param config The config object.
+ */
+void m_config_print_option_list(const struct m_config *config);
+
+
+/* Find the profile with the given name.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object or NULL.
+ */
+struct m_profile *m_config_get_profile0(const struct m_config *config,
+ char *name);
+struct m_profile *m_config_get_profile(const struct m_config *config, bstr name);
+
+/* Get the profile with the given name, creating it if necessary.
+ * \param config The config object.
+ * \param arg The profile's name.
+ * \return The profile object.
+ */
+struct m_profile *m_config_add_profile(struct m_config *config, char *name);
+
+/* Set the description of a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param p The profile object.
+ * \param arg The profile's name.
+ */
+void m_profile_set_desc(struct m_profile *p, bstr desc);
+
+/* Add an option to a profile.
+ * Used by the config file parser when defining a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param name The option's name.
+ * \param val The option's value.
+ */
+int m_config_set_profile_option(struct m_config *config, struct m_profile *p,
+ bstr name, bstr val);
+
+/* Enables profile usage
+ * Used by the config file parser when loading a profile.
+ *
+ * \param config The config object.
+ * \param p The profile object.
+ * \param flags M_SETOPT_* bits
+ */
+void m_config_set_profile(struct m_config *config, struct m_profile *p,
+ int flags);
+
+void *m_config_alloc_struct(void *talloc_ctx,
+ const struct m_sub_options *subopts);
+
+#endif /* MPLAYER_M_CONFIG_H */
diff --git a/options/m_option.c b/options/m_option.c
new file mode 100644
index 0000000000..b4bd50a23f
--- /dev/null
+++ b/options/m_option.c
@@ -0,0 +1,2477 @@
+/*
+ * 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 <ctype.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+#include <libavutil/avstring.h>
+
+#include "talloc.h"
+#include "mpvcore/mp_common.h"
+#include "mpvcore/mp_msg.h"
+#include "m_option.h"
+#include "m_config.h"
+
+char *m_option_strerror(int code)
+{
+ switch (code) {
+ case M_OPT_UNKNOWN:
+ return "option not found";
+ case M_OPT_MISSING_PARAM:
+ return "option requires parameter";
+ case M_OPT_INVALID:
+ return "option parameter could not be parsed";
+ case M_OPT_OUT_OF_RANGE:
+ return "parameter is outside values allowed for option";
+ case M_OPT_DISALLOW_PARAM:
+ return "option doesn't take a parameter";
+ case M_OPT_PARSER_ERR:
+ default:
+ return "parser error";
+ }
+}
+
+int m_option_required_params(const m_option_t *opt)
+{
+ if (((opt->flags & M_OPT_OPTIONAL_PARAM) ||
+ (opt->type->flags & M_OPT_TYPE_OPTIONAL_PARAM)))
+ return 0;
+ return 1;
+}
+
+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 (bstrcmp(bstr_splice(name, 0, lname.len), lname) == 0)
+ return &list[i];
+ } else if (bstrcmp(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 (!bstrcmp0(param, "yes")) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 1;
+ }
+ if (!bstrcmp0(param, "no")) {
+ if (dst)
+ VAL(dst) = opt->min;
+ return 1;
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_INVALID;
+ } else {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ }
+}
+
+static char *print_flag(const m_option_t *opt, const void *val)
+{
+ if (VAL(val) == opt->min)
+ return talloc_strdup(NULL, "no");
+ else
+ return talloc_strdup(NULL, "yes");
+}
+
+static void add_flag(const m_option_t *opt, void *val, double add, bool wrap)
+{
+ if (fabs(add) < 0.5)
+ return;
+ bool state = VAL(val) != opt->min;
+ state = wrap ? !state : add > 0;
+ VAL(val) = state ? opt->max : opt->min;
+}
+
+const m_option_type_t m_option_type_flag = {
+ // need yes or no in config files
+ .name = "Flag",
+ .size = sizeof(int),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_flag,
+ .print = print_flag,
+ .copy = copy_opt,
+ .add = add_flag,
+ .clamp = clamp_flag,
+};
+
+// Single-value, write-only flag
+
+static int parse_store(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0 || bstrcmp0(param, "yes") == 0) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_DISALLOW_PARAM;
+ }
+}
+
+const m_option_type_t m_option_type_store = {
+ // can only be activated
+ .name = "Flag",
+ .size = sizeof(int),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_store,
+ .copy = copy_opt,
+};
+
+// Same for float types
+
+#undef VAL
+#define VAL(x) (*(float *)(x))
+
+static int parse_store_float(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ if (param.len == 0 || bstrcmp0(param, "yes") == 0) {
+ if (dst)
+ VAL(dst) = opt->max;
+ return 0;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid parameter for %.*s flag: %.*s\n",
+ BSTR_P(name), BSTR_P(param));
+ return M_OPT_DISALLOW_PARAM;
+ }
+}
+
+const m_option_type_t m_option_type_float_store = {
+ // can only be activated
+ .name = "Flag",
+ .size = sizeof(float),
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_store_float,
+ .copy = copy_opt,
+};
+
+// 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;
+}
+
+static void multiply_int64(const m_option_t *opt, void *val, double f)
+{
+ double v = *(int64_t *)val * f;
+ int64_t iv = v;
+ if (v < INT64_MIN)
+ iv = INT64_MIN;
+ if (v > INT64_MAX)
+ iv = INT64_MAX;
+ *(int64_t *)val = iv;
+ clamp_int64(opt, val);
+}
+
+static void multiply_int(const m_option_t *opt, void *val, double f)
+{
+ int64_t tmp = *(int *)val;
+ multiply_int64(opt, &tmp, f);
+ *(int *)val = MPCLAMP(tmp, INT_MIN, INT_MAX);
+}
+
+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,
+ .multiply = multiply_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,
+ .multiply = multiply_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 (!bstrcmp0(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;
+ }
+ if (!isfinite(v)) {
+ 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);
+
+ if (bstr_eatstart0(&rest, ":") || bstr_eatstart0(&rest, "/"))
+ tmp_float /= bstrtod(rest, &rest);
+
+ 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 (!isfinite(tmp_float)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "The %.*s option must be a finite number: %.*s\n",
+ BSTR_P(name), 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_f3(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%.3f", 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;
+}
+
+static void multiply_double(const m_option_t *opt, void *val, double f)
+{
+ *(double *)val *= f;
+ clamp_double(opt, val);
+}
+
+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_f3,
+ .copy = copy_opt,
+ .clamp = clamp_double,
+ .add = add_double,
+ .multiply = multiply_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_f3(const m_option_t *opt, const void *val)
+{
+ return talloc_asprintf(NULL, "%.3f", 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;
+}
+
+static void multiply_float(const m_option_t *opt, void *val, double f)
+{
+ double tmp = VAL(val);
+ multiply_double(opt, &tmp, f);
+ 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_f3,
+ .copy = copy_opt,
+ .add = add_float,
+ .multiply = multiply_float,
+ .clamp = clamp_float,
+};
+
+///////////// String
+
+#undef VAL
+#define VAL(x) (*(char **)(x))
+
+static char *unescape_string(void *talloc_ctx, bstr str)
+{
+ char *res = talloc_strdup(talloc_ctx, "");
+ while (str.len) {
+ bstr rest;
+ bool esc = bstr_split_tok(str, "\\", &str, &rest);
+ res = talloc_strndup_append_buffer(res, str.start, str.len);
+ if (esc) {
+ if (!mp_parse_escape(&rest, &res)) {
+ talloc_free(res);
+ return NULL;
+ }
+ }
+ str = rest;
+ }
+ return res;
+}
+
+static char *escape_string(char *str0)
+{
+ char *res = talloc_strdup(NULL, "");
+ bstr str = bstr0(str0);
+ while (str.len) {
+ bstr rest;
+ bool esc = bstr_split_tok(str, "\\", &str, &rest);
+ res = talloc_strndup_append_buffer(res, str.start, str.len);
+ if (esc)
+ res = talloc_strdup_append_buffer(res, "\\\\");
+ str = rest;
+ }
+ return res;
+}
+
+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)
+{
+ int r = 1;
+ void *tmp = talloc_new(NULL);
+
+ if (param.start == NULL) {
+ r = M_OPT_MISSING_PARAM;
+ goto exit;
+ }
+
+ m_opt_string_validate_fn validate = opt->priv;
+ if (validate) {
+ r = validate(opt, name, param);
+ if (r < 0)
+ goto exit;
+ }
+
+ if (opt->flags & M_OPT_PARSE_ESCAPES) {
+ char *res = unescape_string(tmp, param);
+ if (!res) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Parameter has broken escapes: %.*s\n", BSTR_P(param));
+ r = M_OPT_INVALID;
+ goto exit;
+ }
+ param = bstr0(res);
+ }
+
+ 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));
+ r = M_OPT_OUT_OF_RANGE;
+ goto exit;
+ }
+
+ 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));
+ r = M_OPT_OUT_OF_RANGE;
+ goto exit;
+ }
+
+ if (dst) {
+ talloc_free(VAL(dst));
+ VAL(dst) = bstrdup0(NULL, param);
+ }
+
+exit:
+ talloc_free(tmp);
+ return r;
+}
+
+static char *print_str(const m_option_t *opt, const void *val)
+{
+ bool need_escape = opt->flags & M_OPT_PARSE_ESCAPES;
+ char *s = val ? VAL(val) : NULL;
+ return s ? (need_escape ? escape_string(s) : talloc_strdup(NULL, s)) : 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
+#define OP_TOGGLE 5
+
+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 (bstrcmp0(suffix, "-add") == 0)
+ op = OP_ADD;
+ else if (bstrcmp0(suffix, "-pre") == 0)
+ op = OP_PRE;
+ else if (bstrcmp0(suffix, "-del") == 0)
+ op = OP_DEL;
+ else if (bstrcmp0(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 && op != OP_NONE)
+ 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 && op != OP_NONE)
+ 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;
+
+ if (!res[0])
+ free_str_list(dst);
+
+ 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) {
+ const char *msg = opt->p;
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "%s", msg);
+ } else {
+ char *name0 = bstrdup0(NULL, name);
+ char *param0 = bstrdup0(NULL, param);
+ int r = ((m_opt_func_full_t) opt->p)(opt, name0, param0);
+ talloc_free(name0);
+ talloc_free(param0);
+ return r;
+ }
+
+ if (opt->priv == NULL)
+ return M_OPT_EXIT;
+ return 0;
+}
+
+const m_option_type_t m_option_type_print = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_print,
+};
+
+const m_option_type_t m_option_type_print_func_param = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD,
+ .parse = parse_print,
+};
+
+const m_option_type_t m_option_type_print_func = {
+ .name = "Print",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD | M_OPT_TYPE_OPTIONAL_PARAM,
+ .parse = parse_print,
+};
+
+
+/////////////////////// Subconfig
+#undef VAL
+#define VAL(x) (*(char ***)(x))
+
+// Read s sub-option name, or a positional sub-opt value.
+// Return 0 on succes, M_OPT_ error code otherwise.
+// optname is for error reporting.
+static int read_subparam(bstr optname, bstr *str, bstr *out_subparam)
+{
+ bstr p = *str;
+ bstr subparam = {0};
+
+ if (bstr_eatstart0(&p, "\"")) {
+ int 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(optname));
+ return M_OPT_INVALID;
+ }
+ p = bstr_cut(p, 1);
+ } else if (bstr_eatstart0(&p, "[")) {
+ if (!bstr_split_tok(p, "]", &subparam, &p)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Terminating ']' missing for '%.*s'\n",
+ BSTR_P(optname));
+ return M_OPT_INVALID;
+ }
+ } else if (bstr_eatstart0(&p, "%")) {
+ int 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(optname));
+ return M_OPT_INVALID;
+ }
+ subparam = bstr_splice(p, 1, optlen + 1);
+ p = bstr_cut(p, optlen + 1);
+ } else {
+ // Skip until the next character that could possibly be a meta
+ // character in option parsing.
+ int optlen = bstrcspn(p, ":=,\\%\"'[]");
+ subparam = bstr_splice(p, 0, optlen);
+ p = bstr_cut(p, optlen);
+ }
+
+ *str = p;
+ *out_subparam = subparam;
+ return 0;
+}
+
+// Return 0 on success, otherwise error code
+// On success, set *out_name and *out_val, and advance *str
+// out_val.start is NULL if there was no parameter.
+// optname is for error reporting.
+static int split_subconf(bstr optname, bstr *str, bstr *out_name, bstr *out_val)
+{
+ bstr p = *str;
+ bstr subparam = {0};
+ bstr subopt;
+ int r = read_subparam(optname, &p, &subopt);
+ if (r < 0)
+ return r;
+ if (bstr_eatstart0(&p, "=")) {
+ r = read_subparam(subopt, &p, &subparam);
+ if (r < 0)
+ return r;
+ }
+ *str = p;
+ *out_name = subopt;
+ *out_val = subparam;
+ return 0;
+}
+
+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) {
+ bstr subopt, subparam;
+ int r = split_subconf(name, &p, &subopt, &subparam);
+ if (r < 0)
+ return r;
+ 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] = bstrto0(lst, subopt);
+ lst[2 * nr + 1] = bstrto0(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,
+ .copy = copy_opt,
+};
+
+
+// Parse a >=0 number starting at s. Set s to the string following the number.
+// If the number ends with '%', eat that and set *out_per to true, but only
+// if the number is between 0-100; if not, don't eat anything, even the number.
+static bool eat_num_per(bstr *s, int *out_num, bool *out_per)
+{
+ bstr rest;
+ long long v = bstrtoll(*s, &rest, 10);
+ if (s->len == rest.len || v < INT_MIN || v > INT_MAX)
+ return false;
+ *out_num = v;
+ *out_per = false;
+ *s = rest;
+ if (bstr_eatstart0(&rest, "%") && v >= 0 && v <= 100) {
+ *out_per = true;
+ *s = rest;
+ }
+ return true;
+}
+
+static bool parse_geometry_str(struct m_geometry *gm, bstr s)
+{
+ *gm = (struct m_geometry) { .x = INT_MIN, .y = INT_MIN };
+ if (s.len == 0)
+ return true;
+ // Approximate grammar:
+ // [W[xH]][{+-}X{+-}Y] | [X:Y]
+ // (meaning: [optional] {one character of} one|alternative)
+ // Every number can be followed by '%'
+ int num;
+ bool per;
+
+#define READ_NUM(F, F_PER) do { \
+ if (!eat_num_per(&s, &num, &per)) \
+ goto error; \
+ gm->F = num; \
+ gm->F_PER = per; \
+} while(0)
+
+#define READ_SIGN(F) do { \
+ if (bstr_eatstart0(&s, "+")) { \
+ gm->F = false; \
+ } else if (bstr_eatstart0(&s, "-")) {\
+ gm->F = true; \
+ } else goto error; \
+} while(0)
+
+ if (bstrchr(s, ':') < 0) {
+ gm->wh_valid = true;
+ if (!bstr_startswith0(s, "+") && !bstr_startswith0(s, "-")) {
+ READ_NUM(w, w_per);
+ if (bstr_eatstart0(&s, "x"))
+ READ_NUM(h, h_per);
+ }
+ if (s.len > 0) {
+ gm->xy_valid = true;
+ READ_SIGN(x_sign);
+ READ_NUM(x, x_per);
+ READ_SIGN(y_sign);
+ READ_NUM(y, y_per);
+ }
+ } else {
+ gm->xy_valid = true;
+ READ_NUM(x, x_per);
+ if (!bstr_eatstart0(&s, ":"))
+ goto error;
+ READ_NUM(y, y_per);
+ }
+
+ return s.len == 0;
+
+error:
+ return false;
+}
+
+#undef READ_NUM
+#undef READ_SIGN
+
+// xpos,ypos: position of the left upper corner
+// widw,widh: width and height of the window
+// scrw,scrh: width and height of the current screen
+// The input parameters should be set to a centered window (default fallbacks).
+void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
+ int scrw, int scrh, struct m_geometry *gm)
+{
+ if (gm->wh_valid) {
+ int prew = *widw, preh = *widh;
+ if (gm->w > 0)
+ *widw = gm->w_per ? scrw * (gm->w / 100.0) : gm->w;
+ if (gm->h > 0)
+ *widh = gm->h_per ? scrh * (gm->h / 100.0) : gm->h;
+ // keep aspect if the other value is not set
+ double asp = (double)prew / preh;
+ if (gm->w > 0 && !(gm->h > 0)) {
+ *widh = *widw / asp;
+ } else if (!(gm->w > 0) && gm->h > 0) {
+ *widw = *widh * asp;
+ }
+ }
+
+ if (gm->xy_valid) {
+ if (gm->x != INT_MIN) {
+ *xpos = gm->x;
+ if (gm->x_per)
+ *xpos = (scrw - *widw) * (*xpos / 100.0);
+ if (gm->x_sign)
+ *xpos = scrw - *widw - *xpos;
+ }
+ if (gm->y != INT_MIN) {
+ *ypos = gm->y;
+ if (gm->y_per)
+ *ypos = (scrh - *widh) * (*ypos / 100.0);
+ if (gm->y_sign)
+ *ypos = scrh - *widh - *ypos;
+ }
+ }
+}
+
+static int parse_geometry(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_geometry gm;
+ if (!parse_geometry_str(&gm, param))
+ goto error;
+
+ if (dst)
+ *((struct m_geometry *)dst) = gm;
+
+ return 1;
+
+error:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid geometry: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Valid format: [W[%%][xH[%%]]][{+-}X[%%]{+-}Y[%%]] | [X[%%]:Y[%%]]\n");
+ return M_OPT_INVALID;
+}
+
+const m_option_type_t m_option_type_geometry = {
+ .name = "Window geometry",
+ .size = sizeof(struct m_geometry),
+ .parse = parse_geometry,
+ .copy = copy_opt,
+};
+
+static int parse_size_box(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ struct m_geometry gm;
+ if (!parse_geometry_str(&gm, param))
+ goto error;
+
+ if (gm.xy_valid)
+ goto error;
+
+ if (dst)
+ *((struct m_geometry *)dst) = gm;
+
+ return 1;
+
+error:
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: invalid size: '%.*s'\n",
+ BSTR_P(name), BSTR_P(param));
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Valid format: W[%%][xH[%%]] or empty string\n");
+ return M_OPT_INVALID;
+}
+
+const m_option_type_t m_option_type_size_box = {
+ .name = "Window size",
+ .size = sizeof(struct m_geometry),
+ .parse = parse_size_box,
+ .copy = copy_opt,
+};
+
+
+#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, true);
+ 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) {
+ 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,
+};
+
+#include "audio/chmap.h"
+
+static int parse_chmap(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ // min>0: at least min channels, min=0: empty ok, min=-1: invalid ok
+ int min_ch = (opt->flags & M_OPT_MIN) ? opt->min : 1;
+
+ if (bstr_equals0(param, "help")) {
+ mp_chmap_print_help(MSGT_CFGPARSER, MSGL_INFO);
+ return M_OPT_EXIT - 1;
+ }
+
+ if (param.len == 0 && min_ch >= 1)
+ return M_OPT_MISSING_PARAM;
+
+ struct mp_chmap res = {0};
+ if (!mp_chmap_from_str(&res, param)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Error parsing channel layout: %.*s\n", BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if ((min_ch > 0 && !mp_chmap_is_valid(&res)) ||
+ (min_ch >= 0 && mp_chmap_is_empty(&res)))
+ {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid channel layout: %.*s\n", BSTR_P(param));
+ return M_OPT_INVALID;
+ }
+
+ if (dst)
+ *(struct mp_chmap *)dst = res;
+
+ return 1;
+}
+
+const m_option_type_t m_option_type_chmap = {
+ .name = "Audio channels or channel map",
+ .size = sizeof(struct mp_chmap *),
+ .parse = parse_chmap,
+ .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 */
+ if (!isfinite(*time))
+ return 0;
+ 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
+
+#undef VAL
+#define VAL(x) (*(m_obj_settings_t **)(x))
+
+bool m_obj_list_find(struct m_obj_desc *dst, const struct m_obj_list *l,
+ bstr name)
+{
+ for (int i = 0; ; i++) {
+ if (!l->get_desc(dst, i))
+ break;
+ if (bstr_equals0(name, dst->name))
+ return true;
+ }
+ if (l->aliases) {
+ for (int i = 0; l->aliases[i][0]; i++) {
+ const char *aname = l->aliases[i][0];
+ const char *alias = l->aliases[i][1];
+ const char *opts = l->aliases[i][2];
+ if (bstr_equals0(name, aname) &&
+ m_obj_list_find(dst, l, bstr0(alias)))
+ {
+ if (opts) {
+ dst->init_options = opts;
+ } else {
+ // Assume it's deprecated in this case.
+ // Also, it's used by the VO code only, so whatever.
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Driver '%s' has been replaced with '%s'!\n",
+ aname, alias);
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static void obj_setting_free(m_obj_settings_t *item)
+{
+ talloc_free(item->name);
+ talloc_free(item->label);
+ free_str_list(&(item->attribs));
+}
+
+// If at least one item has a label, compare labels only - otherwise ignore them.
+static bool obj_setting_equals(m_obj_settings_t *a, m_obj_settings_t *b)
+{
+ bstr la = bstr0(a->label), lb = bstr0(b->label);
+ if (la.len || lb.len)
+ return bstr_equals(la, lb);
+ if (strcmp(a->name, b->name) != 0)
+ return false;
+
+ int a_attr_count = 0;
+ while (a->attribs && a->attribs[a_attr_count])
+ a_attr_count++;
+ int b_attr_count = 0;
+ while (b->attribs && b->attribs[b_attr_count])
+ b_attr_count++;
+ if (a_attr_count != b_attr_count)
+ return false;
+ for (int n = 0; n < a_attr_count; n++) {
+ if (strcmp(a->attribs[n], b->attribs[n]) != 0)
+ return false;
+ }
+ return true;
+}
+
+static int obj_settings_list_num_items(m_obj_settings_t *obj_list)
+{
+ int num = 0;
+ while (obj_list && obj_list[num].name)
+ num++;
+ return num;
+}
+
+static void obj_settings_list_del_at(m_obj_settings_t **p_obj_list, int idx)
+{
+ m_obj_settings_t *obj_list = *p_obj_list;
+ int num = obj_settings_list_num_items(obj_list);
+
+ assert(idx >= 0 && idx < num);
+
+ obj_setting_free(&obj_list[idx]);
+
+ // Note: the NULL-terminating element is moved down as part of this
+ memmove(&obj_list[idx], &obj_list[idx + 1],
+ sizeof(m_obj_settings_t) * (num - idx));
+
+ *p_obj_list = talloc_realloc(NULL, obj_list, struct m_obj_settings, num);
+}
+
+// Insert such that *p_obj_list[idx] is set to item.
+// If idx < 0, set idx = count + idx + 1 (i.e. -1 inserts it as last element).
+// Memory referenced by *item is not copied.
+static void obj_settings_list_insert_at(m_obj_settings_t **p_obj_list, int idx,
+ m_obj_settings_t *item)
+{
+ int num = obj_settings_list_num_items(*p_obj_list);
+ if (idx < 0)
+ idx = num + idx + 1;
+ assert(idx >= 0 && idx <= num);
+ *p_obj_list = talloc_realloc(NULL, *p_obj_list, struct m_obj_settings,
+ num + 2);
+ memmove(*p_obj_list + idx + 1, *p_obj_list + idx,
+ (num - idx) * sizeof(m_obj_settings_t));
+ (*p_obj_list)[idx] = *item;
+ (*p_obj_list)[num + 1] = (m_obj_settings_t){0};
+}
+
+static int obj_settings_list_find_by_label(m_obj_settings_t *obj_list,
+ bstr label)
+{
+ for (int n = 0; obj_list && obj_list[n].name; n++) {
+ if (label.len && bstr_equals0(label, obj_list[n].label))
+ return n;
+ }
+ return -1;
+}
+
+static int obj_settings_list_find_by_label0(m_obj_settings_t *obj_list,
+ const char *label)
+{
+ return obj_settings_list_find_by_label(obj_list, bstr0(label));
+}
+
+static int obj_settings_find_by_content(m_obj_settings_t *obj_list,
+ m_obj_settings_t *item)
+{
+ for (int n = 0; obj_list && obj_list[n].name; n++) {
+ if (obj_setting_equals(&obj_list[n], item))
+ return n;
+ }
+ 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++)
+ obj_setting_free(&d[n]);
+ talloc_free(d);
+ VAL(dst) = NULL;
+}
+
+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].label = talloc_strdup(NULL, s[n].label);
+ d[n].attribs = NULL;
+ copy_str_list(NULL, &(d[n].attribs), &(s[n].attribs));
+ }
+ d[n].name = NULL;
+ d[n].label = NULL;
+ d[n].attribs = NULL;
+ VAL(dst) = d;
+}
+
+// Consider -vf a=b=c:d=e. This verifies "b"="c" and "d"="e" and that the
+// option names/values are correct. Try to determine whether an option
+// without '=' sets a flag, or whether it's a positional argument.
+static int get_obj_param(bstr opt_name, bstr obj_name, struct m_config *config,
+ bstr name, bstr val, int flags, int *nold,
+ bstr *out_name, bstr *out_val)
+{
+ int r;
+
+ if (!config)
+ return 0; // skip
+
+ // va.start != NULL => of the form name=val (not positional)
+ // If it's just "name", and the associated option exists and is a flag,
+ // don't accept it as positional argument.
+ if (val.start || m_config_option_requires_param(config, name) == 0) {
+ r = m_config_set_option_ext(config, name, val, flags);
+ if (r < 0) {
+ if (r == M_OPT_UNKNOWN) {
+ 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(name));
+ return M_OPT_UNKNOWN;
+ }
+ 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(name),
+ BSTR_P(val));
+ return r;
+ }
+ *out_name = name;
+ *out_val = val;
+ return 1;
+ } else {
+ val = name;
+ // positional fields
+ if (val.len == 0) { // Empty field, count it and go on
+ (*nold)++;
+ return 0;
+ }
+ const char *opt = m_config_get_positional_option(config, *nold);
+ if (!opt) {
+ 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), *nold, *nold);
+ return M_OPT_OUT_OF_RANGE;
+ }
+ r = m_config_set_option_ext(config, bstr0(opt), val, flags);
+ 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, BSTR_P(val));
+ return r;
+ }
+ *out_name = bstr0(opt);
+ *out_val = val;
+ (*nold)++;
+ return 1;
+ }
+}
+
+// Consider -vf a=b:c:d. This parses "b:c:d" into name/value pairs, stored as
+// linear array in *_ret. In particular, config contains what options a the
+// object takes, and verifies the option values as well.
+// If config is NULL, all parameters are accepted without checking.
+// _ret set to NULL can be used for checking-only.
+// flags can contain any M_SETOPT_* flag.
+static int m_obj_parse_sub_config(struct bstr opt_name, struct bstr name,
+ struct bstr *pstr, struct m_config *config,
+ int flags, void (*print_help_fn)(void),
+ char ***ret)
+{
+ int nold = 0;
+ char **args = NULL;
+ int num_args = 0;
+ int r = 1;
+
+ if (ret) {
+ args = *ret;
+ while (args && args[num_args])
+ num_args++;
+ }
+
+ while (pstr->len > 0) {
+ bstr fname, fval;
+ r = split_subconf(opt_name, pstr, &fname, &fval);
+ if (r < 0)
+ goto exit;
+ if (bstr_equals0(fname, "help"))
+ goto print_help;
+ r = get_obj_param(opt_name, name, config, fname, fval, flags, &nold,
+ &fname, &fval);
+ if (r < 0)
+ goto exit;
+
+ if (r > 0 && ret) {
+ MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fname));
+ MP_TARRAY_APPEND(NULL, args, num_args, bstrto0(NULL, fval));
+ MP_TARRAY_APPEND(NULL, args, num_args, NULL);
+ MP_TARRAY_APPEND(NULL, args, num_args, NULL);
+ num_args -= 2;
+ }
+
+ if (!bstr_eatstart0(pstr, ":"))
+ break;
+ }
+
+ if (ret) {
+ if (num_args > 0) {
+ *ret = args;
+ args = NULL;
+ } else {
+ *ret = NULL;
+ }
+ }
+
+ goto exit;
+
+print_help: ;
+ if (config) {
+ if (print_help_fn)
+ print_help_fn();
+ m_config_print_option_list(config);
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Option %.*s doesn't exist.\n",
+ BSTR_P(opt_name));
+ }
+ r = M_OPT_EXIT - 1;
+
+exit:
+ free_str_list(&args);
+ return r;
+}
+
+// Characters which may appear in a filter name
+#define NAMECH "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-"
+
+// Parse one item, e.g. -vf a=b:c:d,e=f:g => parse a=b:c:d into "a" and "b:c:d"
+static int parse_obj_settings(struct bstr opt, struct bstr *pstr,
+ const struct m_obj_list *list,
+ m_obj_settings_t **_ret)
+{
+ int r;
+ char **plist = NULL;
+ struct m_obj_desc desc;
+ bstr label = {0};
+
+ if (bstr_eatstart0(pstr, "@")) {
+ if (!bstr_split_tok(*pstr, ":", &label, pstr)) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Option %.*s: ':' expected after label.\n", BSTR_P(opt));
+ return M_OPT_INVALID;
+ }
+ }
+
+ bool has_param = false;
+ int idx = bstrspn(*pstr, NAMECH);
+ bstr str = bstr_splice(*pstr, 0, idx);
+ *pstr = bstr_cut(*pstr, idx);
+ // video filters use "=", VOs use ":"
+ if (bstr_eatstart0(pstr, "=") || bstr_eatstart0(pstr, ":"))
+ has_param = true;
+
+ bool skip = false;
+ if (!m_obj_list_find(&desc, list, str)) {
+ if (!list->allow_unknown_entries) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Option %.*s: %.*s doesn't exist.\n",
+ BSTR_P(opt), BSTR_P(str));
+ return M_OPT_INVALID;
+ }
+ desc = (struct m_obj_desc){0};
+ skip = true;
+ }
+
+ if (_ret && desc.init_options) {
+ struct m_config *config = m_config_from_obj_desc_noalloc(NULL, &desc);
+ bstr s = bstr0(desc.init_options);
+ m_obj_parse_sub_config(opt, str, &s, config,
+ M_SETOPT_CHECK_ONLY, NULL, &plist);
+ assert(s.len == 0);
+ talloc_free(config);
+ }
+
+ if (has_param) {
+ struct m_config *config = NULL;
+ if (!skip)
+ config = m_config_from_obj_desc_noalloc(NULL, &desc);
+ r = m_obj_parse_sub_config(opt, str, pstr, config,
+ M_SETOPT_CHECK_ONLY, desc.print_help,
+ _ret ? &plist : NULL);
+ talloc_free(config);
+ if (r < 0)
+ return r;
+ }
+ if (!_ret)
+ return 1;
+
+ m_obj_settings_t item = {
+ .name = bstrto0(NULL, str),
+ .label = bstrdup0(NULL, label),
+ .attribs = plist,
+ };
+ obj_settings_list_insert_at(_ret, -1, &item);
+ return 1;
+}
+
+// Parse a single entry for -vf-del (return 0 if not applicable)
+// mark_del is bounded by the number of items in dst
+static int parse_obj_settings_del(struct bstr opt_name, struct bstr *param,
+ void *dst, bool *mark_del)
+{
+ bstr s = *param;
+ if (bstr_eatstart0(&s, "@")) {
+ // '@name:' -> parse as normal filter entry
+ // '@name,' or '@name<end>' -> parse here
+ int idx = bstrspn(s, NAMECH);
+ bstr label = bstr_splice(s, 0, idx);
+ s = bstr_cut(s, idx);
+ if (bstr_startswith0(s, ":"))
+ return 0;
+ if (dst) {
+ int label_index = obj_settings_list_find_by_label(VAL(dst), label);
+ if (label_index >= 0) {
+ mark_del[label_index] = true;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: item label @%.*s not found.\n",
+ BSTR_P(opt_name), BSTR_P(label));
+ }
+ }
+ *param = s;
+ return 1;
+ }
+
+ bstr rest;
+ long long id = bstrtoll(s, &rest, 0);
+ if (rest.len == s.len)
+ return 0;
+
+ if (dst) {
+ int num = obj_settings_list_num_items(VAL(dst));
+ if (id < 0)
+ id = num + id;
+
+ if (id >= 0 && id < num) {
+ mark_del[id] = true;
+ } else {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: Index %lld is out of range.\n",
+ BSTR_P(opt_name), id);
+ }
+ }
+
+ *param = rest;
+ return 1;
+}
+
+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;
+ int op = OP_NONE;
+ bool *mark_del = NULL;
+ int num_items = obj_settings_list_num_items(dst ? VAL(dst) : 0);
+ struct m_obj_list *ol = opt->priv;
+
+ assert(opt->priv);
+
+ if (opt->name[len - 1] == '*' && (name.len > len - 1)) {
+ struct bstr suffix = bstr_cut(name, len - 1);
+ if (bstrcmp0(suffix, "-add") == 0)
+ op = OP_ADD;
+ else if (bstrcmp0(suffix, "-set") == 0)
+ op = OP_NONE;
+ else if (bstrcmp0(suffix, "-pre") == 0)
+ op = OP_PRE;
+ else if (bstrcmp0(suffix, "-del") == 0)
+ op = OP_DEL;
+ else if (bstrcmp0(suffix, "-clr") == 0)
+ op = OP_CLR;
+ else if (bstrcmp0(suffix, "-toggle") == 0)
+ op = OP_TOGGLE;
+ 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-set\n"
+ " Overwrite the old list with the given list\n\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"
+ " Filter names work as well.\n\n"
+ " %s-clr\n"
+ " Clear the current list.\n",
+ BSTR_P(name), BSTR_P(suffix), prefix, prefix, prefix, prefix, prefix);
+
+ return M_OPT_UNKNOWN;
+ }
+ }
+
+ if (!bstrcmp0(param, "help")) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "Available %s:\n", ol->description);
+ for (int n = 0; ; n++) {
+ struct m_obj_desc desc;
+ if (!ol->get_desc(&desc, n))
+ break;
+ if (!desc.hidden) {
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, " %-15s: %s\n",
+ desc.name, desc.description);
+ }
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\n");
+ return M_OPT_EXIT - 1;
+ }
+
+ if (op == OP_CLR) {
+ if (dst)
+ free_obj_settings_list(dst);
+ return 0;
+ } else if (op == OP_DEL) {
+ mark_del = talloc_zero_array(NULL, bool, num_items + 1);
+ }
+
+ if (op != OP_NONE && param.len == 0)
+ return M_OPT_MISSING_PARAM;
+
+ while (param.len > 0) {
+ int r = 0;
+ if (op == OP_DEL)
+ r = parse_obj_settings_del(name, &param, dst, mark_del);
+ if (r == 0) {
+ r = parse_obj_settings(name, &param, ol, dst ? &res : NULL);
+ }
+ if (r < 0)
+ return r;
+ if (param.len > 0) {
+ const char sep[2] = {OPTION_LIST_SEPARATOR, 0};
+ if (!bstr_eatstart0(&param, sep))
+ return M_OPT_INVALID;
+ if (param.len == 0) {
+ if (!ol->allow_trailer)
+ return M_OPT_INVALID;
+ if (dst) {
+ m_obj_settings_t item = {
+ .name = talloc_strdup(NULL, ""),
+ };
+ obj_settings_list_insert_at(&res, -1, &item);
+ }
+ }
+ }
+ }
+
+ if (dst) {
+ m_obj_settings_t *list = VAL(dst);
+ if (op == OP_PRE) {
+ int prepend_counter = 0;
+ for (int n = 0; res && res[n].name; n++) {
+ int label = obj_settings_list_find_by_label0(list, res[n].label);
+ if (label < 0) {
+ obj_settings_list_insert_at(&list, prepend_counter, &res[n]);
+ prepend_counter++;
+ } else {
+ // Prefer replacement semantics, instead of actually
+ // prepending.
+ obj_setting_free(&list[label]);
+ list[label] = res[n];
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_ADD) {
+ for (int n = 0; res && res[n].name; n++) {
+ int label = obj_settings_list_find_by_label0(list, res[n].label);
+ if (label < 0) {
+ obj_settings_list_insert_at(&list, -1, &res[n]);
+ } else {
+ // Prefer replacement semantics, instead of actually
+ // appending.
+ obj_setting_free(&list[label]);
+ list[label] = res[n];
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_TOGGLE) {
+ for (int n = 0; res && res[n].name; n++) {
+ int found = obj_settings_find_by_content(list, &res[n]);
+ if (found < 0) {
+ obj_settings_list_insert_at(&list, -1, &res[n]);
+ } else {
+ obj_settings_list_del_at(&list, found);
+ obj_setting_free(&res[n]);
+ }
+ }
+ talloc_free(res);
+ } else if (op == OP_DEL) {
+ for (int n = num_items - 1; n >= 0; n--) {
+ if (mark_del[n])
+ obj_settings_list_del_at(&list, n);
+ }
+ for (int n = 0; res && res[n].name; n++) {
+ int found = obj_settings_find_by_content(list, &res[n]);
+ if (found < 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN,
+ "Option %.*s: Item not found\n", BSTR_P(name));
+ } else {
+ obj_settings_list_del_at(&list, found);
+ }
+ }
+ free_obj_settings_list(&res);
+ } else {
+ assert(op == OP_NONE);
+ free_obj_settings_list(&list);
+ list = res;
+ }
+ VAL(dst) = list;
+ }
+
+ talloc_free(mark_del);
+ return 1;
+}
+
+static void append_param(char **res, char *param)
+{
+ if (strspn(param, NAMECH) == strlen(param)) {
+ *res = talloc_strdup_append(*res, param);
+ } else {
+ // Simple escaping: %BYTECOUNT%STRING
+ *res = talloc_asprintf_append(*res, "%%%zd%%%s", strlen(param), param);
+ }
+}
+
+static char *print_obj_settings_list(const m_option_t *opt, const void *val)
+{
+ m_obj_settings_t *list = VAL(val);
+ char *res = talloc_strdup(NULL, "");
+ for (int n = 0; list && list[n].name; n++) {
+ m_obj_settings_t *entry = &list[n];
+ if (n > 0)
+ res = talloc_strdup_append(res, ",");
+ // Assume labels and names don't need escaping
+ if (entry->label && entry->label[0])
+ res = talloc_asprintf_append(res, "@%s:", entry->label);
+ res = talloc_strdup_append(res, entry->name);
+ if (entry->attribs && entry->attribs[0]) {
+ res = talloc_strdup_append(res, "=");
+ for (int i = 0; entry->attribs[i * 2 + 0]; i++) {
+ if (i > 0)
+ res = talloc_strdup_append(res, ":");
+ append_param(&res, entry->attribs[i * 2 + 0]);
+ res = talloc_strdup_append(res, "=");
+ append_param(&res, entry->attribs[i * 2 + 1]);
+ }
+ }
+ }
+ return res;
+}
+
+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,
+ .print = print_obj_settings_list,
+ .copy = copy_obj_settings_list,
+ .free = free_obj_settings_list,
+};
diff --git a/options/m_option.h b/options/m_option.h
new file mode 100644
index 0000000000..dfc9e28a9b
--- /dev/null
+++ b/options/m_option.h
@@ -0,0 +1,648 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_OPTION_H
+#define MPLAYER_M_OPTION_H
+
+#include <string.h>
+#include <stddef.h>
+#include <stdbool.h>
+
+#include "config.h"
+#include "mpvcore/bstr.h"
+#include "audio/chmap.h"
+
+// m_option allows to parse, print and copy data of various types.
+
+typedef struct m_option_type m_option_type_t;
+typedef struct m_option m_option_t;
+struct m_config;
+
+///////////////////////////// Options types declarations ////////////////////
+
+// Simple types
+extern const m_option_type_t m_option_type_flag;
+extern const m_option_type_t m_option_type_store;
+extern const m_option_type_t m_option_type_float_store;
+extern const m_option_type_t m_option_type_int;
+extern const m_option_type_t m_option_type_int64;
+extern const m_option_type_t m_option_type_intpair;
+extern const m_option_type_t m_option_type_float;
+extern const m_option_type_t m_option_type_double;
+extern const m_option_type_t m_option_type_string;
+extern const m_option_type_t m_option_type_string_list;
+extern const m_option_type_t m_option_type_time;
+extern const m_option_type_t m_option_type_rel_time;
+extern const m_option_type_t m_option_type_choice;
+
+extern const m_option_type_t m_option_type_print;
+extern const m_option_type_t m_option_type_print_func;
+extern const m_option_type_t m_option_type_print_func_param;
+extern const m_option_type_t m_option_type_subconfig;
+extern const m_option_type_t m_option_type_subconfig_struct;
+extern const m_option_type_t m_option_type_imgfmt;
+extern const m_option_type_t m_option_type_fourcc;
+extern const m_option_type_t m_option_type_afmt;
+extern const m_option_type_t m_option_type_color;
+extern const m_option_type_t m_option_type_geometry;
+extern const m_option_type_t m_option_type_size_box;
+extern const m_option_type_t m_option_type_chmap;
+
+// Callback used by m_option_type_print_func options.
+typedef int (*m_opt_func_full_t)(const m_option_t *, const char *, const char *);
+
+enum m_rel_time_type {
+ REL_TIME_NONE,
+ REL_TIME_ABSOLUTE,
+ REL_TIME_NEGATIVE,
+ REL_TIME_PERCENT,
+ REL_TIME_CHAPTER,
+};
+
+struct m_rel_time {
+ double pos;
+ enum m_rel_time_type type;
+};
+
+struct m_color {
+ uint8_t r, g, b, a;
+};
+
+struct m_geometry {
+ int x, y, w, h;
+ bool xy_valid : 1, wh_valid : 1;
+ bool w_per : 1, h_per : 1;
+ bool x_sign : 1, y_sign : 1, x_per : 1, y_per : 1;
+};
+
+void m_geometry_apply(int *xpos, int *ypos, int *widw, int *widh,
+ int scrw, int scrh, struct m_geometry *gm);
+
+struct m_obj_desc {
+ // Name which will be used in the option string
+ const char *name;
+ // Will be printed when "help" is passed
+ const char *description;
+ // Size of the private struct
+ int priv_size;
+ // If not NULL, default values for private struct
+ const void *priv_defaults;
+ // Options which refer to members in the private struct
+ const struct m_option *options;
+ // For free use by the implementer of m_obj_list.get_desc
+ const void *p;
+ // If not NULL, options which should be set before applying other options.
+ // This member is usually set by m_obj_list_find() only, and read by the
+ // option parser. It's not used anywhere else.
+ const char *init_options;
+ // Don't list entry with "help"
+ bool hidden;
+ // Callback to print custom help if "help" is passed
+ void (*print_help)(void);
+};
+
+// Extra definition needed for \ref m_option_type_obj_settings_list options.
+struct m_obj_list {
+ bool (*get_desc)(struct m_obj_desc *dst, int index);
+ const char *description;
+ // Can be set to a NULL terminated array of aliases
+ const char *aliases[4][5];
+ // Allow a trailing ",", which adds an entry with name=""
+ bool allow_trailer;
+ // Allow unknown entries, for which a dummy entry is inserted, and whose
+ // options are skipped and ignored.
+ bool allow_unknown_entries;
+};
+
+// Find entry by name
+bool m_obj_list_find(struct m_obj_desc *dst, const struct m_obj_list *list,
+ bstr name);
+
+// The data type used by \ref m_option_type_obj_settings_list.
+typedef struct m_obj_settings {
+ // Type of the object.
+ char *name;
+ // Optional user-defined name.
+ char *label;
+ // NULL terminated array of parameter/value pairs.
+ char **attribs;
+} m_obj_settings_t;
+
+// A parser to set up a list of objects.
+/** It creates a NULL terminated array \ref m_obj_settings. The option priv
+ * field (\ref m_option::priv) must point to a \ref m_obj_list_t describing
+ * the available object types.
+ */
+extern const m_option_type_t m_option_type_obj_settings_list;
+
+struct m_opt_choice_alternatives {
+ char *name;
+ int value;
+};
+
+// For OPT_STRING_VALIDATE(). Behaves like m_option_type.parse().
+typedef int (*m_opt_string_validate_fn)(const m_option_t *opt, struct bstr name,
+ struct bstr param);
+
+// m_option.priv points to this if M_OPT_TYPE_USE_SUBSTRUCT is used
+struct m_sub_options {
+ const struct m_option *opts;
+ size_t size;
+ const void *defaults;
+};
+
+// FIXME: backward compatibility
+#define CONF_TYPE_FLAG (&m_option_type_flag)
+#define CONF_TYPE_STORE (&m_option_type_store)
+#define CONF_TYPE_INT (&m_option_type_int)
+#define CONF_TYPE_INT64 (&m_option_type_int64)
+#define CONF_TYPE_FLOAT (&m_option_type_float)
+#define CONF_TYPE_DOUBLE (&m_option_type_double)
+#define CONF_TYPE_STRING (&m_option_type_string)
+#define CONF_TYPE_PRINT (&m_option_type_print)
+#define CONF_TYPE_PRINT_FUNC (&m_option_type_print_func)
+#define CONF_TYPE_SUBCONFIG (&m_option_type_subconfig)
+#define CONF_TYPE_STRING_LIST (&m_option_type_string_list)
+#define CONF_TYPE_IMGFMT (&m_option_type_imgfmt)
+#define CONF_TYPE_FOURCC (&m_option_type_fourcc)
+#define CONF_TYPE_AFMT (&m_option_type_afmt)
+#define CONF_TYPE_OBJ_SETTINGS_LIST (&m_option_type_obj_settings_list)
+#define CONF_TYPE_TIME (&m_option_type_time)
+#define CONF_TYPE_CHOICE (&m_option_type_choice)
+#define CONF_TYPE_INT_PAIR (&m_option_type_intpair)
+
+// Possible option values. Code is allowed to access option data without going
+// through this union. It serves for self-documentation and to get minimal
+// size/alignment requirements for option values in general.
+union m_option_value {
+ int flag; // not the C type "bool"!
+ int store;
+ float float_store;
+ int int_;
+ int64_t int64;
+ int intpair[2];
+ float float_;
+ double double_;
+ char *string;
+ char **string_list;
+ int imgfmt;
+ unsigned int fourcc;
+ int afmt;
+ m_obj_settings_t *obj_settings_list;
+ double time;
+ struct m_rel_time rel_time;
+ struct m_color color;
+ struct m_geometry geometry;
+ struct m_geometry size_box;
+ struct mp_chmap chmap;
+};
+
+////////////////////////////////////////////////////////////////////////////
+
+// Option type description
+struct m_option_type {
+ const char *name;
+ // Size needed for the data.
+ unsigned int size;
+ // One of M_OPT_TYPE*.
+ unsigned int flags;
+
+ // Parse the data from a string.
+ /** It is the only required function, all others can be NULL.
+ *
+ * \param opt The option that is parsed.
+ * \param name The full option name.
+ * \param param The parameter to parse.
+ * may not be an argument meant for this option
+ * \param dst Pointer to the memory where the data should be written.
+ * If NULL the parameter validity should still be checked.
+ * \return On error a negative value is returned, on success the number
+ * of arguments consumed. For details see \ref OptionParserReturn.
+ */
+ int (*parse)(const m_option_t *opt, struct bstr name, struct bstr param,
+ void *dst);
+
+ // Print back a value in string form.
+ /** \param opt The option to print.
+ * \param val Pointer to the memory holding the data to be printed.
+ * \return An allocated string containing the text value or (void*)-1
+ * on error.
+ */
+ char *(*print)(const m_option_t *opt, const void *val);
+
+ // Print the value in a human readable form. Unlike print(), it doesn't
+ // necessarily return the exact value, and is generally not parseable with
+ // parse().
+ char *(*pretty_print)(const m_option_t *opt, const void *val);
+
+ // Copy data between two locations. Deep copy if the data has pointers.
+ /** \param opt The option to copy.
+ * \param dst Pointer to the destination memory.
+ * \param src Pointer to the source memory.
+ */
+ void (*copy)(const m_option_t *opt, void *dst, const void *src);
+
+ // Free the data allocated for a save slot.
+ /** This is only needed for dynamic types like strings.
+ * \param dst Pointer to the data, usually a pointer that should be freed and
+ * set to NULL.
+ */
+ void (*free)(void *dst);
+
+ // Add the value add to the value in val. For types that are not numeric,
+ // add gives merely the direction. The wrap parameter determines whether
+ // the value is clipped, or wraps around to the opposite max/min.
+ void (*add)(const m_option_t *opt, void *val, double add, bool wrap);
+
+ // Multiply the value with the factor f. The callback must clip the result
+ // to the valid value range of the option.
+ void (*multiply)(const m_option_t *opt, void *val, double f);
+
+ // Clamp the value in val to the option's valid value range.
+ // Return values:
+ // M_OPT_OUT_OF_RANGE: val was invalid, and modified (clamped) to be valid
+ // M_OPT_INVALID: val was invalid, and can't be made valid
+ // 0: val was already valid and is unchanged
+ int (*clamp)(const m_option_t *opt, void *val);
+};
+
+// Option description
+struct m_option {
+ // Option name.
+ const char *name;
+
+ // Reserved for higher level APIs, it shouldn't be used by parsers.
+ /** The suboption parser and func types do use it. They should instead
+ * use the priv field but this was inherited from older versions of the
+ * config code.
+ */
+ void *p;
+
+ // Option type.
+ const m_option_type_t *type;
+
+ // See \ref OptionFlags.
+ unsigned int flags;
+
+ // \brief Mostly useful for numeric types, the \ref M_OPT_MIN flags must
+ // also be set.
+ double min;
+
+ // \brief Mostly useful for numeric types, the \ref M_OPT_MAX flags must
+ // also be set.
+ double max;
+
+ // Type dependent data (for all kinds of extended settings).
+ /** This used to be a function pointer to hold a 'reverse to defaults' func.
+ * Now it can be used to pass any type of extra args needed by the parser.
+ */
+ void *priv;
+
+ int is_new_option;
+
+ int offset;
+
+ // Initialize variable to given default before parsing options
+ const void *defval;
+};
+
+
+// The option has a minimum set in \ref m_option::min.
+#define M_OPT_MIN (1 << 0)
+
+// The option has a maximum set in \ref m_option::max.
+#define M_OPT_MAX (1 << 1)
+
+// The option has a minimum and maximum in m_option::min and m_option::max.
+#define M_OPT_RANGE (M_OPT_MIN | M_OPT_MAX)
+
+// The option is forbidden in config files.
+#define M_OPT_NOCFG (1 << 2)
+
+// This option can't be set per-file when used with struct m_config.
+#define M_OPT_GLOBAL (1 << 4)
+
+// The option should be set during command line pre-parsing
+#define M_OPT_PRE_PARSE (1 << 6)
+
+// See M_OPT_TYPE_OPTIONAL_PARAM.
+#define M_OPT_OPTIONAL_PARAM (1 << 10)
+
+// Parse C-style escapes like "\n" (for CONF_TYPE_STRING only)
+#define M_OPT_PARSE_ESCAPES (1 << 11)
+
+// These are kept for compatibility with older code.
+#define CONF_MIN M_OPT_MIN
+#define CONF_MAX M_OPT_MAX
+#define CONF_RANGE M_OPT_RANGE
+#define CONF_NOCFG M_OPT_NOCFG
+#define CONF_GLOBAL M_OPT_GLOBAL
+#define CONF_PRE_PARSE M_OPT_PRE_PARSE
+
+// These flags are used to describe special parser capabilities or behavior.
+
+// Suboption parser flag.
+/** When this flag is set, m_option::p should point to another m_option
+ * array. Only the parse function will be called. If dst is set, it should
+ * create/update an array of char* containg opt/val pairs. The options in
+ * the child array will then be set automatically by the \ref Config.
+ * Also note that suboptions may be directly accessed by using
+ * -option:subopt blah.
+ */
+#define M_OPT_TYPE_HAS_CHILD (1 << 0)
+
+// Wildcard matching flag.
+/** If set the option type has a use for option names ending with a *
+ * (used for -aa*), this only affects the option name matching.
+ */
+#define M_OPT_TYPE_ALLOW_WILDCARD (1 << 1)
+
+// Dynamic data type.
+/** This flag indicates that the data is dynamically allocated (m_option::p
+ * points to a pointer). It enables a little hack in the \ref Config wich
+ * replaces the initial value of such variables with a dynamic copy in case
+ * the initial value is statically allocated (pretty common with strings).
+ */
+#define M_OPT_TYPE_DYNAMIC (1 << 2)
+
+// The parameter is optional and by default no parameter is preferred. If
+// ambiguous syntax is used ("--opt value"), the command line parser will
+// assume that the argument takes no parameter. In config files, these
+// options can be used without "=" and value.
+#define M_OPT_TYPE_OPTIONAL_PARAM (1 << 3)
+
+// modify M_OPT_TYPE_HAS_CHILD so that m_option::p points to
+// struct m_sub_options, instead of a direct m_option array.
+#define M_OPT_TYPE_USE_SUBSTRUCT (1 << 4)
+
+///////////////////////////// Parser flags /////////////////////////////////
+
+// OptionParserReturn
+//
+// On success parsers return a number >= 0.
+//
+// To indicate that MPlayer should exit without playing anything,
+// parsers return M_OPT_EXIT minus the number of parameters they
+// consumed: \ref M_OPT_EXIT or \ref M_OPT_EXIT-1.
+//
+// On error one of the following (negative) error codes is returned:
+
+// For use by higher level APIs when the option name is invalid.
+#define M_OPT_UNKNOWN -1
+
+// Returned when a parameter is needed but wasn't provided.
+#define M_OPT_MISSING_PARAM -2
+
+// Returned when the given parameter couldn't be parsed.
+#define M_OPT_INVALID -3
+
+// Returned if the value is "out of range". The exact meaning may
+// vary from type to type.
+#define M_OPT_OUT_OF_RANGE -4
+
+// The option doesn't take a parameter.
+#define M_OPT_DISALLOW_PARAM -5
+
+// Returned if the parser failed for any other reason than a bad parameter.
+#define M_OPT_PARSER_ERR -6
+
+// Returned when MPlayer should exit. Used by various help stuff.
+/** M_OPT_EXIT must be the lowest number on this list.
+ */
+#define M_OPT_EXIT -7
+
+char *m_option_strerror(int code);
+
+// Find the option matching the given name in the list.
+/** \ingroup Options
+ * This function takes the possible wildcards into account (see
+ * \ref M_OPT_TYPE_ALLOW_WILDCARD).
+ *
+ * \param list Pointer to an array of \ref m_option.
+ * \param name Name of the option.
+ * \return The matching option or NULL.
+ */
+const m_option_t *m_option_list_find(const m_option_t *list, const char *name);
+
+// Helper to parse options, see \ref m_option_type::parse.
+static inline int m_option_parse(const m_option_t *opt, struct bstr name,
+ struct bstr param, void *dst)
+{
+ return opt->type->parse(opt, name, param, dst);
+}
+
+// Helper to print options, see \ref m_option_type::print.
+static inline char *m_option_print(const m_option_t *opt, const void *val_ptr)
+{
+ if (opt->type->print)
+ return opt->type->print(opt, val_ptr);
+ else
+ return NULL;
+}
+
+static inline char *m_option_pretty_print(const m_option_t *opt,
+ const void *val_ptr)
+{
+ if (opt->type->pretty_print)
+ return opt->type->pretty_print(opt, val_ptr);
+ else
+ return m_option_print(opt, val_ptr);
+}
+
+// Helper around \ref m_option_type::copy.
+static inline void m_option_copy(const m_option_t *opt, void *dst,
+ const void *src)
+{
+ if (opt->type->copy)
+ opt->type->copy(opt, dst, src);
+}
+
+// Helper around \ref m_option_type::free.
+static inline void m_option_free(const m_option_t *opt, void *dst)
+{
+ if (opt->type->free)
+ opt->type->free(dst);
+}
+
+int m_option_required_params(const m_option_t *opt);
+
+// Cause a compilation warning if typeof(expr) != type.
+// Should be used with pointer types only.
+#define MP_EXPECT_TYPE(type, expr) (0 ? (type)0 : (expr))
+
+// This behaves like offsetof(type, member), but will cause a compilation
+// warning if typeof(member) != expected_member_type.
+// It uses some trickery to make it compile as expression.
+#define MP_CHECKED_OFFSETOF(type, member, expected_member_type) \
+ (offsetof(type, member) + (0 && MP_EXPECT_TYPE(expected_member_type*, \
+ &((type*)0)->member)))
+
+
+#define OPTION_LIST_SEPARATOR ','
+
+#if HAVE_DOS_PATHS
+#define OPTION_PATH_SEPARATOR ';'
+#else
+#define OPTION_PATH_SEPARATOR ':'
+#endif
+
+#define OPTDEF_STR(s) .defval = (void *)&(char * const){s}
+#define OPTDEF_INT(i) .defval = (void *)&(const int){i}
+#define OPTDEF_FLOAT(f) .defval = (void *)&(const float){f}
+#define OPTDEF_DOUBLE(d) .defval = (void *)&(const double){d}
+
+#define OPT_GENERAL(ctype, optname, varname, flagv, ...) \
+ {.name = optname, .flags = flagv, .is_new_option = 1, \
+ .offset = MP_CHECKED_OFFSETOF(OPT_BASE_STRUCT, varname, ctype), \
+ __VA_ARGS__}
+
+#define OPT_GENERAL_NOTYPE(optname, varname, flagv, ...) \
+ {.name = optname, .flags = flagv, .is_new_option = 1, \
+ .offset = offsetof(OPT_BASE_STRUCT, varname), \
+ __VA_ARGS__}
+
+#define OPT_HELPER_REMOVEPAREN(...) __VA_ARGS__
+
+/* The OPT_FLAG_CONSTANTS->OPT_FLAG_CONSTANTS_ kind of redirection exists to
+ * make the code fully standard-conforming: the C standard requires that
+ * __VA_ARGS__ has at least one argument (though GCC for example would accept
+ * 0). Thus the first OPT_FLAG_CONSTANTS is a wrapper which just adds one
+ * argument to ensure __VA_ARGS__ is not empty when calling the next macro.
+ */
+
+#define OPT_FLAG(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_flag, .max = 1)
+
+#define OPT_FLAG_CONSTANTS_(optname, varname, flags, offvalue, value, ...) \
+ OPT_GENERAL(int, optname, varname, flags, \
+ .min = offvalue, .max = value, __VA_ARGS__)
+#define OPT_FLAG_CONSTANTS(...) \
+ OPT_FLAG_CONSTANTS_(__VA_ARGS__, .type = &m_option_type_flag)
+
+#define OPT_FLAG_STORE(optname, varname, flags, value) \
+ OPT_GENERAL(int, optname, varname, flags, .max = value, \
+ .type = &m_option_type_store)
+
+#define OPT_FLOAT_STORE(optname, varname, flags, value) \
+ OPT_GENERAL(float, optname, varname, flags, .max = value, \
+ .type = &m_option_type_float_store)
+
+#define OPT_STRINGLIST(...) \
+ OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list)
+
+#define OPT_PATHLIST(...) \
+ OPT_GENERAL(char**, __VA_ARGS__, .type = &m_option_type_string_list, \
+ .priv = (void *)&(const char){OPTION_PATH_SEPARATOR})
+
+#define OPT_INT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_int)
+
+#define OPT_INT64(...) \
+ OPT_GENERAL(int64_t, __VA_ARGS__, .type = &m_option_type_int64)
+
+#define OPT_RANGE_(ctype, optname, varname, flags, minval, maxval, ...) \
+ OPT_GENERAL(ctype, optname, varname, (flags) | CONF_RANGE, \
+ .min = minval, .max = maxval, __VA_ARGS__)
+
+#define OPT_INTRANGE(...) \
+ OPT_RANGE_(int, __VA_ARGS__, .type = &m_option_type_int)
+
+#define OPT_FLOATRANGE(...) \
+ OPT_RANGE_(float, __VA_ARGS__, .type = &m_option_type_float)
+
+#define OPT_INTPAIR(...) \
+ OPT_GENERAL_NOTYPE(__VA_ARGS__, .type = &m_option_type_intpair)
+
+#define OPT_FLOAT(...) \
+ OPT_GENERAL(float, __VA_ARGS__, .type = &m_option_type_float)
+
+#define OPT_DOUBLE(...) \
+ OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_double)
+
+#define OPT_STRING(...) \
+ OPT_GENERAL(char*, __VA_ARGS__, .type = &m_option_type_string)
+
+#define OPT_SETTINGSLIST(optname, varname, flags, objlist) \
+ OPT_GENERAL(m_obj_settings_t*, optname, varname, flags, \
+ .type = &m_option_type_obj_settings_list, \
+ .priv = (void*)MP_EXPECT_TYPE(const struct m_obj_list*, objlist))
+
+#define OPT_IMAGEFORMAT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_imgfmt)
+
+#define OPT_AUDIOFORMAT(...) \
+ OPT_GENERAL(int, __VA_ARGS__, .type = &m_option_type_afmt)
+
+#define OPT_CHMAP(...) \
+ OPT_GENERAL(struct mp_chmap, __VA_ARGS__, .type = &m_option_type_chmap)
+
+
+#define M_CHOICES(choices) \
+ .priv = (void *)&(const struct m_opt_choice_alternatives[]){ \
+ OPT_HELPER_REMOVEPAREN choices, {NULL}}
+
+#define OPT_CHOICE(...) \
+ OPT_CHOICE_(__VA_ARGS__, .type = &m_option_type_choice)
+#define OPT_CHOICE_(optname, varname, flags, choices, ...) \
+ OPT_GENERAL(int, optname, varname, flags, M_CHOICES(choices), __VA_ARGS__)
+
+// Union of choices and an int range. The choice values can be included in the
+// int range, or be completely separate - both works.
+#define OPT_CHOICE_OR_INT_(optname, varname, flags, minval, maxval, choices, ...) \
+ OPT_GENERAL(int, optname, varname, (flags) | CONF_RANGE, \
+ .min = minval, .max = maxval, \
+ M_CHOICES(choices), __VA_ARGS__)
+#define OPT_CHOICE_OR_INT(...) \
+ OPT_CHOICE_OR_INT_(__VA_ARGS__, .type = &m_option_type_choice)
+
+#define OPT_TIME(...) \
+ OPT_GENERAL(double, __VA_ARGS__, .type = &m_option_type_time)
+
+#define OPT_REL_TIME(...) \
+ OPT_GENERAL(struct m_rel_time, __VA_ARGS__, .type = &m_option_type_rel_time)
+
+#define OPT_COLOR(...) \
+ OPT_GENERAL(struct m_color, __VA_ARGS__, .type = &m_option_type_color)
+
+#define OPT_GEOMETRY(...) \
+ OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_geometry)
+
+#define OPT_SIZE_BOX(...) \
+ OPT_GENERAL(struct m_geometry, __VA_ARGS__, .type = &m_option_type_size_box)
+
+#define OPT_TRACKCHOICE(name, var) \
+ OPT_CHOICE_OR_INT(name, var, 0, 1, 8190, ({"no", -2}, {"auto", -1}))
+
+#define OPT_STRING_VALIDATE_(optname, varname, flags, validate_fn, ...) \
+ OPT_GENERAL(char*, optname, varname, flags, __VA_ARGS__, \
+ .priv = MP_EXPECT_TYPE(m_opt_string_validate_fn, validate_fn))
+#define OPT_STRING_VALIDATE(...) \
+ OPT_STRING_VALIDATE_(__VA_ARGS__, .type = &m_option_type_string)
+
+// subconf must have the type struct m_sub_options.
+// All sub-options are prefixed with "name-" and are added to the current
+// (containing) option list.
+// If name is "", add the sub-options directly instead.
+// varname refers to the field, that must be a pointer to a field described by
+// the subconf struct.
+#define OPT_SUBSTRUCT(name, varname, subconf, flagv) \
+ OPT_GENERAL_NOTYPE(name, varname, flagv, \
+ .type = &m_option_type_subconfig_struct, \
+ .priv = (void*)&subconf)
+
+#endif /* MPLAYER_M_OPTION_H */
diff --git a/options/m_property.c b/options/m_property.c
new file mode 100644
index 0000000000..38f6a742c9
--- /dev/null
+++ b/options/m_property.c
@@ -0,0 +1,382 @@
+/*
+ * 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 Properties
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <assert.h>
+
+#include <libavutil/common.h>
+
+#include "talloc.h"
+#include "m_option.h"
+#include "m_property.h"
+#include "mpvcore/mp_msg.h"
+#include "mpvcore/mp_common.h"
+
+const struct m_option_type m_option_type_dummy = {
+ .name = "Unknown",
+ .flags = M_OPT_TYPE_ALLOW_WILDCARD, // make "vf*" property work
+};
+
+struct legacy_prop {
+ const char *old, *new;
+};
+static const struct legacy_prop legacy_props[] = {
+ {"switch_video", "video"},
+ {"switch_audio", "audio"},
+ {"switch_program", "program"},
+ {"framedropping", "framedrop"},
+ {"osdlevel", "osd-level"},
+ {0}
+};
+
+static bool translate_legacy_property(const char *name, char *buffer,
+ size_t buffer_size)
+{
+ if (strlen(name) + 1 > buffer_size)
+ return false;
+
+ const char *old_name = name;
+
+ for (int n = 0; legacy_props[n].new; n++) {
+ if (strcmp(name, legacy_props[n].old) == 0) {
+ name = legacy_props[n].new;
+ break;
+ }
+ }
+
+ snprintf(buffer, buffer_size, "%s", name);
+
+ // Old names used "_" instead of "-"
+ for (int n = 0; buffer[n]; n++) {
+ if (buffer[n] == '_')
+ buffer[n] = '-';
+ }
+
+ if (strcmp(old_name, buffer) != 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Warning: property '%s' is deprecated, "
+ "replaced with '%s'. Fix your input.conf!\n", old_name, buffer);
+ }
+
+ return true;
+}
+
+static int do_action(const m_option_t *prop_list, const char *name,
+ int action, void *arg, void *ctx)
+{
+ const char *sep;
+ const m_option_t *prop;
+ struct m_property_action_arg ka;
+ if ((sep = strchr(name, '/')) && sep[1]) {
+ int len = sep - name;
+ char base[len + 1];
+ memcpy(base, name, len);
+ base[len] = 0;
+ prop = m_option_list_find(prop_list, base);
+ ka = (struct m_property_action_arg) {
+ .key = sep + 1,
+ .action = action,
+ .arg = arg,
+ };
+ action = M_PROPERTY_KEY_ACTION;
+ arg = &ka;
+ } else
+ prop = m_option_list_find(prop_list, name);
+ if (!prop)
+ return M_PROPERTY_UNKNOWN;
+ int (*control)(const m_option_t*, int, void*, void*) = prop->p;
+ int r = control(prop, action, arg, ctx);
+ if (action == M_PROPERTY_GET_TYPE && r < 0 &&
+ prop->type != &m_option_type_dummy)
+ {
+ *(struct m_option *)arg = *prop;
+ return M_PROPERTY_OK;
+ }
+ return r;
+}
+
+int m_property_do(const m_option_t *prop_list, const char *in_name,
+ int action, void *arg, void *ctx)
+{
+ union m_option_value val = {0};
+ int r;
+
+ char name[64];
+ if (!translate_legacy_property(in_name, name, sizeof(name)))
+ return M_PROPERTY_UNKNOWN;
+
+ struct m_option opt = {0};
+ r = do_action(prop_list, name, M_PROPERTY_GET_TYPE, &opt, ctx);
+ if (r <= 0)
+ return r;
+ assert(opt.type);
+
+ switch (action) {
+ case M_PROPERTY_PRINT: {
+ if ((r = do_action(prop_list, name, M_PROPERTY_PRINT, arg, ctx)) >= 0)
+ return r;
+ // Fallback to m_option
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ char *str = m_option_pretty_print(&opt, &val);
+ m_option_free(&opt, &val);
+ *(char **)arg = str;
+ return str != NULL;
+ }
+ case M_PROPERTY_GET_STRING: {
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ char *str = m_option_print(&opt, &val);
+ m_option_free(&opt, &val);
+ *(char **)arg = str;
+ return str != NULL;
+ }
+ case M_PROPERTY_SET_STRING: {
+ // (reject 0 return value: success, but empty string with flag)
+ if (m_option_parse(&opt, bstr0(name), bstr0(arg), &val) <= 0)
+ return M_PROPERTY_ERROR;
+ r = do_action(prop_list, name, M_PROPERTY_SET, &val, ctx);
+ m_option_free(&opt, &val);
+ return r;
+ }
+ case M_PROPERTY_SWITCH: {
+ struct m_property_switch_arg *sarg = arg;
+ if ((r = do_action(prop_list, name, M_PROPERTY_SWITCH, arg, ctx)) !=
+ M_PROPERTY_NOT_IMPLEMENTED)
+ return r;
+ // Fallback to m_option
+ if (!opt.type->add)
+ return M_PROPERTY_NOT_IMPLEMENTED;
+ if ((r = do_action(prop_list, name, M_PROPERTY_GET, &val, ctx)) <= 0)
+ return r;
+ opt.type->add(&opt, &val, sarg->inc, sarg->wrap);
+ r = do_action(prop_list, name, M_PROPERTY_SET, &val, ctx);
+ m_option_free(&opt, &val);
+ return r;
+ }
+ case M_PROPERTY_SET: {
+ if (!opt.type->clamp) {
+ mp_msg(MSGT_CPLAYER, MSGL_WARN, "Property '%s' without clamp().\n",
+ name);
+ } else {
+ m_option_copy(&opt, &val, arg);
+ r = opt.type->clamp(&opt, arg);
+ m_option_free(&opt, &val);
+ if (r != 0) {
+ mp_msg(MSGT_CPLAYER, MSGL_ERR,
+ "Property '%s': invalid value.\n", name);
+ return M_PROPERTY_ERROR;
+ }
+ }
+ return do_action(prop_list, name, M_PROPERTY_SET, arg, ctx);
+ }
+ default:
+ return do_action(prop_list, name, action, arg, ctx);
+ }
+}
+
+static int m_property_do_bstr(const m_option_t *prop_list, bstr name,
+ int action, void *arg, void *ctx)
+{
+ char name0[64];
+ if (name.len >= sizeof(name0))
+ return M_PROPERTY_UNKNOWN;
+ snprintf(name0, sizeof(name0), "%.*s", BSTR_P(name));
+ return m_property_do(prop_list, name0, action, arg, ctx);
+}
+
+static void append_str(char **s, int *len, bstr append)
+{
+ MP_TARRAY_GROW(NULL, *s, *len + append.len);
+ memcpy(*s + *len, append.start, append.len);
+ *len = *len + append.len;
+}
+
+static int expand_property(const m_option_t *prop_list, char **ret, int *ret_len,
+ bstr prop, bool silent_error, void *ctx)
+{
+ bool cond_yes = bstr_eatstart0(&prop, "?");
+ bool cond_no = !cond_yes && bstr_eatstart0(&prop, "!");
+ bool test = cond_yes || cond_no;
+ bool raw = bstr_eatstart0(&prop, "=");
+ bstr comp_with = {0};
+ bool comp = test && bstr_split_tok(prop, "==", &prop, &comp_with);
+ if (test && !comp)
+ raw = true;
+ int method = raw ? M_PROPERTY_GET_STRING : M_PROPERTY_PRINT;
+
+ char *s = NULL;
+ int r = m_property_do_bstr(prop_list, prop, method, &s, ctx);
+ bool skip;
+ if (comp) {
+ skip = ((s && bstr_equals0(comp_with, s)) != cond_yes);
+ } else if (test) {
+ skip = (!!s != cond_yes);
+ } else {
+ skip = !!s;
+ char *append = s;
+ if (!s && !silent_error && !raw)
+ append = (r == M_PROPERTY_UNAVAILABLE) ? "(unavailable)" : "(error)";
+ append_str(ret, ret_len, bstr0(append));
+ }
+ talloc_free(s);
+ return skip;
+}
+
+char *m_properties_expand_string(const m_option_t *prop_list,
+ const char *str0, void *ctx)
+{
+ char *ret = NULL;
+ int ret_len = 0;
+ bool skip = false;
+ int level = 0, skip_level = 0;
+ bstr str = bstr0(str0);
+
+ while (str.len) {
+ if (level > 0 && bstr_eatstart0(&str, "}")) {
+ if (skip && level <= skip_level)
+ skip = false;
+ level--;
+ } else if (bstr_startswith0(str, "${") && bstr_find0(str, "}") >= 0) {
+ str = bstr_cut(str, 2);
+ level++;
+
+ // Assume ":" and "}" can't be part of the property name
+ // => if ":" comes before "}", it must be for the fallback
+ int term_pos = bstrcspn(str, ":}");
+ bstr name = bstr_splice(str, 0, term_pos < 0 ? str.len : term_pos);
+ str = bstr_cut(str, term_pos);
+ bool have_fallback = bstr_eatstart0(&str, ":");
+
+ if (!skip) {
+ skip = expand_property(prop_list, &ret, &ret_len, name,
+ have_fallback, ctx);
+ if (skip)
+ skip_level = level;
+ }
+ } else if (level == 0 && bstr_eatstart0(&str, "$>")) {
+ append_str(&ret, &ret_len, str);
+ break;
+ } else {
+ char c;
+
+ // Other combinations, e.g. "$x", are added verbatim
+ if (bstr_eatstart0(&str, "$$")) {
+ c = '$';
+ } else if (bstr_eatstart0(&str, "$}")) {
+ c = '}';
+ } else {
+ c = str.start[0];
+ str = bstr_cut(str, 1);
+ }
+
+ if (!skip)
+ MP_TARRAY_APPEND(NULL, ret, ret_len, c);
+ }
+ }
+
+ MP_TARRAY_APPEND(NULL, ret, ret_len, '\0');
+ return ret;
+}
+
+void m_properties_print_help_list(const m_option_t *list)
+{
+ char min[50], max[50];
+ int i, count = 0;
+
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO,
+ "\n Name Type Min Max\n\n");
+ for (i = 0; list[i].name; i++) {
+ const m_option_t *opt = &list[i];
+ 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);
+ count++;
+ }
+ mp_msg(MSGT_CFGPARSER, MSGL_INFO, "\nTotal: %d properties\n", count);
+}
+
+int m_property_int_ro(const m_option_t *prop, int action,
+ void *arg, int var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(int *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_int64_ro(const struct m_option* prop, int action, void* arg,
+ int64_t var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(int64_t *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_float_ro(const m_option_t *prop, int action,
+ void *arg, float var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(float *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_double_ro(const m_option_t *prop, int action,
+ void *arg, double var)
+{
+ if (action == M_PROPERTY_GET) {
+ *(double *)arg = var;
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
+
+int m_property_strdup_ro(const struct m_option* prop, int action, void* arg,
+ const char *var)
+{
+ if (action == M_PROPERTY_GET) {
+ if (!var)
+ return M_PROPERTY_UNAVAILABLE;
+ *(char **)arg = talloc_strdup(NULL, var);
+ return M_PROPERTY_OK;
+ }
+ return M_PROPERTY_NOT_IMPLEMENTED;
+}
diff --git a/options/m_property.h b/options/m_property.h
new file mode 100644
index 0000000000..8398ad321f
--- /dev/null
+++ b/options/m_property.h
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_M_PROPERTY_H
+#define MPLAYER_M_PROPERTY_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct m_option;
+
+extern const struct m_option_type m_option_type_dummy;
+
+enum mp_property_action {
+ // Get the property type. This defines the fundamental data type read from
+ // or written to the property.
+ // If unimplemented, the m_option entry that defines the property is used.
+ // arg: m_option*
+ M_PROPERTY_GET_TYPE,
+
+ // Get the current value.
+ // arg: pointer to a variable of the type according to the property type
+ M_PROPERTY_GET,
+
+ // Set a new value. The property wrapper will make sure that only valid
+ // values are set (e.g. according to the property type's min/max range).
+ // If unimplemented, the property is read-only.
+ // arg: pointer to a variable of the type according to the property type
+ M_PROPERTY_SET,
+
+ // Get human readable string representing the current value.
+ // If unimplemented, the property wrapper uses the property type as
+ // fallback.
+ // arg: char**
+ M_PROPERTY_PRINT,
+
+ // Switch the property up/down by a given value.
+ // If unimplemented, the property wrapper uses the property type as
+ // fallback.
+ // arg: struct m_property_switch_arg*
+ M_PROPERTY_SWITCH,
+
+ // Get a string containing a parsable representation.
+ // Can't be overridden by property implementations.
+ // arg: char**
+ M_PROPERTY_GET_STRING,
+
+ // Set a new value from a string. The property wrapper parses this using the
+ // parse function provided by the property type.
+ // Can't be overridden by property implementations.
+ // arg: char*
+ M_PROPERTY_SET_STRING,
+
+ // Pass down an action to a sub-property.
+ // arg: struct m_property_action_arg*
+ M_PROPERTY_KEY_ACTION,
+};
+
+// Argument for M_PROPERTY_SWITCH
+struct m_property_switch_arg {
+ double inc; // value to add to property, or cycle direction
+ bool wrap; // whether value should wrap around on over/underflow
+};
+
+// Argument for M_PROPERTY_KEY_ACTION
+struct m_property_action_arg {
+ const char* key;
+ int action;
+ void* arg;
+};
+
+enum mp_property_return {
+ // Returned on success.
+ M_PROPERTY_OK = 1,
+
+ // Returned on error.
+ M_PROPERTY_ERROR = 0,
+
+ // Returned when the property can't be used, for example video related
+ // properties while playing audio only.
+ M_PROPERTY_UNAVAILABLE = -1,
+
+ // Returned if the requested action is not implemented.
+ M_PROPERTY_NOT_IMPLEMENTED = -2,
+
+ // Returned when asking for a property that doesn't exist.
+ M_PROPERTY_UNKNOWN = -3,
+};
+
+// Access a property.
+// action: one of m_property_action
+// ctx: opaque value passed through to property implementation
+// returns: one of mp_property_return
+int m_property_do(const struct m_option* prop_list, const char* property_name,
+ int action, void* arg, void *ctx);
+
+// Print a list of properties.
+void m_properties_print_help_list(const struct m_option* list);
+
+// Expand a property string.
+// This function allows to print strings containing property values.
+// ${NAME} is expanded to the value of property NAME.
+// If NAME starts with '=', use the raw value of the property.
+// ${NAME:STR} expands to the property, or STR if the property is not
+// available.
+// ${?NAME:STR} expands to STR if the property is available.
+// ${!NAME:STR} expands to STR if the property is not available.
+// General syntax: "${" ["?" | "!"] ["="] NAME ":" STR "}"
+// STR is recursively expanded using the same rules.
+// "$$" can be used to escape "$", and "$}" to escape "}".
+// "$>" disables parsing of "$" for the rest of the string.
+char* m_properties_expand_string(const struct m_option* prop_list,
+ const char *str, void *ctx);
+
+// Trivial helpers for implementing properties.
+int m_property_int_ro(const struct m_option* prop, int action, void* arg,
+ int var);
+int m_property_int64_ro(const struct m_option* prop, int action, void* arg,
+ int64_t var);
+int m_property_float_ro(const struct m_option* prop, int action, void* arg,
+ float var);
+int m_property_double_ro(const struct m_option* prop, int action, void* arg,
+ double var);
+int m_property_strdup_ro(const struct m_option* prop, int action, void* arg,
+ const char *var);
+
+#endif /* MPLAYER_M_PROPERTY_H */
diff --git a/options/options.c b/options/options.c
new file mode 100644
index 0000000000..e3d13076f4
--- /dev/null
+++ b/options/options.c
@@ -0,0 +1,886 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_CFG_MPLAYER_H
+#define MPLAYER_CFG_MPLAYER_H
+
+/*
+ * config for cfgparser
+ */
+
+#include <stddef.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#include "options.h"
+#include "config.h"
+#include "version.h"
+#include "m_config.h"
+#include "m_option.h"
+#include "stream/tv.h"
+#include "stream/stream_radio.h"
+#include "video/csputils.h"
+#include "sub/osd.h"
+#include "audio/mixer.h"
+#include "audio/filter/af.h"
+#include "audio/decode/dec_audio.h"
+#include "player/core.h"
+#include "osdep/priority.h"
+
+int network_bandwidth=0;
+int network_cookies_enabled = 0;
+char *network_useragent="mpv " VERSION;
+char *network_referrer=NULL;
+char **network_http_header_fields=NULL;
+int network_tls_verify;
+char *network_tls_ca_file;
+
+extern char *lirc_configfile;
+
+extern int mp_msg_color;
+extern int mp_msg_module;
+
+/* defined in demux: */
+extern const m_option_t demux_rawaudio_opts[];
+extern const m_option_t demux_rawvideo_opts[];
+extern const m_option_t cdda_opts[];
+
+extern int sws_flags;
+
+extern const char mp_help_text[];
+
+static int print_version_opt(const m_option_t *opt, const char *name,
+ const char *param)
+{
+ mp_print_version(true);
+ exit(0);
+}
+
+#if HAVE_RADIO
+static const m_option_t radioopts_conf[]={
+ {"device", &stream_radio_defaults.device, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"driver", &stream_radio_defaults.driver, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"channels", &stream_radio_defaults.channels, CONF_TYPE_STRING_LIST, 0, 0 ,0, NULL},
+ {"volume", &stream_radio_defaults.volume, CONF_TYPE_INT, CONF_RANGE, 0 ,100, NULL},
+ {"adevice", &stream_radio_defaults.adevice, CONF_TYPE_STRING, 0, 0 ,0, NULL},
+ {"arate", &stream_radio_defaults.arate, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL},
+ {"achannels", &stream_radio_defaults.achannels, CONF_TYPE_INT, CONF_MIN, 0 ,0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* HAVE_RADIO */
+
+#if HAVE_TV
+static const m_option_t tvopts_conf[]={
+ {"immediatemode", &stream_tv_defaults.immediate, CONF_TYPE_INT, CONF_RANGE, 0, 1, NULL},
+ {"audio", &stream_tv_defaults.noaudio, CONF_TYPE_FLAG, 0, 1, 0, NULL},
+ {"audiorate", &stream_tv_defaults.audiorate, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"driver", &stream_tv_defaults.driver, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"device", &stream_tv_defaults.device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"freq", &stream_tv_defaults.freq, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"channel", &stream_tv_defaults.channel, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"chanlist", &stream_tv_defaults.chanlist, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"norm", &stream_tv_defaults.norm, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"automute", &stream_tv_defaults.automute, CONF_TYPE_INT, CONF_RANGE, 0, 255, NULL},
+#if HAVE_TV_V4L2
+ {"normid", &stream_tv_defaults.normid, CONF_TYPE_INT, 0, 0, 0, NULL},
+#endif
+ {"width", &stream_tv_defaults.width, CONF_TYPE_INT, 0, 0, 4096, NULL},
+ {"height", &stream_tv_defaults.height, CONF_TYPE_INT, 0, 0, 4096, NULL},
+ {"input", &stream_tv_defaults.input, CONF_TYPE_INT, 0, 0, 20, NULL},
+ {"outfmt", &stream_tv_defaults.outfmt, CONF_TYPE_FOURCC, 0, 0, 0, NULL},
+ {"fps", &stream_tv_defaults.fps, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"channels", &stream_tv_defaults.channels, CONF_TYPE_STRING_LIST, 0, 0, 0, NULL},
+ {"brightness", &stream_tv_defaults.brightness, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"contrast", &stream_tv_defaults.contrast, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"hue", &stream_tv_defaults.hue, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"saturation", &stream_tv_defaults.saturation, CONF_TYPE_INT, CONF_RANGE, -100, 100, NULL},
+ {"gain", &stream_tv_defaults.gain, CONF_TYPE_INT, CONF_RANGE, -1, 100, NULL},
+#if HAVE_TV_V4L2
+ {"amode", &stream_tv_defaults.amode, CONF_TYPE_INT, CONF_RANGE, 0, 3, NULL},
+ {"volume", &stream_tv_defaults.volume, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"bass", &stream_tv_defaults.bass, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"treble", &stream_tv_defaults.treble, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"balance", &stream_tv_defaults.balance, CONF_TYPE_INT, CONF_RANGE, 0, 65535, NULL},
+ {"forcechan", &stream_tv_defaults.forcechan, CONF_TYPE_INT, CONF_RANGE, 1, 2, NULL},
+ {"forceaudio", &stream_tv_defaults.force_audio, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"buffersize", &stream_tv_defaults.buffer_size, CONF_TYPE_INT, CONF_RANGE, 16, 1024, NULL},
+ {"mjpeg", &stream_tv_defaults.mjpeg, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"decimation", &stream_tv_defaults.decimation, CONF_TYPE_INT, CONF_RANGE, 1, 4, NULL},
+ {"quality", &stream_tv_defaults.quality, CONF_TYPE_INT, CONF_RANGE, 0, 100, NULL},
+#if HAVE_ALSA
+ {"alsa", &stream_tv_defaults.alsa, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+#endif /* HAVE_ALSA */
+#endif /* HAVE_TV_V4L2 */
+ {"adevice", &stream_tv_defaults.adevice, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"audioid", &stream_tv_defaults.audio_id, CONF_TYPE_INT, CONF_RANGE, 0, 9, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* HAVE_TV */
+
+extern int pvr_param_aspect_ratio;
+extern int pvr_param_sample_rate;
+extern int pvr_param_audio_layer;
+extern int pvr_param_audio_bitrate;
+extern char *pvr_param_audio_mode;
+extern int pvr_param_bitrate;
+extern char *pvr_param_bitrate_mode;
+extern int pvr_param_bitrate_peak;
+extern char *pvr_param_stream_type;
+
+#if HAVE_PVR
+static const m_option_t pvropts_conf[]={
+ {"aspect", &pvr_param_aspect_ratio, CONF_TYPE_INT, 0, 1, 4, NULL},
+ {"arate", &pvr_param_sample_rate, CONF_TYPE_INT, 0, 32000, 48000, NULL},
+ {"alayer", &pvr_param_audio_layer, CONF_TYPE_INT, 0, 1, 2, NULL},
+ {"abitrate", &pvr_param_audio_bitrate, CONF_TYPE_INT, 0, 32, 448, NULL},
+ {"amode", &pvr_param_audio_mode, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"vbitrate", &pvr_param_bitrate, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"vmode", &pvr_param_bitrate_mode, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"vpeak", &pvr_param_bitrate_peak, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"fmt", &pvr_param_stream_type, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif /* HAVE_PVR */
+
+extern const m_option_t dvbin_opts_conf[];
+extern const m_option_t lavfdopts_conf[];
+
+extern int sws_chr_vshift;
+extern int sws_chr_hshift;
+extern float sws_chr_gblur;
+extern float sws_lum_gblur;
+extern float sws_chr_sharpen;
+extern float sws_lum_sharpen;
+
+static const m_option_t scaler_filter_conf[]={
+ {"lgb", &sws_lum_gblur, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"cgb", &sws_chr_gblur, CONF_TYPE_FLOAT, 0, 0, 100.0, NULL},
+ {"cvs", &sws_chr_vshift, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"chs", &sws_chr_hshift, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"ls", &sws_lum_sharpen, CONF_TYPE_FLOAT, 0, -100.0, 100.0, NULL},
+ {"cs", &sws_chr_sharpen, CONF_TYPE_FLOAT, 0, -100.0, 100.0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+extern double mf_fps;
+extern char * mf_type;
+extern const struct m_obj_list vf_obj_list;
+extern const struct m_obj_list af_obj_list;
+extern const struct m_obj_list vo_obj_list;
+extern const struct m_obj_list ao_obj_list;
+
+static const m_option_t mfopts_conf[]={
+ {"fps", &mf_fps, CONF_TYPE_DOUBLE, 0, 0, 0, NULL},
+ {"type", &mf_type, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+extern int mp_msg_levels[MSGT_MAX];
+extern int mp_msg_level_all;
+
+static const m_option_t msgl_config[]={
+ { "all", &mp_msg_level_all, CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL},
+
+ { "global", &mp_msg_levels[MSGT_GLOBAL], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cplayer", &mp_msg_levels[MSGT_CPLAYER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vo", &mp_msg_levels[MSGT_VO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ao", &mp_msg_levels[MSGT_AO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "demuxer", &mp_msg_levels[MSGT_DEMUXER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ds", &mp_msg_levels[MSGT_DS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "demux", &mp_msg_levels[MSGT_DEMUX], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "header", &mp_msg_levels[MSGT_HEADER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "avsync", &mp_msg_levels[MSGT_AVSYNC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "autoq", &mp_msg_levels[MSGT_AUTOQ], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cfgparser", &mp_msg_levels[MSGT_CFGPARSER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "decaudio", &mp_msg_levels[MSGT_DECAUDIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "decvideo", &mp_msg_levels[MSGT_DECVIDEO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "seek", &mp_msg_levels[MSGT_SEEK], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "win32", &mp_msg_levels[MSGT_WIN32], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "open", &mp_msg_levels[MSGT_OPEN], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "dvd", &mp_msg_levels[MSGT_DVD], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "parsees", &mp_msg_levels[MSGT_PARSEES], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "lirc", &mp_msg_levels[MSGT_LIRC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "stream", &mp_msg_levels[MSGT_STREAM], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cache", &mp_msg_levels[MSGT_CACHE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "encode", &mp_msg_levels[MSGT_ENCODE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "xacodec", &mp_msg_levels[MSGT_XACODEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "tv", &mp_msg_levels[MSGT_TV], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "radio", &mp_msg_levels[MSGT_RADIO], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "osdep", &mp_msg_levels[MSGT_OSDEP], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "spudec", &mp_msg_levels[MSGT_SPUDEC], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "playtree", &mp_msg_levels[MSGT_PLAYTREE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "input", &mp_msg_levels[MSGT_INPUT], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vfilter", &mp_msg_levels[MSGT_VFILTER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "osd", &mp_msg_levels[MSGT_OSD], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "network", &mp_msg_levels[MSGT_NETWORK], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "cpudetect", &mp_msg_levels[MSGT_CPUDETECT], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "codeccfg", &mp_msg_levels[MSGT_CODECCFG], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "sws", &mp_msg_levels[MSGT_SWS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "vobsub", &mp_msg_levels[MSGT_VOBSUB], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "subreader", &mp_msg_levels[MSGT_SUBREADER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "afilter", &mp_msg_levels[MSGT_AFILTER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "netst", &mp_msg_levels[MSGT_NETST], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "muxer", &mp_msg_levels[MSGT_MUXER], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "identify", &mp_msg_levels[MSGT_IDENTIFY], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "ass", &mp_msg_levels[MSGT_ASS], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "statusline", &mp_msg_levels[MSGT_STATUSLINE], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ { "fixme", &mp_msg_levels[MSGT_FIXME], CONF_TYPE_INT, CONF_RANGE, -1, 9, NULL },
+ {"help", "Available msg modules:\n"
+ " global - common player errors/information\n"
+ " cplayer - console player (mplayer.c)\n"
+ " vo - libvo\n"
+ " ao - libao\n"
+ " demuxer - demuxer.c (general stuff)\n"
+ " ds - demux stream (add/read packet etc)\n"
+ " demux - fileformat-specific stuff (demux_*.c)\n"
+ " header - fileformat-specific header (*header.c)\n"
+ " avsync - mplayer.c timer stuff\n"
+ " autoq - mplayer.c auto-quality stuff\n"
+ " cfgparser - cfgparser.c\n"
+ " decaudio - av decoder\n"
+ " decvideo\n"
+ " seek - seeking code\n"
+ " win32 - win32 dll stuff\n"
+ " open - open.c (stream opening)\n"
+ " dvd - open.c (DVD init/read/seek)\n"
+ " parsees - parse_es.c (mpeg stream parser)\n"
+ " lirc - lirc_mp.c and input lirc driver\n"
+ " stream - stream.c\n"
+ " cache - cache2.c\n"
+ " encode - encode_lavc.c and associated vo/ao drivers\n"
+ " xacodec - XAnim codecs\n"
+ " tv - TV input subsystem\n"
+ " osdep - OS-dependent parts\n"
+ " spudec - spudec.c\n"
+ " playtree - Playtree handling (playtree.c, playtreeparser.c)\n"
+ " input\n"
+ " vfilter\n"
+ " osd\n"
+ " network\n"
+ " cpudetect\n"
+ " codeccfg\n"
+ " sws\n"
+ " vobsub\n"
+ " subreader\n"
+ " afilter - Audio filter messages\n"
+ " netst - Netstream\n"
+ " muxer - muxer layer\n"
+ " identify - identify output\n"
+ " ass - libass messages\n"
+ " statusline - playback/encoding status line\n"
+ " fixme - messages not yet fixed to map to module\n"
+ "\n", CONF_TYPE_PRINT, CONF_GLOBAL | CONF_NOCFG, 0, 0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+
+};
+
+#if HAVE_TV
+static const m_option_t tvscan_conf[]={
+ {"autostart", &stream_tv_defaults.scan, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"threshold", &stream_tv_defaults.scan_threshold, CONF_TYPE_INT, CONF_RANGE, 1, 100, NULL},
+ {"period", &stream_tv_defaults.scan_period, CONF_TYPE_FLOAT, CONF_RANGE, 0.1, 2.0, NULL},
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+#endif
+
+#define OPT_BASE_STRUCT struct MPOpts
+
+extern const struct m_sub_options image_writer_conf;
+
+static const m_option_t screenshot_conf[] = {
+ OPT_SUBSTRUCT("", screenshot_image_opts, image_writer_conf, 0),
+ OPT_STRING("template", screenshot_template, 0),
+ {0},
+};
+
+extern const m_option_t lavc_decode_opts_conf[];
+extern const m_option_t ad_lavc_decode_opts_conf[];
+
+extern const m_option_t mp_input_opts[];
+
+const m_option_t mp_opts[] = {
+ // handled in command line pre-parser (parser-mpcmd.c)
+ {"v", NULL, CONF_TYPE_STORE, CONF_GLOBAL | CONF_NOCFG, 0, 0, NULL},
+
+ // handled in command line parser (parser-mpcmd.c)
+ {"playlist", NULL, CONF_TYPE_STRING, CONF_NOCFG | M_OPT_MIN, 1, 0, NULL},
+ {"{", NULL, CONF_TYPE_STORE, CONF_NOCFG, 0, 0, NULL},
+ {"}", NULL, CONF_TYPE_STORE, CONF_NOCFG, 0, 0, NULL},
+
+ // handled in m_config.c
+ { "include", NULL, CONF_TYPE_STRING },
+ { "profile", NULL, CONF_TYPE_STRING_LIST },
+ { "show-profile", NULL, CONF_TYPE_STRING, CONF_NOCFG },
+ { "list-options", NULL, CONF_TYPE_STORE, CONF_NOCFG },
+
+ // handled in mplayer.c (looks at the raw argv[])
+ {"leak-report", NULL, CONF_TYPE_STORE, CONF_GLOBAL | CONF_NOCFG },
+
+ OPT_FLAG("shuffle", shuffle, CONF_GLOBAL | CONF_NOCFG),
+
+// ------------------------- common options --------------------
+ OPT_FLAG("quiet", quiet, CONF_GLOBAL),
+ {"really-quiet", &verbose, CONF_TYPE_STORE, CONF_GLOBAL|CONF_PRE_PARSE, 0, -10, NULL},
+ {"msglevel", (void *) msgl_config, CONF_TYPE_SUBCONFIG, CONF_GLOBAL, 0, 0, NULL},
+ {"msgcolor", &mp_msg_color, CONF_TYPE_FLAG, CONF_GLOBAL | CONF_PRE_PARSE, 0, 1, NULL},
+ {"msgmodule", &mp_msg_module, CONF_TYPE_FLAG, CONF_GLOBAL, 0, 1, NULL},
+#if HAVE_PRIORITY
+ {"priority", &proc_priority, CONF_TYPE_STRING, 0, 0, 0, NULL},
+#endif
+ OPT_FLAG("config", load_config, CONF_GLOBAL | CONF_NOCFG | CONF_PRE_PARSE),
+ OPT_STRINGLIST("reset-on-next-file", reset_options, CONF_GLOBAL),
+
+#if HAVE_LUA
+ OPT_STRINGLIST("lua", lua_files, CONF_GLOBAL),
+ OPT_FLAG("osc", lua_load_osc, CONF_GLOBAL),
+#endif
+
+// ------------------------- stream options --------------------
+
+ OPT_CHOICE_OR_INT("cache", stream_cache_size, 0, 32, 0x7fffffff,
+ ({"no", 0},
+ {"auto", -1}),
+ OPTDEF_INT(-1)),
+ OPT_CHOICE_OR_INT("cache-default", stream_cache_def_size, 0, 32, 0x7fffffff,
+ ({"no", 0}),
+ OPTDEF_INT(320)),
+ OPT_FLOATRANGE("cache-min", stream_cache_min_percent, 0, 0, 99),
+ OPT_FLOATRANGE("cache-seek-min", stream_cache_seek_min_percent, 0, 0, 99),
+ OPT_CHOICE_OR_INT("cache-pause", stream_cache_pause, 0,
+ 0, 40, ({"no", -1})),
+
+ {"cdrom-device", &cdrom_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+#if HAVE_DVDREAD || HAVE_DVDNAV
+ {"dvd-device", &dvd_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"dvd-speed", &dvd_speed, CONF_TYPE_INT, 0, 0, 0, NULL},
+ {"dvdangle", &dvd_angle, CONF_TYPE_INT, CONF_RANGE, 1, 99, NULL},
+#endif /* HAVE_DVDREAD */
+ OPT_INTPAIR("chapter", chapterrange, 0),
+ OPT_CHOICE_OR_INT("edition", edition_id, 0, 0, 8190,
+ ({"auto", -1})),
+#if HAVE_LIBBLURAY
+ {"bluray-device", &bluray_device, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"bluray-angle", &bluray_angle, CONF_TYPE_INT, CONF_RANGE, 0, 999, NULL},
+#endif /* HAVE_LIBBLURAY */
+
+ {"http-header-fields", &network_http_header_fields, CONF_TYPE_STRING_LIST, 0, 0, 0, NULL},
+ {"user-agent", &network_useragent, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"referrer", &network_referrer, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ {"cookies", &network_cookies_enabled, CONF_TYPE_FLAG, 0, 0, 1, NULL},
+ {"cookies-file", &cookies_file, CONF_TYPE_STRING, 0, 0, 0, NULL},
+ OPT_CHOICE("rtsp-transport", network_rtsp_transport, 0,
+ ({"lavf", 0},
+ {"udp", 1},
+ {"tcp", 2},
+ {"http", 3})),
+ {"tls-verify", &network_tls_verify, CONF_TYPE_FLAG, 0, 0, 0, NULL},
+ {"tls-ca-file", &network_tls_ca_file, CONF_TYPE_STRING, 0, 0, 0, NULL},
+
+// ------------------------- demuxer options --------------------
+
+ OPT_CHOICE_OR_INT("frames", play_frames, 0, 0, INT_MAX,
+ ({"all", -1})),
+
+ // seek to byte/seconds position
+ OPT_INT64("sb", seek_to_byte, 0),
+ OPT_REL_TIME("start", play_start, 0),
+ OPT_REL_TIME("end", play_end, 0),
+ OPT_REL_TIME("length", play_length, 0),
+
+ OPT_FLAG("pause", pause, 0),
+ OPT_FLAG("keep-open", keep_open, 0),
+
+ // AVI and Ogg only: (re)build index at startup
+ OPT_FLAG_CONSTANTS("idx", index_mode, 0, -1, 1),
+ OPT_FLAG_STORE("forceidx", index_mode, 0, 2),
+
+ // select audio/video/subtitle stream
+ OPT_TRACKCHOICE("aid", audio_id),
+ OPT_TRACKCHOICE("vid", video_id),
+ OPT_TRACKCHOICE("sid", sub_id),
+ OPT_FLAG_STORE("no-sub", sub_id, 0, -2),
+ OPT_FLAG_STORE("no-video", video_id, 0, -2),
+ OPT_FLAG_STORE("no-audio", audio_id, 0, -2),
+ OPT_STRINGLIST("alang", audio_lang, 0),
+ OPT_STRINGLIST("slang", sub_lang, 0),
+
+ OPT_CHOICE("audio-display", audio_display, 0,
+ ({"no", 0}, {"attachment", 1})),
+
+ OPT_STRING("quvi-format", quvi_format, 0),
+
+#if HAVE_CDDA
+ { "cdda", (void *)&cdda_opts, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif
+
+ // demuxer.c - select audio/sub file/demuxer
+ OPT_STRING("audiofile", audio_stream, 0),
+ OPT_INTRANGE("audiofile-cache", audio_stream_cache, 0, 50, 65536),
+ OPT_STRING("demuxer", demuxer_name, 0),
+ OPT_STRING("audio-demuxer", audio_demuxer_name, 0),
+ OPT_STRING("sub-demuxer", sub_demuxer_name, 0),
+
+ {"mf", (void *) mfopts_conf, CONF_TYPE_SUBCONFIG, 0,0,0, NULL},
+#if HAVE_RADIO
+ {"radio", (void *) radioopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* HAVE_RADIO */
+#if HAVE_TV
+ {"tv", (void *) tvopts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* HAVE_TV */
+#if HAVE_PVR
+ {"pvr", (void *) pvropts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* HAVE_PVR */
+#if HAVE_DVBIN
+ {"dvbin", (void *) dvbin_opts_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif
+
+// ------------------------- a-v sync options --------------------
+
+ // set A-V sync correction speed (0=disables it):
+ OPT_FLOATRANGE("mc", default_max_pts_correction, 0, 0, 100),
+
+ // force video/audio rate:
+ OPT_DOUBLE("fps", force_fps, CONF_MIN, 0),
+ OPT_INTRANGE("srate", force_srate, 0, 1000, 8*48000),
+ OPT_CHMAP("channels", audio_output_channels, CONF_MIN, .min = 1),
+ OPT_AUDIOFORMAT("format", audio_output_format, 0),
+ OPT_DOUBLE("speed", playback_speed, M_OPT_RANGE, .min = 0.01, .max = 100.0),
+
+ // set a-v distance
+ OPT_FLOATRANGE("audio-delay", audio_delay, 0, -100.0, 100.0),
+
+// ------------------------- codec/vfilter options --------------------
+
+ OPT_SETTINGSLIST("af-defaults", af_defs, 0, &af_obj_list),
+ OPT_SETTINGSLIST("af*", af_settings, 0, &af_obj_list),
+ OPT_SETTINGSLIST("vf-defaults", vf_defs, 0, &vf_obj_list),
+ OPT_SETTINGSLIST("vf*", vf_settings, 0, &vf_obj_list),
+
+ OPT_CHOICE("deinterlace", deinterlace, M_OPT_OPTIONAL_PARAM,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1}, {"", 1})),
+
+ OPT_STRING("ad", audio_decoders, 0),
+ OPT_STRING("vd", video_decoders, 0),
+
+ OPT_FLAG("ad-spdif-dtshd", dtshd, 0),
+ OPT_FLAG("dtshd", dtshd, 0), // old alias
+
+ OPT_CHOICE("hwdec", hwdec_api, 0,
+ ({"no", 0},
+ {"auto", -1},
+ {"vdpau", 1},
+ {"vda", 2},
+ {"vaapi", 4},
+ {"vaapi-copy", 5})),
+ OPT_STRING("hwdec-codecs", hwdec_codecs, 0),
+
+ // scaling:
+ {"sws", &sws_flags, CONF_TYPE_INT, 0, 0, 2, NULL},
+ {"ssf", (void *) scaler_filter_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+ // -1 means auto aspect (prefer container size until aspect change)
+ // 0 means square pixels
+ OPT_FLOATRANGE("aspect", movie_aspect, 0, -1.0, 10.0),
+ OPT_FLOAT_STORE("no-aspect", movie_aspect, 0, 0.0),
+
+ OPT_CHOICE("field-dominance", field_dominance, 0,
+ ({"auto", -1}, {"top", 0}, {"bottom", 1})),
+
+ {"vd-lavc", (void *) lavc_decode_opts_conf, CONF_TYPE_SUBCONFIG},
+ {"ad-lavc", (void *) ad_lavc_decode_opts_conf, CONF_TYPE_SUBCONFIG},
+
+ {"demuxer-lavf", (void *) lavfdopts_conf, CONF_TYPE_SUBCONFIG},
+ {"demuxer-rawaudio", (void *)&demux_rawaudio_opts, CONF_TYPE_SUBCONFIG},
+ {"demuxer-rawvideo", (void *)&demux_rawvideo_opts, CONF_TYPE_SUBCONFIG},
+
+ OPT_FLAG("demuxer-mkv-subtitle-preroll", mkv_subtitle_preroll, 0),
+ OPT_FLAG("mkv-subtitle-preroll", mkv_subtitle_preroll, 0), // old alias
+
+// ------------------------- subtitles options --------------------
+
+ OPT_STRINGLIST("sub", sub_name, 0),
+ OPT_PATHLIST("sub-paths", sub_paths, 0),
+ OPT_STRING("subcp", sub_cp, 0),
+ OPT_FLOAT("sub-delay", sub_delay, 0),
+ OPT_FLOAT("subfps", sub_fps, 0),
+ OPT_FLOAT("sub-speed", sub_speed, 0),
+ OPT_FLAG("autosub", sub_auto, 0),
+ OPT_FLAG("sub-visibility", sub_visibility, 0),
+ OPT_FLAG("sub-forced-only", forced_subs_only, 0),
+ OPT_FLAG("stretch-dvd-subs", stretch_dvd_subs, 0),
+ OPT_FLAG_CONSTANTS("sub-fix-timing", suboverlap_enabled, 0, 1, 0),
+ OPT_CHOICE("autosub-match", sub_match_fuzziness, 0,
+ ({"exact", 0}, {"fuzzy", 1}, {"all", 2})),
+ OPT_INTRANGE("sub-pos", sub_pos, 0, 0, 100),
+ OPT_FLOATRANGE("sub-gauss", sub_gauss, 0, 0.0, 3.0),
+ OPT_FLAG("sub-gray", sub_gray, 0),
+ OPT_FLAG("ass", ass_enabled, 0),
+ OPT_FLOATRANGE("sub-scale", sub_scale, 0, 0, 100),
+ OPT_FLOATRANGE("ass-line-spacing", ass_line_spacing, 0, -1000, 1000),
+ OPT_FLAG("ass-use-margins", ass_use_margins, 0),
+ OPT_FLAG("ass-vsfilter-aspect-compat", ass_vsfilter_aspect_compat, 0),
+ OPT_CHOICE("ass-vsfilter-color-compat", ass_vsfilter_color_compat, 0,
+ ({"no", 0}, {"basic", 1}, {"full", 2}, {"force-601", 3})),
+ OPT_FLAG("ass-vsfilter-blur-compat", ass_vsfilter_blur_compat, 0),
+ OPT_FLAG("embeddedfonts", use_embedded_fonts, 0),
+ OPT_STRINGLIST("ass-force-style", ass_force_style_list, 0),
+ OPT_STRING("ass-styles", ass_styles_file, 0),
+ OPT_CHOICE("ass-hinting", ass_hinting, 0,
+ ({"none", 0}, {"light", 1}, {"normal", 2}, {"native", 3})),
+ OPT_CHOICE("ass-shaper", ass_shaper, 0,
+ ({"simple", 0}, {"complex", 1})),
+ OPT_CHOICE("ass-style-override", ass_style_override, 0,
+ ({"no", 0}, {"yes", 1})),
+ OPT_FLAG("osd-bar", osd_bar_visible, 0),
+ OPT_FLOATRANGE("osd-bar-align-x", osd_bar_align_x, 0, -1.0, +1.0),
+ OPT_FLOATRANGE("osd-bar-align-y", osd_bar_align_y, 0, -1.0, +1.0),
+ OPT_FLOATRANGE("osd-bar-w", osd_bar_w, 0, 1, 100),
+ OPT_FLOATRANGE("osd-bar-h", osd_bar_h, 0, 0.1, 50),
+ OPT_SUBSTRUCT("osd", osd_style, osd_style_conf, 0),
+ OPT_SUBSTRUCT("sub-text", sub_text_style, osd_style_conf, 0),
+
+//---------------------- libao/libvo options ------------------------
+ OPT_SETTINGSLIST("vo", vo.video_driver_list, 0, &vo_obj_list),
+ OPT_SETTINGSLIST("vo-defaults", vo.vo_defs, 0, &vo_obj_list),
+ OPT_SETTINGSLIST("ao", audio_driver_list, 0, &ao_obj_list),
+ OPT_SETTINGSLIST("ao-defaults", ao_defs, 0, &ao_obj_list),
+ OPT_FLAG("fixed-vo", fixed_vo, CONF_GLOBAL),
+ OPT_FLAG("force-window", force_vo, CONF_GLOBAL),
+ OPT_FLAG("ontop", vo.ontop, 0),
+ OPT_FLAG("border", vo.border, 0),
+
+ OPT_CHOICE("softvol", softvol, 0,
+ ({"no", SOFTVOL_NO},
+ {"yes", SOFTVOL_YES},
+ {"auto", SOFTVOL_AUTO})),
+ OPT_FLOATRANGE("softvol-max", softvol_max, 0, 10, 10000),
+ OPT_INTRANGE("volstep", volstep, 0, 0, 100),
+ OPT_FLOATRANGE("volume", mixer_init_volume, 0, -1, 100),
+ OPT_CHOICE("mute", mixer_init_mute, M_OPT_OPTIONAL_PARAM,
+ ({"auto", -1},
+ {"no", 0},
+ {"yes", 1}, {"", 1})),
+ OPT_STRING("volume-restore-data", mixer_restore_volume_data, 0),
+ OPT_FLAG("gapless-audio", gapless_audio, 0),
+
+ // set screen dimensions (when not detectable or virtual!=visible)
+ OPT_INTRANGE("screenw", vo.screenwidth, CONF_GLOBAL, 0, 4096),
+ OPT_INTRANGE("screenh", vo.screenheight, CONF_GLOBAL, 0, 4096),
+ OPT_GEOMETRY("geometry", vo.geometry, 0),
+ OPT_SIZE_BOX("autofit", vo.autofit, 0),
+ OPT_SIZE_BOX("autofit-larger", vo.autofit_larger, 0),
+ OPT_FLAG("force-window-position", vo.force_window_position, 0),
+ // vo name (X classname) and window title strings
+ OPT_STRING("name", vo.winname, 0),
+ OPT_STRING("title", wintitle, 0),
+ // set aspect ratio of monitor - useful for 16:9 TV-out
+ OPT_FLOATRANGE("monitoraspect", vo.force_monitor_aspect, 0, 0.0, 9.0),
+ OPT_FLOATRANGE("monitorpixelaspect", vo.monitor_pixel_aspect, 0, 0.2, 9.0),
+ // start in fullscreen mode:
+ OPT_FLAG("fullscreen", vo.fullscreen, 0),
+ OPT_FLAG("fs", vo.fullscreen, 0),
+ // set fullscreen switch method (workaround for buggy WMs)
+ OPT_INTRANGE("fsmode-dontuse", vo.fsmode, 0, 31, 4096),
+ OPT_FLAG("native-keyrepeat", vo.native_keyrepeat, 0),
+ OPT_FLOATRANGE("panscan", vo.panscan, 0, 0.0, 1.0),
+ OPT_FLOATRANGE("video-zoom", vo.zoom, 0, -20.0, 20.0),
+ OPT_FLOATRANGE("video-pan-x", vo.pan_x, 0, -3.0, 3.0),
+ OPT_FLOATRANGE("video-pan-y", vo.pan_y, 0, -3.0, 3.0),
+ OPT_FLOATRANGE("video-align-x", vo.align_x, 0, -1.0, 1.0),
+ OPT_FLOATRANGE("video-align-y", vo.align_y, 0, -1.0, 1.0),
+ OPT_FLAG("video-unscaled", vo.unscaled, 0),
+ OPT_FLAG("force-rgba-osd-rendering", force_rgba_osd, 0),
+ OPT_CHOICE("colormatrix", requested_colorspace, 0,
+ ({"auto", MP_CSP_AUTO},
+ {"BT.601", MP_CSP_BT_601},
+ {"BT.709", MP_CSP_BT_709},
+ {"SMPTE-240M", MP_CSP_SMPTE_240M},
+ {"YCgCo", MP_CSP_YCGCO})),
+ OPT_CHOICE("colormatrix-input-range", requested_input_range, 0,
+ ({"auto", MP_CSP_LEVELS_AUTO},
+ {"limited", MP_CSP_LEVELS_TV},
+ {"full", MP_CSP_LEVELS_PC})),
+ OPT_CHOICE("colormatrix-output-range", requested_output_range, 0,
+ ({"auto", MP_CSP_LEVELS_AUTO},
+ {"limited", MP_CSP_LEVELS_TV},
+ {"full", MP_CSP_LEVELS_PC})),
+
+ OPT_CHOICE_OR_INT("cursor-autohide", cursor_autohide_delay, 0,
+ 0, 30000, ({"no", -1}, {"always", -2})),
+ OPT_FLAG("cursor-autohide-fs-only", cursor_autohide_fs, 0),
+ OPT_FLAG("stop-screensaver", stop_screensaver, 0),
+
+ OPT_INT64("wid", vo.WinID, CONF_GLOBAL),
+#if HAVE_X11
+ OPT_STRINGLIST("fstype", vo.fstype_list, 0),
+#endif
+ OPT_STRING("heartbeat-cmd", heartbeat_cmd, 0),
+ OPT_FLOAT("heartbeat-interval", heartbeat_interval, CONF_MIN, 0),
+
+ OPT_CHOICE_OR_INT("screen", vo.screen_id, 0, 0, 32,
+ ({"default", -1})),
+
+ OPT_CHOICE_OR_INT("fs-screen", vo.fsscreen_id, 0, 0, 32,
+ ({"all", -2}, {"current", -1})),
+
+#if HAVE_COCOA
+ OPT_FLAG("native-fs", vo.native_fs, 0),
+#endif
+
+ OPT_INTRANGE("brightness", gamma_brightness, 0, -100, 100),
+ OPT_INTRANGE("saturation", gamma_saturation, 0, -100, 100),
+ OPT_INTRANGE("contrast", gamma_contrast, 0, -100, 100),
+ OPT_INTRANGE("hue", gamma_hue, 0, -100, 100),
+ OPT_INTRANGE("gamma", gamma_gamma, 0, -100, 100),
+ OPT_FLAG("keepaspect", vo.keepaspect, 0),
+
+//---------------------- mplayer-only options ------------------------
+
+ OPT_FLAG("use-filedir-conf", use_filedir_conf, CONF_GLOBAL),
+ OPT_CHOICE("osd-level", osd_level, 0,
+ ({"0", 0}, {"1", 1}, {"2", 2}, {"3", 3})),
+ OPT_INTRANGE("osd-duration", osd_duration, 0, 0, 3600000),
+ OPT_FLAG("osd-fractions", osd_fractions, 0),
+ OPT_FLOATRANGE("osd-scale", osd_scale, 0, 0, 100),
+ OPT_FLAG("osd-scale-by-window", osd_scale_by_window, 0),
+
+ OPT_DOUBLE("sstep", step_sec, CONF_MIN, 0),
+
+ OPT_CHOICE("framedrop", frame_dropping, 0,
+ ({"no", 0},
+ {"yes", 1},
+ {"hard", 2})),
+
+ OPT_FLAG("untimed", untimed, 0),
+
+ OPT_STRING("stream-capture", stream_capture, 0),
+ OPT_STRING("stream-dump", stream_dump, 0),
+
+#if HAVE_LIRC
+ {"lircconf", &lirc_configfile, CONF_TYPE_STRING, CONF_GLOBAL, 0, 0, NULL},
+#endif
+
+ OPT_CHOICE_OR_INT("loop", loop_times, M_OPT_GLOBAL, 2, 10000,
+ ({"no", -1}, {"1", -1},
+ {"inf", 0})),
+
+ OPT_FLAG("resume-playback", position_resume, 0),
+ OPT_FLAG("save-position-on-quit", position_save_on_quit, 0),
+
+ OPT_FLAG("ordered-chapters", ordered_chapters, 0),
+ OPT_STRING("ordered-chapters-files", ordered_chapters_files, 0),
+ OPT_INTRANGE("chapter-merge-threshold", chapter_merge_threshold, 0, 0, 10000),
+
+ OPT_DOUBLE("chapter-seek-threshold", chapter_seek_threshold, 0),
+
+ OPT_FLAG("load-unsafe-playlists", load_unsafe_playlists, 0),
+ OPT_FLAG("merge-files", merge_files, 0),
+
+ // a-v sync stuff:
+ OPT_FLAG("correct-pts", correct_pts, 0),
+ OPT_CHOICE("pts-association-mode", user_pts_assoc_mode, 0,
+ ({"auto", 0}, {"decoder", 1}, {"sort", 2})),
+ OPT_FLAG("initial-audio-sync", initial_audio_sync, 0),
+ OPT_CHOICE("hr-seek", hr_seek, 0,
+ ({"no", -1}, {"absolute", 0}, {"always", 1}, {"yes", 1})),
+ OPT_FLOATRANGE("hr-seek-demuxer-offset", hr_seek_demuxer_offset, 0, -9, 99),
+ OPT_CHOICE_OR_INT("autosync", autosync, 0, 0, 10000,
+ ({"no", -1})),
+
+ OPT_FLAG("softsleep", softsleep, 0),
+
+ OPT_CHOICE("term-osd", term_osd, 0,
+ ({"force", 1},
+ {"auto", 2},
+ {"no", 0})),
+
+ OPT_STRING("term-osd-esc", term_osd_esc, M_OPT_PARSE_ESCAPES,
+ OPTDEF_STR("\x1b[A\r\x1b[K")),
+ OPT_STRING("playing-msg", playing_msg, M_OPT_PARSE_ESCAPES),
+ OPT_STRING("status-msg", status_msg, M_OPT_PARSE_ESCAPES),
+ OPT_STRING("osd-status-msg", osd_status_msg, M_OPT_PARSE_ESCAPES),
+
+ OPT_FLAG("slave-broken", slave_mode, CONF_GLOBAL),
+ OPT_FLAG("idle", player_idle_mode, CONF_GLOBAL),
+ OPT_INTRANGE("key-fifo-size", input.key_fifo_size, CONF_GLOBAL, 2, 65000),
+ OPT_FLAG("consolecontrols", consolecontrols, CONF_GLOBAL),
+ OPT_FLAG("mouse-movements", vo.enable_mouse_movements, CONF_GLOBAL),
+#if HAVE_TV
+ {"tvscan", (void *) tvscan_conf, CONF_TYPE_SUBCONFIG, 0, 0, 0, NULL},
+#endif /* HAVE_TV */
+
+ {"screenshot", (void *) screenshot_conf, CONF_TYPE_SUBCONFIG},
+
+ {"", (void *) mp_input_opts, CONF_TYPE_SUBCONFIG},
+
+ OPT_FLAG("list-properties", list_properties, CONF_GLOBAL),
+ {"identify", &mp_msg_levels[MSGT_IDENTIFY], CONF_TYPE_FLAG, CONF_GLOBAL, 0, MSGL_V, NULL},
+ {"help", (void *) mp_help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL},
+ {"h", (void *) mp_help_text, CONF_TYPE_PRINT, CONF_NOCFG|CONF_GLOBAL, 0, 0, NULL},
+ {"version", (void *)print_version_opt, CONF_TYPE_PRINT_FUNC, CONF_NOCFG|CONF_GLOBAL|M_OPT_PRE_PARSE},
+ {"V", (void *)print_version_opt, CONF_TYPE_PRINT_FUNC, CONF_NOCFG|CONF_GLOBAL|M_OPT_PRE_PARSE},
+
+#if HAVE_ENCODING
+ OPT_STRING("o", encode_output.file, CONF_GLOBAL),
+ OPT_STRING("of", encode_output.format, CONF_GLOBAL),
+ OPT_STRINGLIST("ofopts*", encode_output.fopts, CONF_GLOBAL),
+ OPT_FLOATRANGE("ofps", encode_output.fps, CONF_GLOBAL, 0.0, 1000000.0),
+ OPT_FLOATRANGE("omaxfps", encode_output.maxfps, CONF_GLOBAL, 0.0, 1000000.0),
+ OPT_STRING("ovc", encode_output.vcodec, CONF_GLOBAL),
+ OPT_STRINGLIST("ovcopts*", encode_output.vopts, CONF_GLOBAL),
+ OPT_STRING("oac", encode_output.acodec, CONF_GLOBAL),
+ OPT_STRINGLIST("oacopts*", encode_output.aopts, CONF_GLOBAL),
+ OPT_FLAG("oharddup", encode_output.harddup, CONF_GLOBAL),
+ OPT_FLOATRANGE("ovoffset", encode_output.voffset, CONF_GLOBAL, -1000000.0, 1000000.0),
+ OPT_FLOATRANGE("oaoffset", encode_output.aoffset, CONF_GLOBAL, -1000000.0, 1000000.0),
+ OPT_FLAG("ocopyts", encode_output.copyts, CONF_GLOBAL),
+ OPT_FLAG("orawts", encode_output.rawts, CONF_GLOBAL),
+ OPT_FLAG("oautofps", encode_output.autofps, CONF_GLOBAL),
+ OPT_FLAG("oneverdrop", encode_output.neverdrop, CONF_GLOBAL),
+ OPT_FLAG("ovfirst", encode_output.video_first, CONF_GLOBAL),
+ OPT_FLAG("oafirst", encode_output.audio_first, CONF_GLOBAL),
+#endif
+
+ {NULL, NULL, 0, 0, 0, 0, NULL}
+};
+
+const struct MPOpts mp_default_opts = {
+ .reset_options = (char **)(const char *[]){"pause", NULL},
+ .audio_driver_list = NULL,
+ .audio_decoders = "-spdif:*", // never select spdif by default
+ .video_decoders = NULL,
+ .deinterlace = -1,
+ .fixed_vo = 1,
+ .softvol = SOFTVOL_AUTO,
+ .softvol_max = 200,
+ .mixer_init_volume = -1,
+ .mixer_init_mute = -1,
+ .volstep = 3,
+ .vo = {
+ .video_driver_list = NULL,
+ .monitor_pixel_aspect = 1.0,
+ .screen_id = -1,
+ .fsscreen_id = -1,
+ .enable_mouse_movements = 1,
+ .fsmode = 0,
+ .panscan = 0.0f,
+ .keepaspect = 1,
+ .border = 1,
+ .WinID = -1,
+ },
+ .wintitle = "mpv - ${media-title}",
+ .heartbeat_interval = 30.0,
+ .stop_screensaver = 1,
+ .cursor_autohide_delay = 1000,
+ .gamma_gamma = 1000,
+ .gamma_brightness = 1000,
+ .gamma_contrast = 1000,
+ .gamma_saturation = 1000,
+ .gamma_hue = 1000,
+ .osd_level = 1,
+ .osd_duration = 1000,
+ .osd_bar_align_y = 0.5,
+ .osd_bar_w = 75.0,
+ .osd_bar_h = 3.125,
+ .osd_scale = 1,
+ .osd_scale_by_window = 1,
+ .lua_load_osc = 1,
+ .loop_times = -1,
+ .ordered_chapters = 1,
+ .chapter_merge_threshold = 100,
+ .chapter_seek_threshold = 5.0,
+ .load_config = 1,
+ .position_resume = 1,
+ .stream_cache_min_percent = 20.0,
+ .stream_cache_seek_min_percent = 50.0,
+ .stream_cache_pause = 10.0,
+ .network_rtsp_transport = 2,
+ .chapterrange = {-1, -1},
+ .edition_id = -1,
+ .default_max_pts_correction = -1,
+ .correct_pts = 1,
+ .user_pts_assoc_mode = 1,
+ .initial_audio_sync = 1,
+ .term_osd = 2,
+ .consolecontrols = 1,
+ .play_frames = -1,
+ .keep_open = 0,
+ .audio_id = -1,
+ .video_id = -1,
+ .sub_id = -1,
+ .audio_display = 1,
+ .sub_visibility = 1,
+ .sub_pos = 100,
+ .sub_speed = 1.0,
+ .audio_output_channels = MP_CHMAP_INIT_STEREO,
+ .audio_output_format = 0, // AF_FORMAT_UNKNOWN
+ .playback_speed = 1.,
+ .movie_aspect = -1.,
+ .field_dominance = -1,
+ .sub_auto = 1,
+ .osd_bar_visible = 1,
+#if HAVE_LIBASS
+ .ass_enabled = 1,
+#endif
+ .sub_scale = 1,
+ .ass_vsfilter_aspect_compat = 1,
+ .ass_vsfilter_color_compat = 1,
+ .ass_vsfilter_blur_compat = 1,
+ .ass_style_override = 1,
+ .ass_shaper = 1,
+ .use_embedded_fonts = 1,
+ .suboverlap_enabled = 0,
+#if HAVE_ENCA
+ .sub_cp = "enca",
+#else
+ .sub_cp = "UTF-8:UTF-8-BROKEN",
+#endif
+
+ .hwdec_codecs = "h264,vc1,wmv3",
+
+ .index_mode = -1,
+
+ .ad_lavc_param = {
+ .ac3drc = 1.,
+ .downmix = 1,
+ .threads = 1,
+ },
+ .lavfdopts = {
+ .allow_mimetype = 1,
+ },
+ .lavc_param = {
+ .check_hw_profile = 1,
+ },
+ .input = {
+ .key_fifo_size = 7,
+ .doubleclick_time = 300,
+ .ar_delay = 200,
+ .ar_rate = 40,
+ .use_joystick = 1,
+ .use_lirc = 1,
+ .use_alt_gr = 1,
+#if HAVE_COCOA
+ .use_ar = 1,
+ .use_media_keys = 1,
+#endif
+ .default_bindings = 1,
+ },
+};
+
+#endif /* MPLAYER_CFG_MPLAYER_H */
diff --git a/options/options.h b/options/options.h
new file mode 100644
index 0000000000..d8f1d46488
--- /dev/null
+++ b/options/options.h
@@ -0,0 +1,297 @@
+#ifndef MPLAYER_OPTIONS_H
+#define MPLAYER_OPTIONS_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include "m_option.h"
+
+typedef struct mp_vo_opts {
+ struct m_obj_settings *video_driver_list, *vo_defs;
+
+ int screenwidth;
+ int screenheight;
+ int ontop;
+ int fullscreen;
+ int screen_id;
+ int fsscreen_id;
+ char *winname;
+ char** fstype_list;
+ int native_keyrepeat;
+
+ float panscan;
+ float zoom;
+ float pan_x, pan_y;
+ float align_x, align_y;
+ int unscaled;
+
+ struct m_geometry geometry;
+ struct m_geometry autofit;
+ struct m_geometry autofit_larger;
+
+ int fsmode;
+ int keepaspect;
+ int border;
+
+ int enable_mouse_movements;
+
+ int64_t WinID;
+
+ float force_monitor_aspect;
+ float monitor_pixel_aspect;
+ int force_window_position;
+
+ int native_fs;
+} mp_vo_opts;
+
+typedef struct MPOpts {
+ char **reset_options;
+ char **lua_files;
+ int lua_load_osc;
+
+ struct m_obj_settings *audio_driver_list, *ao_defs;
+ int fixed_vo;
+ int force_vo;
+ int softvol;
+ float mixer_init_volume;
+ int mixer_init_mute;
+ char *mixer_restore_volume_data;
+ int volstep;
+ float softvol_max;
+ int gapless_audio;
+
+ mp_vo_opts vo;
+
+ char *wintitle;
+ int force_rgba_osd;
+
+ // ranges -100 - 100, 1000 if the vo default should be used
+ int gamma_gamma;
+ int gamma_brightness;
+ int gamma_contrast;
+ int gamma_saturation;
+ int gamma_hue;
+
+ int stop_screensaver;
+ int cursor_autohide_delay;
+ int cursor_autohide_fs;
+
+ int requested_colorspace;
+ int requested_input_range;
+ int requested_output_range;
+
+ char *audio_decoders;
+ char *video_decoders;
+
+ int osd_level;
+ int osd_duration;
+ int osd_fractions;
+ int untimed;
+ char *stream_capture;
+ char *stream_dump;
+ int loop_times;
+ int shuffle;
+ int ordered_chapters;
+ char *ordered_chapters_files;
+ int chapter_merge_threshold;
+ double chapter_seek_threshold;
+ int load_unsafe_playlists;
+ int merge_files;
+ int quiet;
+ int load_config;
+ int use_filedir_conf;
+ int stream_cache_size;
+ int stream_cache_def_size;
+ float stream_cache_min_percent;
+ float stream_cache_seek_min_percent;
+ int network_rtsp_transport;
+ int stream_cache_pause;
+ int chapterrange[2];
+ int edition_id;
+ int correct_pts;
+ int user_pts_assoc_mode;
+ int initial_audio_sync;
+ int hr_seek;
+ float hr_seek_demuxer_offset;
+ float audio_delay;
+ float default_max_pts_correction;
+ int autosync;
+ int softsleep;
+ int frame_dropping;
+ int term_osd;
+ char *term_osd_esc;
+ char *playing_msg;
+ char *status_msg;
+ char *osd_status_msg;
+ char *heartbeat_cmd;
+ float heartbeat_interval;
+ int player_idle_mode;
+ int slave_mode;
+ int consolecontrols;
+ int list_properties;
+ struct m_rel_time play_start;
+ struct m_rel_time play_end;
+ struct m_rel_time play_length;
+ int play_frames;
+ double step_sec;
+ int64_t seek_to_byte;
+ int position_resume;
+ int position_save_on_quit;
+ int pause;
+ int keep_open;
+ int audio_id;
+ int video_id;
+ int sub_id;
+ char **audio_lang;
+ char **sub_lang;
+ int audio_display;
+ int sub_visibility;
+ int sub_pos;
+ float sub_delay;
+ float sub_fps;
+ float sub_speed;
+ int forced_subs_only;
+ int stretch_dvd_subs;
+ char *quvi_format;
+
+ // subreader.c
+ int suboverlap_enabled;
+ char *sub_cp;
+
+ char *audio_stream;
+ int audio_stream_cache;
+ char *demuxer_name;
+ char *audio_demuxer_name;
+ char *sub_demuxer_name;
+ int mkv_subtitle_preroll;
+
+ struct image_writer_opts *screenshot_image_opts;
+ char *screenshot_template;
+
+ double force_fps;
+ int index_mode; // -1=untouched 0=don't use index 1=use (generate) index
+
+ struct mp_chmap audio_output_channels;
+ int audio_output_format;
+ int force_srate;
+ int dtshd;
+ double playback_speed;
+ struct m_obj_settings *vf_settings, *vf_defs;
+ struct m_obj_settings *af_settings, *af_defs;
+ int deinterlace;
+ float movie_aspect;
+ int field_dominance;
+ char **sub_name;
+ char **sub_paths;
+ int sub_auto;
+ int sub_match_fuzziness;
+ int osd_bar_visible;
+ float osd_bar_align_x;
+ float osd_bar_align_y;
+ float osd_bar_w;
+ float osd_bar_h;
+ float osd_scale;
+ int osd_scale_by_window;
+ struct osd_style_opts *osd_style;
+ struct osd_style_opts *sub_text_style;
+ float sub_scale;
+ float sub_gauss;
+ int sub_gray;
+ int ass_enabled;
+ float ass_line_spacing;
+ int ass_use_margins;
+ int ass_vsfilter_aspect_compat;
+ int ass_vsfilter_color_compat;
+ int ass_vsfilter_blur_compat;
+ int use_embedded_fonts;
+ char **ass_force_style_list;
+ char *ass_styles_file;
+ int ass_style_override;
+ int ass_hinting;
+ int ass_shaper;
+
+ int hwdec_api;
+ char *hwdec_codecs;
+
+ struct lavc_param {
+ int fast;
+ char *skip_loop_filter_str;
+ char *skip_idct_str;
+ char *skip_frame_str;
+ int threads;
+ int bitexact;
+ int check_hw_profile;
+ char *avopt;
+ } lavc_param;
+
+ struct ad_lavc_param {
+ float ac3drc;
+ int downmix;
+ int threads;
+ char *avopt;
+ } ad_lavc_param;
+
+ struct lavfdopts {
+ int probesize;
+ int probescore;
+ float analyzeduration;
+ int buffersize;
+ int allow_mimetype;
+ char *format;
+ char *cryptokey;
+ char *avopt;
+ int genptsmode;
+ } lavfdopts;
+
+ struct input_conf {
+ char *config_file;
+ int doubleclick_time;
+ int key_fifo_size;
+ int ar_delay;
+ int ar_rate;
+ char *js_dev;
+ char *in_file;
+ int use_joystick;
+ int use_lirc;
+ int use_lircc;
+ int use_alt_gr;
+ int use_ar;
+ int use_media_keys;
+ int default_bindings;
+ int test;
+ } input;
+
+ struct encode_output_conf {
+ char *file;
+ char *format;
+ char **fopts;
+ float fps;
+ float maxfps;
+ char *vcodec;
+ char **vopts;
+ char *acodec;
+ char **aopts;
+ int harddup;
+ float voffset;
+ float aoffset;
+ int copyts;
+ int rawts;
+ int autofps;
+ int neverdrop;
+ int video_first;
+ int audio_first;
+ } encode_output;
+} MPOpts;
+
+// Should be moved into MPOpts
+extern char **network_http_header_fields;
+extern char *network_useragent;
+extern char *network_referrer;
+extern int network_cookies_enabled;
+extern int network_tls_verify;
+extern char *network_tls_ca_file;
+extern char *cookies_file;
+
+extern const m_option_t mp_opts[];
+extern const struct MPOpts mp_default_opts;
+
+#endif
diff --git a/options/parse_commandline.c b/options/parse_commandline.c
new file mode 100644
index 0000000000..a6feee82c3
--- /dev/null
+++ b/options/parse_commandline.c
@@ -0,0 +1,294 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <stdbool.h>
+
+#include "mpvcore/mp_msg.h"
+#include "m_option.h"
+#include "m_config.h"
+#include "mpvcore/playlist.h"
+#include "mpvcore/playlist_parser.h"
+#include "parse_commandline.h"
+
+#define GLOBAL 0
+#define LOCAL 1
+
+#define dvd_range(a) (a > 0 && a < 256)
+
+
+struct parse_state {
+ struct m_config *config;
+ int argc;
+ char **argv;
+
+ bool no_more_opts;
+ bool error;
+
+ bool is_opt;
+ struct bstr arg;
+ struct bstr param;
+};
+
+// Returns 0 if a valid option/file is available, <0 on error, 1 on end of args.
+static int split_opt_silent(struct parse_state *p)
+{
+ assert(!p->error);
+
+ if (p->argc < 1)
+ return 1;
+
+ p->is_opt = false;
+ p->arg = bstr0(p->argv[0]);
+ p->param = bstr0(NULL);
+
+ p->argc--;
+ p->argv++;
+
+ if (p->no_more_opts || !bstr_startswith0(p->arg, "-") || p->arg.len == 1)
+ return 0;
+
+ if (bstrcmp0(p->arg, "--") == 0) {
+ p->no_more_opts = true;
+ return split_opt_silent(p);
+ }
+
+ p->is_opt = true;
+
+ if (!bstr_eatstart0(&p->arg, "--"))
+ bstr_eatstart0(&p->arg, "-");
+
+ bool ambiguous = !bstr_split_tok(p->arg, "=", &p->arg, &p->param);
+
+ int r = m_config_option_requires_param(p->config, p->arg);
+ if (r < 0)
+ return r;
+
+ if (ambiguous && r > 0) {
+ if (p->argc < 1)
+ return M_OPT_MISSING_PARAM;
+ p->param = bstr0(p->argv[0]);
+ p->argc--;
+ p->argv++;
+ }
+
+ return 0;
+}
+
+// Returns true if more args, false if all parsed or an error occurred.
+static bool split_opt(struct parse_state *p)
+{
+ int r = split_opt_silent(p);
+ if (r >= 0)
+ return r == 0;
+ p->error = true;
+
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Error parsing commandline option %.*s: %s\n",
+ BSTR_P(p->arg), m_option_strerror(r));
+ return false;
+}
+
+// returns M_OPT_... error code
+int m_config_parse_mp_command_line(m_config_t *config, struct playlist *files,
+ int argc, char **argv)
+{
+ struct MPOpts *opts = config->optstruct;
+ int ret = M_OPT_UNKNOWN;
+ int mode = 0;
+ struct playlist_entry *local_start = NULL;
+
+ int local_params_count = 0;
+ struct playlist_param *local_params = 0;
+
+ assert(config != NULL);
+
+ mode = GLOBAL;
+
+ struct parse_state p = {config, argc, argv};
+ while (split_opt(&p)) {
+ if (p.is_opt) {
+ int flags = M_SETOPT_FROM_CMDLINE;
+ if (mode == LOCAL)
+ flags |= M_SETOPT_BACKUP | M_SETOPT_CHECK_ONLY;
+ int r = m_config_set_option_ext(config, p.arg, p.param, flags);
+ if (r <= M_OPT_EXIT) {
+ ret = r;
+ goto err_out;
+ }
+ if (r < 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Setting commandline option --%.*s=%.*s failed.\n",
+ BSTR_P(p.arg), BSTR_P(p.param));
+ goto err_out;
+ }
+
+ // Handle some special arguments outside option parser.
+
+ if (!bstrcmp0(p.arg, "{")) {
+ if (mode != GLOBAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "'--{' can not be nested.\n");
+ goto err_out;
+ }
+ mode = LOCAL;
+ assert(!local_start);
+ local_start = files->last;
+ continue;
+ }
+
+ if (!bstrcmp0(p.arg, "}")) {
+ if (mode != LOCAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Too many closing '--}'.\n");
+ goto err_out;
+ }
+ if (local_params_count) {
+ // The files added between '{' and '}' are the entries from
+ // the entry _after_ local_start, until the end of the list.
+ // If local_start is NULL, the list was empty on '{', and we
+ // want all files in the list.
+ struct playlist_entry *cur
+ = local_start ? local_start->next : files->first;
+ if (!cur)
+ mp_msg(MSGT_CFGPARSER, MSGL_WARN, "Ignored options!\n");
+ while (cur) {
+ playlist_entry_add_params(cur, local_params,
+ local_params_count);
+ cur = cur->next;
+ }
+ }
+ local_params_count = 0;
+ mode = GLOBAL;
+ m_config_restore_backups(config);
+ local_start = NULL;
+ continue;
+ }
+
+ if (bstrcmp0(p.arg, "playlist") == 0) {
+ // append the playlist to the local args
+ char *param0 = bstrdup0(NULL, p.param);
+ struct playlist *pl = playlist_parse_file(param0, opts);
+ talloc_free(param0);
+ if (!pl) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
+ "Error reading playlist '%.*s'", BSTR_P(p.param));
+ goto err_out;
+ }
+ playlist_transfer_entries(files, pl);
+ talloc_free(pl);
+ continue;
+ }
+
+ if (mode == LOCAL) {
+ MP_TARRAY_APPEND(NULL, local_params, local_params_count,
+ (struct playlist_param) {p.arg, p.param});
+ }
+ } else {
+ // filename
+ void *tmp = talloc_new(NULL);
+ bstr file = p.arg;
+ char *file0 = bstrdup0(tmp, p.arg);
+ // expand DVD filename entries like dvd://1-3 into component titles
+ if (bstr_startswith0(file, "dvd://")) {
+ int offset = 6;
+ char *splitpos = strstr(file0 + offset, "-");
+ if (splitpos != NULL) {
+ char *endpos;
+ int start_title = strtol(file0 + offset, &endpos, 10);
+ int end_title;
+ //entries like dvd://-2 imply start at title 1
+ if (start_title < 0) {
+ end_title = abs(start_title);
+ start_title = 1;
+ } else
+ end_title = strtol(splitpos + 1, &endpos, 10);
+
+ if (dvd_range(start_title) && dvd_range(end_title)
+ && (start_title < end_title)) {
+ for (int j = start_title; j <= end_title; j++) {
+ char *f = talloc_asprintf(tmp, "dvd://%d%s", j,
+ endpos);
+ playlist_add_file(files, f);
+ }
+ } else
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Invalid play entry %s\n", file0);
+
+ } else // dvd:// or dvd://x entry
+ playlist_add_file(files, file0);
+ } else
+ playlist_add_file(files, file0);
+ talloc_free(tmp);
+
+ // Lock stdin if it will be used as input
+ if (bstrcmp0(file, "-") == 0)
+ m_config_set_option0(config, "consolecontrols", "no");
+ }
+ }
+
+ if (p.error)
+ goto err_out;
+
+ if (mode != GLOBAL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "Missing closing --} on command line.\n");
+ goto err_out;
+ }
+
+ ret = 0; // success
+
+err_out:
+ talloc_free(local_params);
+ m_config_restore_backups(config);
+ return ret;
+}
+
+extern int mp_msg_levels[];
+
+/* Parse some command line options early before main parsing.
+ * --no-config prevents reading configuration files (otherwise done before
+ * command line parsing), and --really-quiet suppresses messages printed
+ * during normal options parsing.
+ */
+void m_config_preparse_command_line(m_config_t *config, int argc, char **argv)
+{
+ // Hack to shut up parser error messages
+ int msg_lvl_backup = mp_msg_levels[MSGT_CFGPARSER];
+ mp_msg_levels[MSGT_CFGPARSER] = -11;
+
+ struct parse_state p = {config, argc, argv};
+ while (split_opt_silent(&p) == 0) {
+ if (p.is_opt) {
+ // Ignore non-pre-parse options. They will be set later.
+ // Option parsing errors will be handled later as well.
+ int flags = M_SETOPT_FROM_CMDLINE | M_SETOPT_PRE_PARSE_ONLY;
+ m_config_set_option_ext(config, p.arg, p.param, flags);
+ if (bstrcmp0(p.arg, "v") == 0)
+ verbose++;
+ }
+ }
+
+ mp_msg_levels[MSGT_CFGPARSER] = msg_lvl_backup;
+}
diff --git a/options/parse_commandline.h b/options/parse_commandline.h
new file mode 100644
index 0000000000..256bc514b2
--- /dev/null
+++ b/options/parse_commandline.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_PARSER_MPCMD_H
+#define MPLAYER_PARSER_MPCMD_H
+
+#include <stdbool.h>
+
+struct playlist;
+struct m_config;
+
+int m_config_parse_mp_command_line(struct m_config *config,
+ struct playlist *files,
+ int argc, char **argv);
+void m_config_preparse_command_line(struct m_config *config,
+ int argc, char **argv);
+
+#endif /* MPLAYER_PARSER_MPCMD_H */
diff --git a/options/parse_configfile.c b/options/parse_configfile.c
new file mode 100644
index 0000000000..f82e740df9
--- /dev/null
+++ b/options/parse_configfile.c
@@ -0,0 +1,277 @@
+/*
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "osdep/io.h"
+
+#include "parse_configfile.h"
+#include "mpvcore/mp_msg.h"
+#include "m_option.h"
+#include "m_config.h"
+
+/// Maximal include depth.
+#define MAX_RECURSION_DEPTH 8
+
+/// Current include depth.
+static int recursion_depth = 0;
+
+/// Setup the \ref Config from a config file.
+/** \param config The config object.
+ * \param conffile Path to the config file.
+ * \param flags M_SETOPT_* bits
+ * \return 1 on sucess, -1 on error, 0 if file not accessible.
+ */
+int m_config_parse_config_file(m_config_t *config, const char *conffile,
+ int flags)
+{
+#define PRINT_LINENUM mp_msg(MSGT_CFGPARSER, MSGL_ERR, "%s:%d: ", conffile, line_num)
+#define MAX_LINE_LEN 10000
+#define MAX_OPT_LEN 1000
+#define MAX_PARAM_LEN 1500
+ FILE *fp = NULL;
+ char *line = NULL;
+ char opt[MAX_OPT_LEN + 1];
+ char param[MAX_PARAM_LEN + 1];
+ char c; /* for the "" and '' check */
+ int tmp;
+ int line_num = 0;
+ int line_pos; /* line pos */
+ int opt_pos; /* opt pos */
+ int param_pos; /* param pos */
+ int ret = 1;
+ int errors = 0;
+ m_profile_t *profile = NULL;
+
+ flags = flags | M_SETOPT_FROM_CONFIG_FILE;
+
+ mp_msg(MSGT_CFGPARSER, MSGL_V, "Reading config file %s", conffile);
+
+ if (recursion_depth > MAX_RECURSION_DEPTH) {
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ ": too deep 'include'. check your configfiles\n");
+ ret = -1;
+ goto out;
+ }
+
+ if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL,
+ "\ncan't get memory for 'line': %s", strerror(errno));
+ ret = -1;
+ goto out;
+ } else
+
+ mp_msg(MSGT_CFGPARSER, MSGL_V, "\n");
+
+ if ((fp = fopen(conffile, "r")) == NULL) {
+ mp_msg(MSGT_CFGPARSER, MSGL_V, ": %s\n", strerror(errno));
+ ret = 0;
+ goto out;
+ }
+
+ while (fgets(line, MAX_LINE_LEN, fp)) {
+ if (errors >= 16) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "too many errors\n");
+ goto out;
+ }
+
+ line_num++;
+ line_pos = 0;
+
+ /* skip whitespaces */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ /* EOL / comment */
+ if (line[line_pos] == '\0' || line[line_pos] == '#')
+ continue;
+
+ /* read option. */
+ for (opt_pos = 0; isprint(line[line_pos]) &&
+ line[line_pos] != ' ' &&
+ line[line_pos] != '#' &&
+ line[line_pos] != '='; /* NOTHING */) {
+ opt[opt_pos++] = line[line_pos++];
+ if (opt_pos >= MAX_OPT_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long option\n");
+ errors++;
+ ret = -1;
+ goto nextline;
+ }
+ }
+ if (opt_pos == 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "parse error\n");
+ ret = -1;
+ errors++;
+ continue;
+ }
+ opt[opt_pos] = '\0';
+
+ /* Profile declaration */
+ if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') {
+ opt[opt_pos - 1] = '\0';
+ if (strcmp(opt + 1, "default"))
+ profile = m_config_add_profile(config, opt + 1);
+ else
+ profile = NULL;
+ continue;
+ }
+
+ /* skip whitespaces */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ param_pos = 0;
+ bool param_set = false;
+
+ /* check '=' */
+ if (line[line_pos] == '=') {
+ line_pos++;
+ param_set = true;
+
+ /* whitespaces... */
+ while (isspace(line[line_pos]))
+ ++line_pos;
+
+ /* read the parameter */
+ if (line[line_pos] == '"' || line[line_pos] == '\'') {
+ c = line[line_pos];
+ ++line_pos;
+ for (param_pos = 0; line[line_pos] != c; /* NOTHING */) {
+ param[param_pos++] = line[line_pos++];
+ if (param_pos >= MAX_PARAM_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "option %s has a too long parameter\n", opt);
+ ret = -1;
+ errors++;
+ goto nextline;
+ }
+ }
+ line_pos++; /* skip the closing " or ' */
+ goto param_done;
+ }
+
+ if (line[line_pos] == '%') {
+ char *start = &line[line_pos + 1];
+ char *end = start;
+ unsigned long len = strtoul(start, &end, 10);
+ if (start != end && end[0] == '%') {
+ if (len >= MAX_PARAM_LEN - 1 ||
+ strlen(end + 1) < len)
+ {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "bogus %% length\n");
+ ret = -1;
+ errors++;
+ goto nextline;
+ }
+ param_pos = snprintf(param, sizeof(param), "%.*s",
+ (int)len, end + 1);
+ line_pos += 1 + (end - start) + 1 + len;
+ goto param_done;
+ }
+ }
+
+ for (param_pos = 0; isprint(line[line_pos])
+ && !isspace(line[line_pos])
+ && line[line_pos] != '#'; /* NOTHING */) {
+ param[param_pos++] = line[line_pos++];
+ if (param_pos >= MAX_PARAM_LEN) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR, "too long parameter\n");
+ ret = -1;
+ errors++;
+ goto nextline;
+ }
+ }
+
+ param_done:
+
+ while (isspace(line[line_pos]))
+ ++line_pos;
+ }
+ param[param_pos] = '\0';
+
+ /* EOL / comment */
+ if (line[line_pos] != '\0' && line[line_pos] != '#') {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "extra characters: %s\n", line + line_pos);
+ ret = -1;
+ }
+
+ bstr bopt = bstr0(opt);
+ bstr bparam = bstr0(param);
+
+ if (bopt.len >= 3)
+ bstr_eatstart0(&bopt, "--");
+
+ if (profile && bstr_equals0(bopt, "profile-desc")) {
+ m_profile_set_desc(profile, bparam);
+ goto nextline;
+ }
+
+ tmp = m_config_option_requires_param(config, bopt);
+ if (tmp > 0 && !param_set)
+ tmp = M_OPT_MISSING_PARAM;
+ if (tmp < 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "error parsing option %.*s=%.*s: %s\n",
+ BSTR_P(bopt), BSTR_P(bparam), m_option_strerror(tmp));
+ continue;
+ }
+
+ if (profile) {
+ tmp = m_config_set_profile_option(config, profile, bopt, bparam);
+ } else {
+ tmp = m_config_set_option_ext(config, bopt, bparam, flags);
+ }
+ if (tmp < 0) {
+ PRINT_LINENUM;
+ mp_msg(MSGT_CFGPARSER, MSGL_ERR,
+ "setting option %.*s='%.*s' failed.\n",
+ BSTR_P(bopt), BSTR_P(bparam));
+ continue;
+ /* break */
+ }
+nextline:
+ ;
+ }
+
+out:
+ free(line);
+ if (fp)
+ fclose(fp);
+ --recursion_depth;
+ if (ret < 0) {
+ mp_msg(MSGT_CFGPARSER, MSGL_FATAL, "Error loading config file %s.\n",
+ conffile);
+ }
+ return ret;
+}
diff --git a/options/parse_configfile.h b/options/parse_configfile.h
new file mode 100644
index 0000000000..65a4b49496
--- /dev/null
+++ b/options/parse_configfile.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+
+#ifndef MPLAYER_PARSER_CFG_H
+#define MPLAYER_PARSER_CFG_H
+
+#include "m_config.h"
+
+int m_config_parse_config_file(m_config_t* config, const char *conffile,
+ int flags);
+
+#endif /* MPLAYER_PARSER_CFG_H */
diff --git a/options/path.c b/options/path.c
new file mode 100644
index 0000000000..a2b2f135c5
--- /dev/null
+++ b/options/path.c
@@ -0,0 +1,238 @@
+/*
+ * Get path to config dir/file.
+ *
+ * Return Values:
+ * Returns the pointer to the ALLOCATED buffer containing the
+ * zero terminated path string. This buffer has to be FREED
+ * by the caller.
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with MPlayer; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include "config.h"
+#include "mpvcore/mp_msg.h"
+#include "options/path.h"
+#include "talloc.h"
+#include "osdep/io.h"
+#include "osdep/path.h"
+
+typedef char *(*lookup_fun)(const char *);
+static const lookup_fun config_lookup_functions[] = {
+ mp_find_user_config_file,
+#if HAVE_COCOA
+ mp_get_macosx_bundled_path,
+#endif
+ mp_find_global_config_file,
+ NULL
+};
+
+char *mp_find_config_file(const char *filename)
+{
+ for (int i = 0; config_lookup_functions[i] != NULL; i++) {
+ char *path = config_lookup_functions[i](filename);
+ if (!path) continue;
+
+ if (mp_path_exists(path))
+ return path;
+
+ talloc_free(path);
+ }
+ return NULL;
+}
+
+char *mp_find_user_config_file(const char *filename)
+{
+ char *homedir = getenv("MPV_HOME");
+ char *configdir = NULL;
+ char *result = NULL;
+
+ if (!homedir) {
+#ifdef _WIN32
+ result = mp_get_win_config_path(filename);
+#endif
+ homedir = getenv("HOME");
+ configdir = ".mpv";
+ }
+
+ if (!result && homedir) {
+ char *temp = mp_path_join(NULL, bstr0(homedir), bstr0(configdir));
+ result = mp_path_join(NULL, bstr0(temp), bstr0(filename));
+ talloc_free(temp);
+ }
+
+ mp_msg(MSGT_GLOBAL, MSGL_V, "mp_find_user_config_file('%s') -> '%s'\n",
+ filename ? filename : "(NULL)", result ? result : "(NULL)");
+ return result;
+}
+
+char *mp_find_global_config_file(const char *filename)
+{
+ if (filename) {
+ return mp_path_join(NULL, bstr0(MPLAYER_CONFDIR), bstr0(filename));
+ } else {
+ return talloc_strdup(NULL, MPLAYER_CONFDIR);
+ }
+}
+
+char *mp_get_user_path(void *talloc_ctx, const char *path)
+{
+ if (!path)
+ return NULL;
+ bstr bpath = bstr0(path);
+ if (bstr_eatstart0(&bpath, "~")) {
+ // parse to "~" <prefix> "/" <rest>
+ bstr prefix, rest;
+ if (bstr_split_tok(bpath, "/", &prefix, &rest)) {
+ const char *rest0 = rest.start; // ok in this case
+ char *res = NULL;
+ if (bstr_equals0(prefix, "~"))
+ res = talloc_steal(talloc_ctx, mp_find_user_config_file(rest0));
+ if (bstr_equals0(prefix, ""))
+ res = mp_path_join(talloc_ctx, bstr0(getenv("HOME")), rest);
+ if (res)
+ return res;
+ }
+ }
+ return talloc_strdup(talloc_ctx, path);
+}
+
+char *mp_basename(const char *path)
+{
+ char *s;
+
+#if HAVE_DOS_PATHS
+ s = strrchr(path, '\\');
+ if (s)
+ path = s + 1;
+ s = strrchr(path, ':');
+ if (s)
+ path = s + 1;
+#endif
+ s = strrchr(path, '/');
+ return s ? s + 1 : (char *)path;
+}
+
+struct bstr mp_dirname(const char *path)
+{
+ struct bstr ret = {
+ (uint8_t *)path, mp_basename(path) - path
+ };
+ if (ret.len == 0)
+ return bstr0(".");
+ return ret;
+}
+
+char *mp_splitext(const char *path, bstr *root)
+{
+ assert(path);
+ const char *split = strrchr(path, '.');
+ if (!split)
+ split = path + strlen(path);
+ if (root)
+ *root = (bstr){.start = (char *)path, .len = path - split};
+ return (char *)split;
+}
+
+char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2)
+{
+ if (p1.len == 0)
+ return bstrdup0(talloc_ctx, p2);
+ if (p2.len == 0)
+ return bstrdup0(talloc_ctx, p1);
+
+#if HAVE_DOS_PATHS
+ if (p2.len >= 2 && p2.start[1] == ':'
+ || p2.start[0] == '\\' || p2.start[0] == '/')
+#else
+ if (p2.start[0] == '/')
+#endif
+ return bstrdup0(talloc_ctx, p2); // absolute path
+
+ bool have_separator;
+ int endchar1 = p1.start[p1.len - 1];
+#if HAVE_DOS_PATHS
+ have_separator = endchar1 == '/' || endchar1 == '\\'
+ || p1.len == 2 && endchar1 == ':'; // "X:" only
+#else
+ have_separator = endchar1 == '/';
+#endif
+
+ return talloc_asprintf(talloc_ctx, "%.*s%s%.*s", BSTR_P(p1),
+ have_separator ? "" : "/", BSTR_P(p2));
+}
+
+char *mp_getcwd(void *talloc_ctx)
+{
+ char *wd = talloc_array(talloc_ctx, char, 20);
+ while (getcwd(wd, talloc_get_size(wd)) == NULL) {
+ if (errno != ERANGE) {
+ talloc_free(wd);
+ return NULL;
+ }
+ wd = talloc_realloc(talloc_ctx, wd, char, talloc_get_size(wd) * 2);
+ }
+ return wd;
+}
+
+bool mp_path_exists(const char *path)
+{
+ struct stat st;
+ return mp_stat(path, &st) == 0;
+}
+
+bool mp_path_isdir(const char *path)
+{
+ struct stat st;
+ return mp_stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+// Return false if it's considered a normal local filesystem path.
+bool mp_is_url(bstr path)
+{
+ int proto = bstr_find0(path, "://");
+ if (proto < 0)
+ return false;
+ // The protocol part must be alphanumeric, otherwise it's not an URL.
+ for (int i = 0; i < proto; i++) {
+ unsigned char c = path.start[i];
+ if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') &&
+ !(c >= '0' && c <= '9') && c != '_')
+ return false;
+ }
+ return true;
+}
+
+void mp_mk_config_dir(char *subdir)
+{
+ void *tmp = talloc_new(NULL);
+ char *confdir = talloc_steal(tmp, mp_find_user_config_file(""));
+ if (confdir) {
+ if (subdir)
+ confdir = mp_path_join(tmp, bstr0(confdir), bstr0(subdir));
+ mkdir(confdir, 0777);
+ }
+ talloc_free(tmp);
+}
diff --git a/options/path.h b/options/path.h
new file mode 100644
index 0000000000..e0c61321d2
--- /dev/null
+++ b/options/path.h
@@ -0,0 +1,75 @@
+/*
+ * Get path to config dir/file.
+ *
+ * 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.
+ */
+
+#ifndef MPLAYER_PATH_H
+#define MPLAYER_PATH_H
+
+#include <stdbool.h>
+#include "mpvcore/bstr.h"
+
+
+// Search for the input filename in several paths. These include user and global
+// config locations by default. Some platforms may implement additional platform
+// related lookups (i.e.: OSX inside an application bundle).
+char *mp_find_config_file(const char *filename);
+
+// Search for the input filename in the global configuration location.
+char *mp_find_global_config_file(const char *filename);
+
+// Search for the input filename in the user configuration location.
+char *mp_find_user_config_file(const char *filename);
+
+// Normally returns a talloc_strdup'ed copy of the path, except for special
+// paths starting with '~'. Used to allow the user explicitly reference a
+// file from the user's home or mpv config directory.
+char *mp_get_user_path(void *talloc_ctx, const char *path);
+
+// Return pointer to filename part of path
+
+char *mp_basename(const char *path);
+
+/* Return file extension, including the '.'. If root is not NULL, set it to the
+ * part of the path without extension. So: path == root + returnvalue
+ * Don't consider it a file extension if the only '.' is the first character.
+ * Return "" if no extension.
+ */
+char *mp_splitext(const char *path, bstr *root);
+
+/* Return struct bstr referencing directory part of path, or if that
+ * would be empty, ".".
+ */
+struct bstr mp_dirname(const char *path);
+
+/* Join two path components and return a newly allocated string
+ * for the result. '/' is inserted between the components if needed.
+ * If p2 is an absolute path then the value of p1 is ignored.
+ */
+char *mp_path_join(void *talloc_ctx, struct bstr p1, struct bstr p2);
+
+char *mp_getcwd(void *talloc_ctx);
+
+bool mp_path_exists(const char *path);
+bool mp_path_isdir(const char *path);
+
+bool mp_is_url(bstr path);
+
+void mp_mk_config_dir(char *subdir);
+
+#endif /* MPLAYER_PATH_H */