|
|
|
/*
|
|
|
|
* Various utilities for command line tools
|
|
|
|
* Copyright (c) 2000-2003 Fabrice Bellard
|
|
|
|
*
|
|
|
|
* This file is part of FFmpeg.
|
|
|
|
*
|
|
|
|
* FFmpeg is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* FFmpeg 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
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
|
|
* License along with FFmpeg; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <errno.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
/* Include only the enabled headers since some compilers (namely, Sun
|
|
|
|
Studio) will not omit unused inline functions and create undefined
|
|
|
|
references to libraries that are not being built. */
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
#include "compat/va_copy.h"
|
|
|
|
#include "libavformat/avformat.h"
|
|
|
|
#include "libswscale/swscale.h"
|
|
|
|
#include "libswscale/version.h"
|
|
|
|
#include "libswresample/swresample.h"
|
|
|
|
#include "libavutil/avassert.h"
|
|
|
|
#include "libavutil/avstring.h"
|
|
|
|
#include "libavutil/channel_layout.h"
|
|
|
|
#include "libavutil/display.h"
|
|
|
|
#include "libavutil/getenv_utf8.h"
|
|
|
|
#include "libavutil/mathematics.h"
|
|
|
|
#include "libavutil/imgutils.h"
|
|
|
|
#include "libavutil/libm.h"
|
|
|
|
#include "libavutil/parseutils.h"
|
|
|
|
#include "libavutil/eval.h"
|
|
|
|
#include "libavutil/dict.h"
|
|
|
|
#include "libavutil/opt.h"
|
|
|
|
#include "cmdutils.h"
|
|
|
|
#include "fopen_utf8.h"
|
|
|
|
#include "opt_common.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include <windows.h>
|
|
|
|
#include "compat/w32dlfcn.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
AVDictionary *sws_dict;
|
|
|
|
AVDictionary *swr_opts;
|
|
|
|
AVDictionary *format_opts, *codec_opts;
|
|
|
|
|
|
|
|
int hide_banner = 0;
|
|
|
|
|
|
|
|
void uninit_opts(void)
|
|
|
|
{
|
|
|
|
av_dict_free(&swr_opts);
|
|
|
|
av_dict_free(&sws_dict);
|
|
|
|
av_dict_free(&format_opts);
|
|
|
|
av_dict_free(&codec_opts);
|
|
|
|
}
|
|
|
|
|
|
|
|
void log_callback_help(void *ptr, int level, const char *fmt, va_list vl)
|
|
|
|
{
|
|
|
|
vfprintf(stdout, fmt, vl);
|
|
|
|
}
|
|
|
|
|
|
|
|
void init_dynload(void)
|
|
|
|
{
|
|
|
|
#if HAVE_SETDLLDIRECTORY && defined(_WIN32)
|
|
|
|
/* Calling SetDllDirectory with the empty string (but not NULL) removes the
|
|
|
|
* current working directory from the DLL search path as a security pre-caution. */
|
|
|
|
SetDllDirectory("");
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
static void (*program_exit)(int ret);
|
|
|
|
|
|
|
|
void register_exit(void (*cb)(int ret))
|
|
|
|
{
|
|
|
|
program_exit = cb;
|
|
|
|
}
|
|
|
|
|
|
|
|
void report_and_exit(int ret)
|
|
|
|
{
|
|
|
|
av_log(NULL, AV_LOG_FATAL, "%s\n", av_err2str(ret));
|
|
|
|
exit_program(AVUNERROR(ret));
|
|
|
|
}
|
|
|
|
|
|
|
|
void exit_program(int ret)
|
|
|
|
{
|
|
|
|
if (program_exit)
|
|
|
|
program_exit(ret);
|
|
|
|
|
|
|
|
exit(ret);
|
|
|
|
}
|
|
|
|
|
|
|
|
double parse_number_or_die(const char *context, const char *numstr, int type,
|
|
|
|
double min, double max)
|
|
|
|
{
|
|
|
|
char *tail;
|
|
|
|
const char *error;
|
|
|
|
double d = av_strtod(numstr, &tail);
|
|
|
|
if (*tail)
|
|
|
|
error = "Expected number for %s but found: %s\n";
|
|
|
|
else if (d < min || d > max)
|
|
|
|
error = "The value for %s was %s which is not within %f - %f\n";
|
|
|
|
else if (type == OPT_INT64 && (int64_t)d != d)
|
|
|
|
error = "Expected int64 for %s but found %s\n";
|
|
|
|
else if (type == OPT_INT && (int)d != d)
|
|
|
|
error = "Expected int for %s but found %s\n";
|
|
|
|
else
|
|
|
|
return d;
|
|
|
|
av_log(NULL, AV_LOG_FATAL, error, context, numstr, min, max);
|
|
|
|
exit_program(1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int64_t parse_time_or_die(const char *context, const char *timestr,
|
|
|
|
int is_duration)
|
|
|
|
{
|
|
|
|
int64_t us;
|
|
|
|
if (av_parse_time(&us, timestr, is_duration) < 0) {
|
|
|
|
av_log(NULL, AV_LOG_FATAL, "Invalid %s specification for %s: %s\n",
|
|
|
|
is_duration ? "duration" : "date", context, timestr);
|
|
|
|
exit_program(1);
|
|
|
|
}
|
|
|
|
return us;
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_help_options(const OptionDef *options, const char *msg, int req_flags,
|
|
|
|
int rej_flags, int alt_flags)
|
|
|
|
{
|
|
|
|
const OptionDef *po;
|
|
|
|
int first;
|
|
|
|
|
|
|
|
first = 1;
|
|
|
|
for (po = options; po->name; po++) {
|
|
|
|
char buf[128];
|
|
|
|
|
|
|
|
if (((po->flags & req_flags) != req_flags) ||
|
|
|
|
(alt_flags && !(po->flags & alt_flags)) ||
|
|
|
|
(po->flags & rej_flags))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (first) {
|
|
|
|
printf("%s\n", msg);
|
|
|
|
first = 0;
|
|
|
|
}
|
|
|
|
av_strlcpy(buf, po->name, sizeof(buf));
|
|
|
|
if (po->argname) {
|
|
|
|
av_strlcat(buf, " ", sizeof(buf));
|
|
|
|
av_strlcat(buf, po->argname, sizeof(buf));
|
|
|
|
}
|
|
|
|
printf("-%-17s %s\n", buf, po->help);
|
|
|
|
}
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
void show_help_children(const AVClass *class, int flags)
|
|
|
|
{
|
|
|
|
void *iter = NULL;
|
|
|
|
const AVClass *child;
|
|
|
|
if (class->option) {
|
|
|
|
av_opt_show2(&class, NULL, flags, 0);
|
|
|
|
printf("\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
while (child = av_opt_child_class_iterate(class, &iter))
|
|
|
|
show_help_children(child, flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
static const OptionDef *find_option(const OptionDef *po, const char *name)
|
|
|
|
{
|
|
|
|
while (po->name) {
|
|
|
|
const char *end;
|
|
|
|
if (av_strstart(name, po->name, &end) && (!*end || *end == ':'))
|
|
|
|
break;
|
|
|
|
po++;
|
|
|
|
}
|
|
|
|
return po;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* _WIN32 means using the windows libc - cygwin doesn't define that
|
|
|
|
* by default. HAVE_COMMANDLINETOARGVW is true on cygwin, while
|
|
|
|
* it doesn't provide the actual command line via GetCommandLineW(). */
|
|
|
|
#if HAVE_COMMANDLINETOARGVW && defined(_WIN32)
|
|
|
|
#include <shellapi.h>
|
|
|
|
/* Will be leaked on exit */
|
|
|
|
static char** win32_argv_utf8 = NULL;
|
|
|
|
static int win32_argc = 0;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Prepare command line arguments for executable.
|
|
|
|
* For Windows - perform wide-char to UTF-8 conversion.
|
|
|
|
* Input arguments should be main() function arguments.
|
|
|
|
* @param argc_ptr Arguments number (including executable)
|
|
|
|
* @param argv_ptr Arguments list.
|
|
|
|
*/
|
|
|
|
static void prepare_app_arguments(int *argc_ptr, char ***argv_ptr)
|
|
|
|
{
|
|
|
|
char *argstr_flat;
|
|
|
|
wchar_t **argv_w;
|
|
|
|
int i, buffsize = 0, offset = 0;
|
|
|
|
|
|
|
|
if (win32_argv_utf8) {
|
|
|
|
*argc_ptr = win32_argc;
|
|
|
|
*argv_ptr = win32_argv_utf8;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
win32_argc = 0;
|
|
|
|
argv_w = CommandLineToArgvW(GetCommandLineW(), &win32_argc);
|
|
|
|
if (win32_argc <= 0 || !argv_w)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* determine the UTF-8 buffer size (including NULL-termination symbols) */
|
|
|
|
for (i = 0; i < win32_argc; i++)
|
|
|
|
buffsize += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1,
|
|
|
|
NULL, 0, NULL, NULL);
|
|
|
|
|
|
|
|
win32_argv_utf8 = av_mallocz(sizeof(char *) * (win32_argc + 1) + buffsize);
|
|
|
|
argstr_flat = (char *)win32_argv_utf8 + sizeof(char *) * (win32_argc + 1);
|
|
|
|
if (!win32_argv_utf8) {
|
|
|
|
LocalFree(argv_w);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < win32_argc; i++) {
|
|
|
|
win32_argv_utf8[i] = &argstr_flat[offset];
|
|
|
|
offset += WideCharToMultiByte(CP_UTF8, 0, argv_w[i], -1,
|
|
|
|
&argstr_flat[offset],
|
|
|
|
buffsize - offset, NULL, NULL);
|
|
|
|
}
|
|
|
|
win32_argv_utf8[i] = NULL;
|
|
|
|
LocalFree(argv_w);
|
|
|
|
|
|
|
|
*argc_ptr = win32_argc;
|
|
|
|
*argv_ptr = win32_argv_utf8;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline void prepare_app_arguments(int *argc_ptr, char ***argv_ptr)
|
|
|
|
{
|
|
|
|
/* nothing to do */
|
|
|
|
}
|
|
|
|
#endif /* HAVE_COMMANDLINETOARGVW */
|
|
|
|
|
|
|
|
static int write_option(void *optctx, const OptionDef *po, const char *opt,
|
|
|
|
const char *arg)
|
|
|
|
{
|
|
|
|
/* new-style options contain an offset into optctx, old-style address of
|
|
|
|
* a global var*/
|
|
|
|
void *dst = po->flags & (OPT_OFFSET | OPT_SPEC) ?
|
|
|
|
(uint8_t *)optctx + po->u.off : po->u.dst_ptr;
|
|
|
|
int *dstcount;
|
|
|
|
|
|
|
|
if (po->flags & OPT_SPEC) {
|
|
|
|
SpecifierOpt **so = dst;
|
|
|
|
char *p = strchr(opt, ':');
|
|
|
|
char *str;
|
|
|
|
|
|
|
|
dstcount = (int *)(so + 1);
|
|
|
|
*so = grow_array(*so, sizeof(**so), dstcount, *dstcount + 1);
|
|
|
|
str = av_strdup(p ? p + 1 : "");
|
|
|
|
if (!str)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
(*so)[*dstcount - 1].specifier = str;
|
|
|
|
dst = &(*so)[*dstcount - 1].u;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (po->flags & OPT_STRING) {
|
|
|
|
char *str;
|
|
|
|
str = av_strdup(arg);
|
|
|
|
av_freep(dst);
|
|
|
|
if (!str)
|
|
|
|
return AVERROR(ENOMEM);
|
|
|
|
*(char **)dst = str;
|
|
|
|
} else if (po->flags & OPT_BOOL || po->flags & OPT_INT) {
|
|
|
|
*(int *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT_MIN, INT_MAX);
|
|
|
|
} else if (po->flags & OPT_INT64) {
|
|
|
|
*(int64_t *)dst = parse_number_or_die(opt, arg, OPT_INT64, INT64_MIN, INT64_MAX);
|
|
|
|
} else if (po->flags & OPT_TIME) {
|
|
|
|
*(int64_t *)dst = parse_time_or_die(opt, arg, 1);
|
|
|
|
} else if (po->flags & OPT_FLOAT) {
|
|
|
|
*(float *)dst = parse_number_or_die(opt, arg, OPT_FLOAT, -INFINITY, INFINITY);
|
|
|
|
} else if (po->flags & OPT_DOUBLE) {
|
|
|
|
*(double *)dst = parse_number_or_die(opt, arg, OPT_DOUBLE, -INFINITY, INFINITY);
|
|
|
|
} else if (po->u.func_arg) {
|
|
|
|
int ret = po->u.func_arg(optctx, opt, arg);
|
|
|
|
if (ret < 0) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR,
|
|
|
|
"Failed to set value '%s' for option '%s': %s\n",
|
|
|
|
arg, opt, av_err2str(ret));
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (po->flags & OPT_EXIT)
|
|
|
|
exit_program(0);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int parse_option(void *optctx, const char *opt, const char *arg,
|
|
|
|
const OptionDef *options)
|
|
|
|
{
|
|
|
|
static const OptionDef opt_avoptions = {
|
|
|
|
.name = "AVOption passthrough",
|
|
|
|
.flags = HAS_ARG,
|
|
|
|
.u.func_arg = opt_default,
|
|
|
|
};
|
|
|
|
|
|
|
|
const OptionDef *po;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
po = find_option(options, opt);
|
|
|
|
if (!po->name && opt[0] == 'n' && opt[1] == 'o') {
|
|
|
|
/* handle 'no' bool option */
|
|
|
|
po = find_option(options, opt + 2);
|
|
|
|
if ((po->name && (po->flags & OPT_BOOL)))
|
|
|
|
arg = "0";
|
|
|
|
} else if (po->flags & OPT_BOOL)
|
|
|
|
arg = "1";
|
|
|
|
|
|
|
|
if (!po->name)
|
|
|
|
po = &opt_avoptions;
|
|
|
|
if (!po->name) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "Unrecognized option '%s'\n", opt);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
if (po->flags & HAS_ARG && !arg) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "Missing argument for option '%s'\n", opt);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = write_option(optctx, po, opt, arg);
|
|
|
|
if (ret < 0)
|
|
|
|
return ret;
|
|
|
|
|
|
|
|
return !!(po->flags & HAS_ARG);
|
|
|
|
}
|
|
|
|
|
|
|
|
void parse_options(void *optctx, int argc, char **argv, const OptionDef *options,
|
|
|
|
void (*parse_arg_function)(void *, const char*))
|
|
|
|
{
|
|
|
|
const char *opt;
|
|
|
|
int optindex, handleoptions = 1, ret;
|
|
|
|
|
|
|
|
/* perform system-dependent conversions for arguments list */
|
|
|
|
prepare_app_arguments(&argc, &argv);
|
|
|
|
|
|
|
|
/* parse options */
|
|
|
|
optindex = 1;
|
|
|
|
while (optindex < argc) {
|
|
|
|
opt = argv[optindex++];
|
|
|
|
|
|
|
|
if (handleoptions && opt[0] == '-' && opt[1] != '\0') {
|
|
|
|
if (opt[1] == '-' && opt[2] == '\0') {
|
|
|
|
handleoptions = 0;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
opt++;
|
|
|
|
|
|
|
|
if ((ret = parse_option(optctx, opt, argv[optindex], options)) < 0)
|
|
|
|
exit_program(1);
|
|
|
|
optindex += ret;
|
|
|
|
} else {
|
|
|
|
if (parse_arg_function)
|
|
|
|
parse_arg_function(optctx, opt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
|
|
|
if (g->group_def->flags &&
|
|
|
|
!(g->group_def->flags & o->opt->flags)) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "Option %s (%s) cannot be applied to "
|
|
|
|
"%s %s -- you are trying to apply an input option to an "
|
|
|
|
"output file or vice versa. Move this option before the "
|
|
|
|
"file it belongs to.\n", o->key, o->opt->help,
|
|
|
|
g->group_def->name, g->arg);
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
const OptionDef *po;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
|
|
const char *cur_opt = argv[i];
|
|
|
|
|
|
|
|
if (*cur_opt++ != '-')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
po = find_option(options, cur_opt);
|
|
|
|
if (!po->name && cur_opt[0] == 'n' && cur_opt[1] == 'o')
|
|
|
|
po = find_option(options, cur_opt + 2);
|
|
|
|
|
|
|
|
if ((!po->name && !strcmp(cur_opt, optname)) ||
|
|
|
|
(po->name && !strcmp(optname, po->name)))
|
|
|
|
return i;
|
|
|
|
|
|
|
|
if (!po->name || po->flags & HAS_ARG)
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void dump_argument(FILE *report_file, const char *a)
|
|
|
|
{
|
|
|
|
const unsigned char *p;
|
|
|
|
|
|
|
|
for (p = a; *p; p++)
|
|
|
|
if (!((*p >= '+' && *p <= ':') || (*p >= '@' && *p <= 'Z') ||
|
|
|
|
*p == '_' || (*p >= 'a' && *p <= 'z')))
|
|
|
|
break;
|
|
|
|
if (!*p) {
|
|
|
|
fputs(a, report_file);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
fputc('"', report_file);
|
|
|
|
for (p = a; *p; p++) {
|
|
|
|
if (*p == '\\' || *p == '"' || *p == '$' || *p == '`')
|
|
|
|
fprintf(report_file, "\\%c", *p);
|
|
|
|
else if (*p < ' ' || *p > '~')
|
|
|
|
fprintf(report_file, "\\x%02x", *p);
|
|
|
|
else
|
|
|
|
fputc(*p, report_file);
|
|
|
|
}
|
|
|
|
fputc('"', report_file);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void check_options(const OptionDef *po)
|
|
|
|
{
|
|
|
|
while (po->name) {
|
|
|
|
if (po->flags & OPT_PERFILE)
|
|
|
|
av_assert0(po->flags & (OPT_INPUT | OPT_OUTPUT));
|
|
|
|
po++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void parse_loglevel(int argc, char **argv, const OptionDef *options)
|
|
|
|
{
|
|
|
|
int idx = locate_option(argc, argv, options, "loglevel");
|
|
|
|
char *env;
|
|
|
|
|
|
|
|
check_options(options);
|
|
|
|
|
|
|
|
if (!idx)
|
|
|
|
idx = locate_option(argc, argv, options, "v");
|
|
|
|
if (idx && argv[idx + 1])
|
|
|
|
opt_loglevel(NULL, "loglevel", argv[idx + 1]);
|
|
|
|
idx = locate_option(argc, argv, options, "report");
|
|
|
|
env = getenv_utf8("FFREPORT");
|
|
|
|
if (env || idx) {
|
|
|
|
FILE *report_file = NULL;
|
|
|
|
init_report(env, &report_file);
|
|
|
|
if (report_file) {
|
|
|
|
int i;
|
|
|
|
fprintf(report_file, "Command line:\n");
|
|
|
|
for (i = 0; i < argc; i++) {
|
|
|
|
dump_argument(report_file, argv[i]);
|
|
|
|
fputc(i < argc - 1 ? ' ' : '\n', report_file);
|
|
|
|
}
|
|
|
|
fflush(report_file);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
freeenv_utf8(env);
|
|
|
|
idx = locate_option(argc, argv, options, "hide_banner");
|
|
|
|
if (idx)
|
|
|
|
hide_banner = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const AVOption *opt_find(void *obj, const char *name, const char *unit,
|
|
|
|
int opt_flags, int search_flags)
|
|
|
|
{
|
|
|
|
const AVOption *o = av_opt_find(obj, name, unit, opt_flags, search_flags);
|
|
|
|
if(o && !o->flags)
|
|
|
|
return NULL;
|
|
|
|
return o;
|
|
|
|
}
|
|
|
|
|
|
|
|
#define FLAGS (o->type == AV_OPT_TYPE_FLAGS && (arg[0]=='-' || arg[0]=='+')) ? AV_DICT_APPEND : 0
|
|
|
|
int opt_default(void *optctx, const char *opt, const char *arg)
|
|
|
|
{
|
|
|
|
const AVOption *o;
|
|
|
|
int consumed = 0;
|
|
|
|
char opt_stripped[128];
|
|
|
|
const char *p;
|
|
|
|
const AVClass *cc = avcodec_get_class(), *fc = avformat_get_class();
|
|
|
|
#if CONFIG_SWSCALE
|
|
|
|
const AVClass *sc = sws_get_class();
|
|
|
|
#endif
|
|
|
|
#if CONFIG_SWRESAMPLE
|
|
|
|
const AVClass *swr_class = swr_get_class();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (!strcmp(opt, "debug") || !strcmp(opt, "fdebug"))
|
|
|
|
av_log_set_level(AV_LOG_DEBUG);
|
|
|
|
|
|
|
|
if (!(p = strchr(opt, ':')))
|
|
|
|
p = opt + strlen(opt);
|
|
|
|
av_strlcpy(opt_stripped, opt, FFMIN(sizeof(opt_stripped), p - opt + 1));
|
|
|
|
|
|
|
|
if ((o = opt_find(&cc, opt_stripped, NULL, 0,
|
|
|
|
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ)) ||
|
|
|
|
((opt[0] == 'v' || opt[0] == 'a' || opt[0] == 's') &&
|
|
|
|
(o = opt_find(&cc, opt + 1, NULL, 0, AV_OPT_SEARCH_FAKE_OBJ)))) {
|
|
|
|
av_dict_set(&codec_opts, opt, arg, FLAGS);
|
|
|
|
consumed = 1;
|
|
|
|
}
|
|
|
|
if ((o = opt_find(&fc, opt, NULL, 0,
|
|
|
|
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
|
|
|
|
av_dict_set(&format_opts, opt, arg, FLAGS);
|
|
|
|
if (consumed)
|
|
|
|
av_log(NULL, AV_LOG_VERBOSE, "Routing option %s to both codec and muxer layer\n", opt);
|
|
|
|
consumed = 1;
|
|
|
|
}
|
|
|
|
#if CONFIG_SWSCALE
|
|
|
|
if (!consumed && (o = opt_find(&sc, opt, NULL, 0,
|
|
|
|
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
|
|
|
|
if (!strcmp(opt, "srcw") || !strcmp(opt, "srch") ||
|
|
|
|
!strcmp(opt, "dstw") || !strcmp(opt, "dsth") ||
|
|
|
|
!strcmp(opt, "src_format") || !strcmp(opt, "dst_format")) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "Directly using swscale dimensions/format options is not supported, please use the -s or -pix_fmt options\n");
|
|
|
|
return AVERROR(EINVAL);
|
|
|
|
}
|
|
|
|
av_dict_set(&sws_dict, opt, arg, FLAGS);
|
|
|
|
|
|
|
|
consumed = 1;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
if (!consumed && !strcmp(opt, "sws_flags")) {
|
|
|
|
av_log(NULL, AV_LOG_WARNING, "Ignoring %s %s, due to disabled swscale\n", opt, arg);
|
|
|
|
consumed = 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
#if CONFIG_SWRESAMPLE
|
|
|
|
if (!consumed && (o=opt_find(&swr_class, opt, NULL, 0,
|
|
|
|
AV_OPT_SEARCH_CHILDREN | AV_OPT_SEARCH_FAKE_OBJ))) {
|
|
|
|
av_dict_set(&swr_opts, opt, arg, FLAGS);
|
|
|
|
consumed = 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (consumed)
|
|
|
|
return 0;
|
|
|
|
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, int nb_groups,
|
|
|
|
const char *opt)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < nb_groups; i++) {
|
|
|
|
const OptionGroupDef *p = &groups[i];
|
|
|
|
if (p->sep && !strcmp(p->sep, opt))
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
g->sws_dict = sws_dict;
|
|
|
|
g->swr_opts = swr_opts;
|
|
|
|
g->codec_opts = codec_opts;
|
|
|
|
g->format_opts = format_opts;
|
|
|
|
|
|
|
|
codec_opts = NULL;
|
|
|
|
format_opts = NULL;
|
|
|
|
sws_dict = NULL;
|
|
|
|
swr_opts = NULL;
|
|
|
|
|
|
|
|
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, int nb_groups)
|
|
|
|
{
|
|
|
|
static const OptionGroupDef global_group = { "global" };
|
|
|
|
int i;
|
|
|
|
|
|
|
|
memset(octx, 0, sizeof(*octx));
|
|
|
|
|
|
|
|
octx->nb_groups = nb_groups;
|
|
|
|
octx->groups = av_calloc(octx->nb_groups, sizeof(*octx->groups));
|
|
|
|
if (!octx->groups)
|
|
|
|
report_and_exit(AVERROR(ENOMEM));
|
|
|
|
|
|
|
|
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 = "";
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
av_dict_free(&l->groups[j].sws_dict);
|
|
|
|
av_dict_free(&l->groups[j].swr_opts);
|
|
|
|
}
|
|
|
|
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 nb_groups)
|
|
|
|
{
|
|
|
|
int optindex = 1;
|
|
|
|
int dashdash = -2;
|
|
|
|
|
|
|
|
/* perform system-dependent conversions for arguments list */
|
|
|
|
prepare_app_arguments(&argc, &argv);
|
|
|
|
|
|
|
|
init_parse_context(octx, groups, nb_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);
|
|
|
|
|
|
|
|
if (opt[0] == '-' && opt[1] == '-' && !opt[2]) {
|
|
|
|
dashdash = optindex;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
/* unnamed group separators, e.g. output filename */
|
|
|
|
if (opt[0] != '-' || !opt[1] || dashdash+1 == optindex) {
|
|
|
|
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, nb_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 option(s) found in the "
|
|
|
|
"command: may be ignored.\n");
|
|
|
|
|
|
|
|
av_log(NULL, AV_LOG_DEBUG, "Finished splitting the commandline.\n");
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void print_error(const char *filename, int err)
|
|
|
|
{
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "%s: %s\n", filename, av_err2str(err));
|
|
|
|
}
|
|
|
|
|
|
|
|
int read_yesno(void)
|
|
|
|
{
|
|
|
|
int c = getchar();
|
|
|
|
int yesno = (av_toupper(c) == 'Y');
|
|
|
|
|
|
|
|
while (c != '\n' && c != EOF)
|
|
|
|
c = getchar();
|
|
|
|
|
|
|
|
return yesno;
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE *get_preset_file(char *filename, size_t filename_size,
|
|
|
|
const char *preset_name, int is_path,
|
|
|
|
const char *codec_name)
|
|
|
|
{
|
|
|
|
FILE *f = NULL;
|
|
|
|
int i;
|
|
|
|
#if HAVE_GETMODULEHANDLE && defined(_WIN32)
|
|
|
|
char *datadir = NULL;
|
|
|
|
#endif
|
|
|
|
char *env_home = getenv_utf8("HOME");
|
|
|
|
char *env_ffmpeg_datadir = getenv_utf8("FFMPEG_DATADIR");
|
|
|
|
const char *base[3] = { env_ffmpeg_datadir,
|
|
|
|
env_home, /* index=1(HOME) is special: search in a .ffmpeg subfolder */
|
|
|
|
FFMPEG_DATADIR, };
|
|
|
|
|
|
|
|
if (is_path) {
|
|
|
|
av_strlcpy(filename, preset_name, filename_size);
|
|
|
|
f = fopen_utf8(filename, "r");
|
|
|
|
} else {
|
|
|
|
#if HAVE_GETMODULEHANDLE && defined(_WIN32)
|
|
|
|
wchar_t *datadir_w = get_module_filename(NULL);
|
|
|
|
base[2] = NULL;
|
|
|
|
|
|
|
|
if (wchartoutf8(datadir_w, &datadir))
|
|
|
|
datadir = NULL;
|
|
|
|
av_free(datadir_w);
|
|
|
|
|
|
|
|
if (datadir)
|
|
|
|
{
|
|
|
|
char *ls;
|
|
|
|
for (ls = datadir; *ls; ls++)
|
|
|
|
if (*ls == '\\') *ls = '/';
|
|
|
|
|
|
|
|
if (ls = strrchr(datadir, '/'))
|
|
|
|
{
|
|
|
|
ptrdiff_t datadir_len = ls - datadir;
|
|
|
|
size_t desired_size = datadir_len + strlen("/ffpresets") + 1;
|
|
|
|
char *new_datadir = av_realloc_array(
|
|
|
|
datadir, desired_size, sizeof *datadir);
|
|
|
|
if (new_datadir) {
|
|
|
|
datadir = new_datadir;
|
|
|
|
datadir[datadir_len] = 0;
|
|
|
|
strncat(datadir, "/ffpresets", desired_size - 1 - datadir_len);
|
|
|
|
base[2] = datadir;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
for (i = 0; i < 3 && !f; i++) {
|
|
|
|
if (!base[i])
|
|
|
|
continue;
|
|
|
|
snprintf(filename, filename_size, "%s%s/%s.ffpreset", base[i],
|
|
|
|
i != 1 ? "" : "/.ffmpeg", preset_name);
|
|
|
|
f = fopen_utf8(filename, "r");
|
|
|
|
if (!f && codec_name) {
|
|
|
|
snprintf(filename, filename_size,
|
|
|
|
"%s%s/%s-%s.ffpreset",
|
|
|
|
base[i], i != 1 ? "" : "/.ffmpeg", codec_name,
|
|
|
|
preset_name);
|
|
|
|
f = fopen_utf8(filename, "r");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if HAVE_GETMODULEHANDLE && defined(_WIN32)
|
|
|
|
av_free(datadir);
|
|
|
|
#endif
|
|
|
|
freeenv_utf8(env_ffmpeg_datadir);
|
|
|
|
freeenv_utf8(env_home);
|
|
|
|
return f;
|
|
|
|
}
|
|
|
|
|
|
|
|
int check_stream_specifier(AVFormatContext *s, AVStream *st, const char *spec)
|
|
|
|
{
|
|
|
|
int ret = avformat_match_stream_specifier(s, st, spec);
|
|
|
|
if (ret < 0)
|
|
|
|
av_log(s, AV_LOG_ERROR, "Invalid stream specifier: %s.\n", spec);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
AVDictionary *filter_codec_opts(AVDictionary *opts, enum AVCodecID codec_id,
|
|
|
|
AVFormatContext *s, AVStream *st, const AVCodec *codec)
|
|
|
|
{
|
|
|
|
AVDictionary *ret = NULL;
|
|
|
|
const AVDictionaryEntry *t = NULL;
|
|
|
|
int flags = s->oformat ? AV_OPT_FLAG_ENCODING_PARAM
|
|
|
|
: AV_OPT_FLAG_DECODING_PARAM;
|
|
|
|
char prefix = 0;
|
|
|
|
const AVClass *cc = avcodec_get_class();
|
|
|
|
|
|
|
|
if (!codec)
|
|
|
|
codec = s->oformat ? avcodec_find_encoder(codec_id)
|
|
|
|
: avcodec_find_decoder(codec_id);
|
|
|
|
|
|
|
|
switch (st->codecpar->codec_type) {
|
|
|
|
case AVMEDIA_TYPE_VIDEO:
|
|
|
|
prefix = 'v';
|
|
|
|
flags |= AV_OPT_FLAG_VIDEO_PARAM;
|
|
|
|
break;
|
|
|
|
case AVMEDIA_TYPE_AUDIO:
|
|
|
|
prefix = 'a';
|
|
|
|
flags |= AV_OPT_FLAG_AUDIO_PARAM;
|
|
|
|
break;
|
|
|
|
case AVMEDIA_TYPE_SUBTITLE:
|
|
|
|
prefix = 's';
|
|
|
|
flags |= AV_OPT_FLAG_SUBTITLE_PARAM;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
while (t = av_dict_iterate(opts, t)) {
|
|
|
|
const AVClass *priv_class;
|
|
|
|
char *p = strchr(t->key, ':');
|
|
|
|
|
|
|
|
/* check stream specification in opt name */
|
|
|
|
if (p)
|
|
|
|
switch (check_stream_specifier(s, st, p + 1)) {
|
|
|
|
case 1: *p = 0; break;
|
|
|
|
case 0: continue;
|
|
|
|
default: exit_program(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (av_opt_find(&cc, t->key, NULL, flags, AV_OPT_SEARCH_FAKE_OBJ) ||
|
|
|
|
!codec ||
|
|
|
|
((priv_class = codec->priv_class) &&
|
|
|
|
av_opt_find(&priv_class, t->key, NULL, flags,
|
|
|
|
AV_OPT_SEARCH_FAKE_OBJ)))
|
|
|
|
av_dict_set(&ret, t->key, t->value, 0);
|
|
|
|
else if (t->key[0] == prefix &&
|
|
|
|
av_opt_find(&cc, t->key + 1, NULL, flags,
|
|
|
|
AV_OPT_SEARCH_FAKE_OBJ))
|
|
|
|
av_dict_set(&ret, t->key + 1, t->value, 0);
|
|
|
|
|
|
|
|
if (p)
|
|
|
|
*p = ':';
|
|
|
|
}
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
AVDictionary **setup_find_stream_info_opts(AVFormatContext *s,
|
|
|
|
AVDictionary *codec_opts)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
AVDictionary **opts;
|
|
|
|
|
|
|
|
if (!s->nb_streams)
|
|
|
|
return NULL;
|
|
|
|
opts = av_calloc(s->nb_streams, sizeof(*opts));
|
|
|
|
if (!opts)
|
|
|
|
report_and_exit(AVERROR(ENOMEM));
|
|
|
|
for (i = 0; i < s->nb_streams; i++)
|
|
|
|
opts[i] = filter_codec_opts(codec_opts, s->streams[i]->codecpar->codec_id,
|
|
|
|
s, s->streams[i], NULL);
|
|
|
|
return opts;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *grow_array(void *array, int elem_size, int *size, int new_size)
|
|
|
|
{
|
|
|
|
if (new_size >= INT_MAX / elem_size) {
|
|
|
|
av_log(NULL, AV_LOG_ERROR, "Array too big.\n");
|
|
|
|
exit_program(1);
|
|
|
|
}
|
|
|
|
if (*size < new_size) {
|
|
|
|
uint8_t *tmp = av_realloc_array(array, new_size, elem_size);
|
|
|
|
if (!tmp)
|
|
|
|
report_and_exit(AVERROR(ENOMEM));
|
|
|
|
memset(tmp + *size*elem_size, 0, (new_size-*size) * elem_size);
|
|
|
|
*size = new_size;
|
|
|
|
return tmp;
|
|
|
|
}
|
|
|
|
return array;
|
|
|
|
}
|
|
|
|
|
|
|
|
void *allocate_array_elem(void *ptr, size_t elem_size, int *nb_elems)
|
|
|
|
{
|
|
|
|
void *new_elem;
|
|
|
|
|
|
|
|
if (!(new_elem = av_mallocz(elem_size)) ||
|
|
|
|
av_dynarray_add_nofree(ptr, nb_elems, new_elem) < 0)
|
|
|
|
report_and_exit(AVERROR(ENOMEM));
|
|
|
|
return new_elem;
|
|
|
|
}
|
|
|
|
|
|
|
|
double get_rotation(const int32_t *displaymatrix)
|
|
|
|
{
|
|
|
|
double theta = 0;
|
avformat, ffmpeg: deprecate old rotation API
The old "API" that signaled rotation as a metadata value has been
replaced by DISPLAYMATRIX side data quite a while ago.
There is no reason to make muxers/demuxers/API users support both. In
addition, the metadata API is dangerous, as user tags could "leak" into
it, creating unintended features or bugs.
ffmpeg CLI has to be updated to use the new API. In particular, we must
not allow to leak the "rotate" tag into the muxer. Some muxers will
catch this properly (like mov), but others (like mkv) can add it as
generic tag. Note applications, which use libavformat and assume the
old rotate API, will interpret such "rotate" user tags as rotate
metadata (which it is not), and incorrectly rotate the video.
The ffmpeg/ffplay tools drop the use of the old API for muxing and
demuxing, as all muxers/demuxers support the new API. This will mean
that the tools will not mistakenly interpret per-track "rotate" user
tags as rotate metadata. It will _not_ be treated as regression.
Unfortunately, hacks have been added, that allow the user to override
rotation by setting metadata explicitly, e.g. via
-metadata:s:v:0 rotate=0
See references to trac #4560. fate-filter-meta-4560-rotate0 tests this.
It's easier to adjust the hack for supporting it than arguing for its
removal, so ffmpeg CLI now explicitly catches this case, and essentially
replaces the "rotate" value with a display matrix side data. (It would
be easier for both user and implementation to create an explicit option
for rotation.)
When the code under FF_API_OLD_ROTATE_API is disabled, one FATE
reference file has to be updated (because "rotate" is not exported
anymore).
Tested-by: Michael Niedermayer <michael@niedermayer.cc>
Reviewed-by: Michael Niedermayer <michael@niedermayer.cc>
8 years ago
|
|
|
if (displaymatrix)
|
|
|
|
theta = -round(av_display_rotation_get((int32_t*) displaymatrix));
|
|
|
|
|
|
|
|
theta -= 360*floor(theta/360 + 0.9/360);
|
|
|
|
|
|
|
|
if (fabs(theta - 90*round(theta/90)) > 2)
|
|
|
|
av_log(NULL, AV_LOG_WARNING, "Odd rotation angle.\n"
|
|
|
|
"If you want to help, upload a sample "
|
|
|
|
"of this file to https://streams.videolan.org/upload/ "
|
|
|
|
"and contact the ffmpeg-devel mailing list. (ffmpeg-devel@ffmpeg.org)");
|
|
|
|
|
|
|
|
return theta;
|
|
|
|
}
|