From a7b5e841ffe4b0f8423288965b8d069bd2a7a792 Mon Sep 17 00:00:00 2001 From: Alexandra Khirnova Date: Tue, 13 Dec 2011 10:23:06 +0000 Subject: [PATCH] avconv: support stream specifiers in -metadata and -map_metadata Signed-off-by: Anton Khirnov --- avconv.c | 207 ++++++++++++++++++++--------------- doc/avconv.texi | 38 +++++-- doc/avtools-common-opts.texi | 1 + 3 files changed, 150 insertions(+), 96 deletions(-) diff --git a/avconv.c b/avconv.c index 90ed00d9357..ecbf5233c4e 100644 --- a/avconv.c +++ b/avconv.c @@ -332,6 +332,8 @@ typedef struct OptionsContext { int nb_inter_matrices; SpecifierOpt *top_field_first; int nb_top_field_first; + SpecifierOpt *metadata_map; + int nb_metadata_map; SpecifierOpt *presets; int nb_presets; SpecifierOpt *copy_initial_nonkeyframes; @@ -2752,7 +2754,13 @@ static int opt_attach(OptionsContext *o, const char *opt, const char *arg) 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) { *type = *arg; @@ -2760,6 +2768,12 @@ static void parse_meta_type(char *arg, char *type, int *index) case 'g': break; 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 'p': if (*(++arg) == ':') @@ -2773,31 +2787,76 @@ static void parse_meta_type(char *arg, char *type, int *index) *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; - char *p; - - o->meta_data_maps = grow_array(o->meta_data_maps, sizeof(*o->meta_data_maps), - &o->nb_meta_data_maps, o->nb_meta_data_maps + 1); - - m = &o->meta_data_maps[o->nb_meta_data_maps - 1][1]; - m->file = strtol(arg, &p, 0); - parse_meta_type(*p ? p + 1 : p, &m->type, &m->index); + AVDictionary **meta_in = NULL; + 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; - 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'; + parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); + parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); - if (m->type == 'g' || m1->type == 'g') + if (type_in == 'g' || type_out == 'g') o->metadata_global_manual = 1; - if (m->type == 's' || m1->type == 's') + if (type_in == 's' || type_out == 's') o->metadata_streams_manual = 1; - if (m->type == 'c' || m1->type == 'c') + if (type_in == 'c' || type_out == 'c') 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; } @@ -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->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 */ if (o->chapters_input_file >= nb_input_files) { 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], !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 */ if (!o->metadata_global_manual && nb_input_files) 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++) { AVDictionary **m; char type, *val; - int index = 0; + const char *stream_spec; + int index = 0, j, ret; val = strchr(o->metadata[i].u.str, '='); if (!val) { @@ -3795,31 +3823,34 @@ static void opt_output_file(void *optctx, const char *filename) } *val++ = 0; - parse_meta_type(o->metadata[i].specifier, &type, &index); - switch (type) { - case 'g': - m = &oc->metadata; - break; - case 's': - if (index < 0 || index >= oc->nb_streams) { - av_log(NULL, AV_LOG_FATAL, "Invalid stream index %d in metadata specifier.\n", index); - exit_program(1); + parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); + if (type == 's') { + for (j = 0; j < oc->nb_streams; j++) { + if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) { + av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); + } else if (ret < 0) + exit_program(1); } - m = &oc->streams[index]->metadata; - break; - case 'c': - if (index < 0 || index >= oc->nb_chapters) { - av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + printf("ret %d, stream_spec %s\n", ret, stream_spec); + } + else { + switch (type) { + case 'g': + m = &oc->metadata; + break; + case 'c': + if (index < 0 || index >= oc->nb_chapters) { + av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); + 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); } - 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); @@ -4114,7 +4145,7 @@ static const OptionDef options[] = { { "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" }, { "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]" }, { "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" }, diff --git a/doc/avconv.texi b/doc/avconv.texi index 84e0c1c4ef4..157e233cc61 100644 --- a/doc/avconv.texi +++ b/doc/avconv.texi @@ -170,9 +170,9 @@ For example, for setting the title in the output file: avconv -i in.avi -metadata title="my title" out.flv @end example -To set the language of the second stream: +To set the language of the first audio stream: @example -avconv -i INPUT -metadata:s:1 language=eng OUTPUT +avconv -i INPUT -metadata:s:a:0 language=eng OUTPUT @end example @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. -@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 those are file indices (zero-based), not filenames. -Optional @var{metadata_type} parameters specify, which metadata to copy - (g)lobal -(i.e. metadata that applies to the whole file), per-(s)tream, per-(c)hapter or -per-(p)rogram. All metadata specifiers other than global must be followed by the -stream/chapter/program index. If metadata specifier is omitted, it defaults to -global. +Optional @var{metadata_spec_in/out} parameters specify, which metadata to copy. +A metadata specifier can have the following forms: +@table @option +@item @var{g} +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, per-stream and per-chapter metadata is copied along with streams/chapters. These @@ -696,6 +710,14 @@ of the output file: @example avconv -i in.ogg -map_metadata 0:s:0 out.mp3 @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}) 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 diff --git a/doc/avtools-common-opts.texi b/doc/avtools-common-opts.texi index 372a6f2dc1f..18e7ba5cbe4 100644 --- a/doc/avtools-common-opts.texi +++ b/doc/avtools-common-opts.texi @@ -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 command line will set to false the boolean option with name "foo". +@anchor{Stream specifiers} @section 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. -- 2.39.5