diff --git a/cmdutils.c b/cmdutils.c index e7476bbcb3..f2e0651bb9 100644 --- a/cmdutils.c +++ b/cmdutils.c @@ -340,6 +340,29 @@ void parse_options(void *optctx, int argc, char **argv, const OptionDef *options } } +int parse_optgroup(void *optctx, OptionGroup *g) +{ + int i, ret; + + av_log(NULL, AV_LOG_DEBUG, "Parsing a group of options: %s %s.\n", + g->group_def->name, g->arg); + + for (i = 0; i < g->nb_opts; i++) { + Option *o = &g->opts[i]; + + av_log(NULL, AV_LOG_DEBUG, "Applying option %s (%s) with argument %s.\n", + o->key, o->opt->help, o->val); + + ret = write_option(optctx, o->opt, o->key, o->val); + if (ret < 0) + return ret; + } + + av_log(NULL, AV_LOG_DEBUG, "Successfully parsed a group of options.\n"); + + return 0; +} + int locate_option(int argc, char **argv, const OptionDef *options, const char *optname) { @@ -416,6 +439,224 @@ int opt_default(void *optctx, const char *opt, const char *arg) return AVERROR_OPTION_NOT_FOUND; } +/* + * Check whether given option is a group separator. + * + * @return index of the group definition that matched or -1 if none + */ +static int match_group_separator(const OptionGroupDef *groups, const char *opt) +{ + const OptionGroupDef *p = groups; + + while (p->name) { + if (p->sep && !strcmp(p->sep, opt)) + return p - groups; + p++; + } + + return -1; +} + +/* + * Finish parsing an option group. + * + * @param group_idx which group definition should this group belong to + * @param arg argument of the group delimiting option + */ +static void finish_group(OptionParseContext *octx, int group_idx, + const char *arg) +{ + OptionGroupList *l = &octx->groups[group_idx]; + OptionGroup *g; + + GROW_ARRAY(l->groups, l->nb_groups); + g = &l->groups[l->nb_groups - 1]; + + *g = octx->cur_group; + g->arg = arg; + g->group_def = l->group_def; +#if CONFIG_SWSCALE + g->sws_opts = sws_opts; +#endif + g->codec_opts = codec_opts; + g->format_opts = format_opts; + + codec_opts = NULL; + format_opts = NULL; +#if CONFIG_SWSCALE + sws_opts = NULL; +#endif + init_opts(); + + memset(&octx->cur_group, 0, sizeof(octx->cur_group)); +} + +/* + * Add an option instance to currently parsed group. + */ +static void add_opt(OptionParseContext *octx, const OptionDef *opt, + const char *key, const char *val) +{ + int global = !(opt->flags & (OPT_PERFILE | OPT_SPEC | OPT_OFFSET)); + OptionGroup *g = global ? &octx->global_opts : &octx->cur_group; + + GROW_ARRAY(g->opts, g->nb_opts); + g->opts[g->nb_opts - 1].opt = opt; + g->opts[g->nb_opts - 1].key = key; + g->opts[g->nb_opts - 1].val = val; +} + +static void init_parse_context(OptionParseContext *octx, + const OptionGroupDef *groups) +{ + static const OptionGroupDef global_group = { "global" }; + const OptionGroupDef *g = groups; + int i; + + memset(octx, 0, sizeof(*octx)); + + while (g->name) + g++; + octx->nb_groups = g - groups; + octx->groups = av_mallocz(sizeof(*octx->groups) * octx->nb_groups); + if (!octx->groups) + exit(1); + + for (i = 0; i < octx->nb_groups; i++) + octx->groups[i].group_def = &groups[i]; + + octx->global_opts.group_def = &global_group; + octx->global_opts.arg = ""; + + init_opts(); +} + +void uninit_parse_context(OptionParseContext *octx) +{ + int i, j; + + for (i = 0; i < octx->nb_groups; i++) { + OptionGroupList *l = &octx->groups[i]; + + for (j = 0; j < l->nb_groups; j++) { + av_freep(&l->groups[j].opts); + av_dict_free(&l->groups[j].codec_opts); + av_dict_free(&l->groups[j].format_opts); +#if CONFIG_SWSCALE + sws_freeContext(l->groups[j].sws_opts); +#endif + } + av_freep(&l->groups); + } + av_freep(&octx->groups); + + av_freep(&octx->cur_group.opts); + av_freep(&octx->global_opts.opts); + + uninit_opts(); +} + +int split_commandline(OptionParseContext *octx, int argc, char *argv[], + const OptionDef *options, + const OptionGroupDef *groups) +{ + int optindex = 1; + + /* perform system-dependent conversions for arguments list */ + prepare_app_arguments(&argc, &argv); + + init_parse_context(octx, groups); + av_log(NULL, AV_LOG_DEBUG, "Splitting the commandline.\n"); + + while (optindex < argc) { + const char *opt = argv[optindex++], *arg; + const OptionDef *po; + int ret; + + av_log(NULL, AV_LOG_DEBUG, "Reading option '%s' ...", opt); + + /* unnamed group separators, e.g. output filename */ + if (opt[0] != '-' || !opt[1]) { + finish_group(octx, 0, opt); + av_log(NULL, AV_LOG_DEBUG, " matched as %s.\n", groups[0].name); + continue; + } + opt++; + +#define GET_ARG(arg) \ +do { \ + arg = argv[optindex++]; \ + if (!arg) { \ + av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'.\n", opt);\ + return AVERROR(EINVAL); \ + } \ +} while (0) + + /* named group separators, e.g. -i */ + if ((ret = match_group_separator(groups, opt)) >= 0) { + GET_ARG(arg); + finish_group(octx, ret, arg); + av_log(NULL, AV_LOG_DEBUG, " matched as %s with argument '%s'.\n", + groups[ret].name, arg); + continue; + } + + /* normal options */ + po = find_option(options, opt); + if (po->name) { + if (po->flags & OPT_EXIT) { + /* optional argument, e.g. -h */ + arg = argv[optindex++]; + } else if (po->flags & HAS_ARG) { + GET_ARG(arg); + } else { + arg = "1"; + } + + add_opt(octx, po, opt, arg); + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " + "argument '%s'.\n", po->name, po->help, arg); + continue; + } + + /* AVOptions */ + if (argv[optindex]) { + ret = opt_default(NULL, opt, argv[optindex]); + if (ret >= 0) { + av_log(NULL, AV_LOG_DEBUG, " matched as AVOption '%s' with " + "argument '%s'.\n", opt, argv[optindex]); + optindex++; + continue; + } else if (ret != AVERROR_OPTION_NOT_FOUND) { + av_log(NULL, AV_LOG_ERROR, "Error parsing option '%s' " + "with argument '%s'.\n", opt, argv[optindex]); + return ret; + } + } + + /* boolean -nofoo options */ + if (opt[0] == 'n' && opt[1] == 'o' && + (po = find_option(options, opt + 2)) && + po->name && po->flags & OPT_BOOL) { + add_opt(octx, po, opt, "0"); + av_log(NULL, AV_LOG_DEBUG, " matched as option '%s' (%s) with " + "argument 0.\n", po->name, po->help); + continue; + } + + av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'.\n", opt); + return AVERROR_OPTION_NOT_FOUND; + } + + if (octx->cur_group.nb_opts || codec_opts || format_opts) + av_log(NULL, AV_LOG_WARNING, "Trailing options were found on the " + "commandline.\n"); + + av_log(NULL, AV_LOG_DEBUG, "Finished splitting the commandline.\n"); + + return 0; +} + int opt_loglevel(void *optctx, const char *opt, const char *arg) { const struct { const char *name; int level; } log_levels[] = { diff --git a/cmdutils.h b/cmdutils.h index 8485a8ba61..1af30d7676 100644 --- a/cmdutils.h +++ b/cmdutils.h @@ -205,6 +205,94 @@ void parse_options(void *optctx, int argc, char **argv, const OptionDef *options int parse_option(void *optctx, const char *opt, const char *arg, const OptionDef *options); +/** + * An option extracted from the commandline. + * Cannot use AVDictionary because of options like -map which can be + * used multiple times. + */ +typedef struct Option { + const OptionDef *opt; + const char *key; + const char *val; +} Option; + +typedef struct OptionGroupDef { + /**< group name */ + const char *name; + /** + * Option to be used as group separator. Can be NULL for groups which + * are terminated by a non-option argument (e.g. avconv output files) + */ + const char *sep; +} OptionGroupDef; + +typedef struct OptionGroup { + const OptionGroupDef *group_def; + const char *arg; + + Option *opts; + int nb_opts; + + AVDictionary *codec_opts; + AVDictionary *format_opts; + struct SwsContext *sws_opts; +} OptionGroup; + +/** + * A list of option groups that all have the same group type + * (e.g. input files or output files) + */ +typedef struct OptionGroupList { + const OptionGroupDef *group_def; + + OptionGroup *groups; + int nb_groups; +} OptionGroupList; + +typedef struct OptionParseContext { + OptionGroup global_opts; + + OptionGroupList *groups; + int nb_groups; + + /* parsing state */ + OptionGroup cur_group; +} OptionParseContext; + +/** + * Parse an options group and write results into optctx. + * + * @param optctx an app-specific options context. NULL for global options group + */ +int parse_optgroup(void *optctx, OptionGroup *g); + +/** + * Split the commandline into an intermediate form convenient for further + * processing. + * + * The commandline is assumed to be composed of options which either belong to a + * group (those with OPT_SPEC, OPT_OFFSET or OPT_PERFILE) or are global + * (everything else). + * + * A group (defined by an OptionGroupDef struct) is a sequence of options + * terminated by either a group separator option (e.g. -i) or a parameter that + * is not an option (doesn't start with -). A group without a separator option + * must always be first in the supplied groups list. + * + * All options within the same group are stored in one OptionGroup struct in an + * OptionGroupList, all groups with the same group definition are stored in one + * OptionGroupList in OptionParseContext.groups. The order of group lists is the + * same as the order of group definitions. + */ +int split_commandline(OptionParseContext *octx, int argc, char *argv[], + const OptionDef *options, + const OptionGroupDef *groups); + +/** + * Free all allocated memory in an OptionParseContext. + */ +void uninit_parse_context(OptionParseContext *octx); + /** * Find the '-loglevel' option in the command line args and apply it. */