1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-01-24 13:56:33 +02:00

avconv: support stream specifiers in -metadata and -map_metadata

Signed-off-by: Anton Khirnov <anton@khirnov.net>
This commit is contained in:
Alexandra Khirnova 2011-12-13 10:23:06 +00:00 committed by Anton Khirnov
parent bb9747c8ee
commit a7b5e841ff
3 changed files with 152 additions and 98 deletions

211
avconv.c
View File

@ -332,6 +332,8 @@ typedef struct OptionsContext {
int nb_inter_matrices; int nb_inter_matrices;
SpecifierOpt *top_field_first; SpecifierOpt *top_field_first;
int nb_top_field_first; int nb_top_field_first;
SpecifierOpt *metadata_map;
int nb_metadata_map;
SpecifierOpt *presets; SpecifierOpt *presets;
int nb_presets; int nb_presets;
SpecifierOpt *copy_initial_nonkeyframes; SpecifierOpt *copy_initial_nonkeyframes;
@ -2752,7 +2754,13 @@ static int opt_attach(OptionsContext *o, const char *opt, const char *arg)
return 0; return 0;
} }
static void parse_meta_type(char *arg, char *type, int *index) /**
* Parse a metadata specifier in arg.
* @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram)
* @param index for type c/p, chapter/program index is written here
* @param stream_spec for type s, the stream specifier is written here
*/
static void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec)
{ {
if (*arg) { if (*arg) {
*type = *arg; *type = *arg;
@ -2760,6 +2768,12 @@ static void parse_meta_type(char *arg, char *type, int *index)
case 'g': case 'g':
break; break;
case 's': case 's':
if (*(++arg) && *arg != ':') {
av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg);
exit_program(1);
}
*stream_spec = *arg == ':' ? arg + 1 : "";
break;
case 'c': case 'c':
case 'p': case 'p':
if (*(++arg) == ':') if (*(++arg) == ':')
@ -2773,31 +2787,76 @@ static void parse_meta_type(char *arg, char *type, int *index)
*type = 'g'; *type = 'g';
} }
static int opt_map_metadata(OptionsContext *o, const char *opt, const char *arg) static int copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o)
{ {
MetadataMap *m, *m1; AVDictionary **meta_in = NULL;
char *p; AVDictionary **meta_out;
int i, ret = 0;
char type_in, type_out;
const char *istream_spec = NULL, *ostream_spec = NULL;
int idx_in = 0, idx_out = 0;
o->meta_data_maps = grow_array(o->meta_data_maps, sizeof(*o->meta_data_maps), parse_meta_type(inspec, &type_in, &idx_in, &istream_spec);
&o->nb_meta_data_maps, o->nb_meta_data_maps + 1); parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec);
m = &o->meta_data_maps[o->nb_meta_data_maps - 1][1]; if (type_in == 'g' || type_out == 'g')
m->file = strtol(arg, &p, 0);
parse_meta_type(*p ? p + 1 : p, &m->type, &m->index);
m1 = &o->meta_data_maps[o->nb_meta_data_maps - 1][0];
if (p = strchr(opt, ':'))
parse_meta_type(p + 1, &m1->type, &m1->index);
else
m1->type = 'g';
if (m->type == 'g' || m1->type == 'g')
o->metadata_global_manual = 1; o->metadata_global_manual = 1;
if (m->type == 's' || m1->type == 's') if (type_in == 's' || type_out == 's')
o->metadata_streams_manual = 1; o->metadata_streams_manual = 1;
if (m->type == 'c' || m1->type == 'c') if (type_in == 'c' || type_out == 'c')
o->metadata_chapters_manual = 1; o->metadata_chapters_manual = 1;
#define METADATA_CHECK_INDEX(index, nb_elems, desc)\
if ((index) < 0 || (index) >= (nb_elems)) {\
av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\
(desc), (index));\
exit_program(1);\
}
#define SET_DICT(type, meta, context, index)\
switch (type) {\
case 'g':\
meta = &context->metadata;\
break;\
case 'c':\
METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\
meta = &context->chapters[index]->metadata;\
break;\
case 'p':\
METADATA_CHECK_INDEX(index, context->nb_programs, "program")\
meta = &context->programs[index]->metadata;\
break;\
}\
SET_DICT(type_in, meta_in, ic, idx_in);
SET_DICT(type_out, meta_out, oc, idx_out);
/* for input streams choose first matching stream */
if (type_in == 's') {
for (i = 0; i < ic->nb_streams; i++) {
if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) {
meta_in = &ic->streams[i]->metadata;
break;
} else if (ret < 0)
exit_program(1);
}
if (!meta_in) {
av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec);
exit_program(1);
}
}
if (type_out == 's') {
for (i = 0; i < oc->nb_streams; i++) {
if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) {
meta_out = &oc->streams[i]->metadata;
av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE);
} else if (ret < 0)
exit_program(1);
}
} else
av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE);
return 0; return 0;
} }
@ -3702,6 +3761,20 @@ static void opt_output_file(void *optctx, const char *filename)
oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE);
oc->flags |= AVFMT_FLAG_NONBLOCK; oc->flags |= AVFMT_FLAG_NONBLOCK;
/* copy metadata */
for (i = 0; i < o->nb_metadata_map; i++) {
char *p;
int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0);
if (in_file_index < 0)
continue;
if (in_file_index >= nb_input_files) {
av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index);
exit_program(1);
}
copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, input_files[in_file_index].ctx, o);
}
/* copy chapters */ /* copy chapters */
if (o->chapters_input_file >= nb_input_files) { if (o->chapters_input_file >= nb_input_files) {
if (o->chapters_input_file == INT_MAX) { if (o->chapters_input_file == INT_MAX) {
@ -3722,52 +3795,6 @@ static void opt_output_file(void *optctx, const char *filename)
copy_chapters(&input_files[o->chapters_input_file], &output_files[nb_output_files - 1], copy_chapters(&input_files[o->chapters_input_file], &output_files[nb_output_files - 1],
!o->metadata_chapters_manual); !o->metadata_chapters_manual);
/* copy metadata */
for (i = 0; i < o->nb_meta_data_maps; i++) {
AVFormatContext *files[2];
AVDictionary **meta[2];
int j;
#define METADATA_CHECK_INDEX(index, nb_elems, desc)\
if ((index) < 0 || (index) >= (nb_elems)) {\
av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps\n",\
(desc), (index));\
exit_program(1);\
}
int in_file_index = o->meta_data_maps[i][1].file;
if (in_file_index < 0)
continue;
METADATA_CHECK_INDEX(in_file_index, nb_input_files, "input file")
files[0] = oc;
files[1] = input_files[in_file_index].ctx;
for (j = 0; j < 2; j++) {
MetadataMap *map = &o->meta_data_maps[i][j];
switch (map->type) {
case 'g':
meta[j] = &files[j]->metadata;
break;
case 's':
METADATA_CHECK_INDEX(map->index, files[j]->nb_streams, "stream")
meta[j] = &files[j]->streams[map->index]->metadata;
break;
case 'c':
METADATA_CHECK_INDEX(map->index, files[j]->nb_chapters, "chapter")
meta[j] = &files[j]->chapters[map->index]->metadata;
break;
case 'p':
METADATA_CHECK_INDEX(map->index, files[j]->nb_programs, "program")
meta[j] = &files[j]->programs[map->index]->metadata;
break;
}
}
av_dict_copy(meta[0], *meta[1], AV_DICT_DONT_OVERWRITE);
}
/* copy global metadata by default */ /* copy global metadata by default */
if (!o->metadata_global_manual && nb_input_files) if (!o->metadata_global_manual && nb_input_files)
av_dict_copy(&oc->metadata, input_files[0].ctx->metadata, av_dict_copy(&oc->metadata, input_files[0].ctx->metadata,
@ -3785,7 +3812,8 @@ static void opt_output_file(void *optctx, const char *filename)
for (i = 0; i < o->nb_metadata; i++) { for (i = 0; i < o->nb_metadata; i++) {
AVDictionary **m; AVDictionary **m;
char type, *val; char type, *val;
int index = 0; const char *stream_spec;
int index = 0, j, ret;
val = strchr(o->metadata[i].u.str, '='); val = strchr(o->metadata[i].u.str, '=');
if (!val) { if (!val) {
@ -3795,31 +3823,34 @@ static void opt_output_file(void *optctx, const char *filename)
} }
*val++ = 0; *val++ = 0;
parse_meta_type(o->metadata[i].specifier, &type, &index); parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec);
switch (type) { if (type == 's') {
case 'g': for (j = 0; j < oc->nb_streams; j++) {
m = &oc->metadata; if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) {
break; av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0);
case 's': } else if (ret < 0)
if (index < 0 || index >= oc->nb_streams) { exit_program(1);
av_log(NULL, AV_LOG_FATAL, "Invalid stream index %d in metadata specifier.\n", index);
exit_program(1);
} }
m = &oc->streams[index]->metadata; printf("ret %d, stream_spec %s\n", ret, stream_spec);
break; }
case 'c': else {
if (index < 0 || index >= oc->nb_chapters) { switch (type) {
av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); case 'g':
exit_program(1); m = &oc->metadata;
} break;
m = &oc->chapters[index]->metadata; case 'c':
break; if (index < 0 || index >= oc->nb_chapters) {
default: av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index);
av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); exit_program(1);
exit_program(1); }
m = &oc->chapters[index]->metadata;
break;
default:
av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier);
exit_program(1);
}
av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0);
} }
av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0);
} }
reset_options(o); reset_options(o);
@ -4114,7 +4145,7 @@ static const OptionDef options[] = {
{ "codec", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(codec_names)}, "codec name", "codec" }, { "codec", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(codec_names)}, "codec name", "codec" },
{ "pre", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(presets)}, "preset name", "preset" }, { "pre", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(presets)}, "preset name", "preset" },
{ "map", HAS_ARG | OPT_EXPERT | OPT_FUNC2, {(void*)opt_map}, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" }, { "map", HAS_ARG | OPT_EXPERT | OPT_FUNC2, {(void*)opt_map}, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" },
{ "map_metadata", HAS_ARG | OPT_EXPERT | OPT_FUNC2, {(void*)opt_map_metadata}, "set metadata information of outfile from infile", { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(metadata_map)}, "set metadata information of outfile from infile",
"outfile[,metadata]:infile[,metadata]" }, "outfile[,metadata]:infile[,metadata]" },
{ "map_chapters", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_OFFSET, {.off = OFFSET(chapters_input_file)}, "set chapters mapping", "input_file_index" }, { "map_chapters", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_OFFSET, {.off = OFFSET(chapters_input_file)}, "set chapters mapping", "input_file_index" },
{ "t", HAS_ARG | OPT_TIME | OPT_OFFSET, {.off = OFFSET(recording_time)}, "record or transcode \"duration\" seconds of audio/video", "duration" }, { "t", HAS_ARG | OPT_TIME | OPT_OFFSET, {.off = OFFSET(recording_time)}, "record or transcode \"duration\" seconds of audio/video", "duration" },

View File

@ -170,9 +170,9 @@ For example, for setting the title in the output file:
avconv -i in.avi -metadata title="my title" out.flv avconv -i in.avi -metadata title="my title" out.flv
@end example @end example
To set the language of the second stream: To set the language of the first audio stream:
@example @example
avconv -i INPUT -metadata:s:1 language=eng OUTPUT avconv -i INPUT -metadata:s:a:0 language=eng OUTPUT
@end example @end example
@item -target @var{type} (@emph{output}) @item -target @var{type} (@emph{output})
@ -677,14 +677,28 @@ avconv -i INPUT -map 0 -map -0:a:1 OUTPUT
Note that using this option disables the default mappings for this output file. Note that using this option disables the default mappings for this output file.
@item -map_metadata[:@var{metadata_type}][:@var{index}] @var{infile}[:@var{metadata_type}][:@var{index}] (@emph{output,per-metadata}) @item -map_metadata[:@var{metadata_spec_out}] @var{infile}[:@var{metadata_spec_in}] (@emph{output,per-metadata})
Set metadata information of the next output file from @var{infile}. Note that Set metadata information of the next output file from @var{infile}. Note that
those are file indices (zero-based), not filenames. those are file indices (zero-based), not filenames.
Optional @var{metadata_type} parameters specify, which metadata to copy - (g)lobal Optional @var{metadata_spec_in/out} parameters specify, which metadata to copy.
(i.e. metadata that applies to the whole file), per-(s)tream, per-(c)hapter or A metadata specifier can have the following forms:
per-(p)rogram. All metadata specifiers other than global must be followed by the @table @option
stream/chapter/program index. If metadata specifier is omitted, it defaults to @item @var{g}
global. global metadata, i.e. metadata that applies to the whole file
@item @var{s}[:@var{stream_spec}]
per-stream metadata. @var{stream_spec} is a stream specifier as described
in the @ref{Stream specifiers} chapter. In an input metadata specifier, the first
matching stream is copied from. In an output metadata specifier, all matching
streams are copied to.
@item @var{c}:@var{chapter_index}
per-chapter metadata. @var{chapter_index} is the zero-based chapter index.
@item @var{p}:@var{program_index}
per-program metadata. @var{program_index} is the zero-based program index.
@end table
If metadata specifier is omitted, it defaults to global.
By default, global metadata is copied from the first input file, By default, global metadata is copied from the first input file,
per-stream and per-chapter metadata is copied along with streams/chapters. These per-stream and per-chapter metadata is copied along with streams/chapters. These
@ -696,6 +710,14 @@ of the output file:
@example @example
avconv -i in.ogg -map_metadata 0:s:0 out.mp3 avconv -i in.ogg -map_metadata 0:s:0 out.mp3
@end example @end example
To do the reverse, i.e. copy global metadata to all audio streams:
@example
avconv -i in.mkv -map_metadata:s:a 0:g out.mkv
@end example
Note that simple @code{0} would work as well in this example, since global
metadata is assumed by default.
@item -map_chapters @var{input_file_index} (@emph{output}) @item -map_chapters @var{input_file_index} (@emph{output})
Copy chapters from input file with index @var{input_file_index} to the next Copy chapters from input file with index @var{input_file_index} to the next
output file. If no chapter mapping is specified, then chapters are copied from output file. If no chapter mapping is specified, then chapters are copied from

View File

@ -11,6 +11,7 @@ corresponding value to true. They can be set to false by prefixing
with "no" the option name, for example using "-nofoo" in the with "no" the option name, for example using "-nofoo" in the
command line will set to false the boolean option with name "foo". command line will set to false the boolean option with name "foo".
@anchor{Stream specifiers}
@section Stream specifiers @section Stream specifiers
Some options are applied per-stream, e.g. bitrate or codec. Stream specifiers Some options are applied per-stream, e.g. bitrate or codec. Stream specifiers
are used to precisely specify which stream(s) does a given option belong to. are used to precisely specify which stream(s) does a given option belong to.