Signed-off-by: Anton Khirnov <anton@khirnov.net>
| ... | ... |
@@ -332,6 +332,8 @@ typedef struct OptionsContext {
|
| 332 | 332 |
int nb_inter_matrices; |
| 333 | 333 |
SpecifierOpt *top_field_first; |
| 334 | 334 |
int nb_top_field_first; |
| 335 |
+ SpecifierOpt *metadata_map; |
|
| 336 |
+ int nb_metadata_map; |
|
| 335 | 337 |
SpecifierOpt *presets; |
| 336 | 338 |
int nb_presets; |
| 337 | 339 |
SpecifierOpt *copy_initial_nonkeyframes; |
| ... | ... |
@@ -2752,7 +2754,13 @@ static int opt_attach(OptionsContext *o, const char *opt, const char *arg) |
| 2752 | 2752 |
return 0; |
| 2753 | 2753 |
} |
| 2754 | 2754 |
|
| 2755 |
-static void parse_meta_type(char *arg, char *type, int *index) |
|
| 2755 |
+/** |
|
| 2756 |
+ * Parse a metadata specifier in arg. |
|
| 2757 |
+ * @param type metadata type is written here -- g(lobal)/s(tream)/c(hapter)/p(rogram) |
|
| 2758 |
+ * @param index for type c/p, chapter/program index is written here |
|
| 2759 |
+ * @param stream_spec for type s, the stream specifier is written here |
|
| 2760 |
+ */ |
|
| 2761 |
+static void parse_meta_type(char *arg, char *type, int *index, const char **stream_spec) |
|
| 2756 | 2762 |
{
|
| 2757 | 2763 |
if (*arg) {
|
| 2758 | 2764 |
*type = *arg; |
| ... | ... |
@@ -2760,6 +2768,12 @@ static void parse_meta_type(char *arg, char *type, int *index) |
| 2760 | 2760 |
case 'g': |
| 2761 | 2761 |
break; |
| 2762 | 2762 |
case 's': |
| 2763 |
+ if (*(++arg) && *arg != ':') {
|
|
| 2764 |
+ av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", arg); |
|
| 2765 |
+ exit_program(1); |
|
| 2766 |
+ } |
|
| 2767 |
+ *stream_spec = *arg == ':' ? arg + 1 : ""; |
|
| 2768 |
+ break; |
|
| 2763 | 2769 |
case 'c': |
| 2764 | 2770 |
case 'p': |
| 2765 | 2771 |
if (*(++arg) == ':') |
| ... | ... |
@@ -2773,31 +2787,76 @@ static void parse_meta_type(char *arg, char *type, int *index) |
| 2773 | 2773 |
*type = 'g'; |
| 2774 | 2774 |
} |
| 2775 | 2775 |
|
| 2776 |
-static int opt_map_metadata(OptionsContext *o, const char *opt, const char *arg) |
|
| 2776 |
+static int copy_metadata(char *outspec, char *inspec, AVFormatContext *oc, AVFormatContext *ic, OptionsContext *o) |
|
| 2777 | 2777 |
{
|
| 2778 |
- MetadataMap *m, *m1; |
|
| 2779 |
- char *p; |
|
| 2780 |
- |
|
| 2781 |
- o->meta_data_maps = grow_array(o->meta_data_maps, sizeof(*o->meta_data_maps), |
|
| 2782 |
- &o->nb_meta_data_maps, o->nb_meta_data_maps + 1); |
|
| 2783 |
- |
|
| 2784 |
- m = &o->meta_data_maps[o->nb_meta_data_maps - 1][1]; |
|
| 2785 |
- m->file = strtol(arg, &p, 0); |
|
| 2786 |
- parse_meta_type(*p ? p + 1 : p, &m->type, &m->index); |
|
| 2778 |
+ AVDictionary **meta_in = NULL; |
|
| 2779 |
+ AVDictionary **meta_out; |
|
| 2780 |
+ int i, ret = 0; |
|
| 2781 |
+ char type_in, type_out; |
|
| 2782 |
+ const char *istream_spec = NULL, *ostream_spec = NULL; |
|
| 2783 |
+ int idx_in = 0, idx_out = 0; |
|
| 2787 | 2784 |
|
| 2788 |
- m1 = &o->meta_data_maps[o->nb_meta_data_maps - 1][0]; |
|
| 2789 |
- if (p = strchr(opt, ':')) |
|
| 2790 |
- parse_meta_type(p + 1, &m1->type, &m1->index); |
|
| 2791 |
- else |
|
| 2792 |
- m1->type = 'g'; |
|
| 2785 |
+ parse_meta_type(inspec, &type_in, &idx_in, &istream_spec); |
|
| 2786 |
+ parse_meta_type(outspec, &type_out, &idx_out, &ostream_spec); |
|
| 2793 | 2787 |
|
| 2794 |
- if (m->type == 'g' || m1->type == 'g') |
|
| 2788 |
+ if (type_in == 'g' || type_out == 'g') |
|
| 2795 | 2789 |
o->metadata_global_manual = 1; |
| 2796 |
- if (m->type == 's' || m1->type == 's') |
|
| 2790 |
+ if (type_in == 's' || type_out == 's') |
|
| 2797 | 2791 |
o->metadata_streams_manual = 1; |
| 2798 |
- if (m->type == 'c' || m1->type == 'c') |
|
| 2792 |
+ if (type_in == 'c' || type_out == 'c') |
|
| 2799 | 2793 |
o->metadata_chapters_manual = 1; |
| 2800 | 2794 |
|
| 2795 |
+#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ |
|
| 2796 |
+ if ((index) < 0 || (index) >= (nb_elems)) {\
|
|
| 2797 |
+ av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps.\n",\ |
|
| 2798 |
+ (desc), (index));\ |
|
| 2799 |
+ exit_program(1);\ |
|
| 2800 |
+ } |
|
| 2801 |
+ |
|
| 2802 |
+#define SET_DICT(type, meta, context, index)\ |
|
| 2803 |
+ switch (type) {\
|
|
| 2804 |
+ case 'g':\ |
|
| 2805 |
+ meta = &context->metadata;\ |
|
| 2806 |
+ break;\ |
|
| 2807 |
+ case 'c':\ |
|
| 2808 |
+ METADATA_CHECK_INDEX(index, context->nb_chapters, "chapter")\ |
|
| 2809 |
+ meta = &context->chapters[index]->metadata;\ |
|
| 2810 |
+ break;\ |
|
| 2811 |
+ case 'p':\ |
|
| 2812 |
+ METADATA_CHECK_INDEX(index, context->nb_programs, "program")\ |
|
| 2813 |
+ meta = &context->programs[index]->metadata;\ |
|
| 2814 |
+ break;\ |
|
| 2815 |
+ }\ |
|
| 2816 |
+ |
|
| 2817 |
+ SET_DICT(type_in, meta_in, ic, idx_in); |
|
| 2818 |
+ SET_DICT(type_out, meta_out, oc, idx_out); |
|
| 2819 |
+ |
|
| 2820 |
+ /* for input streams choose first matching stream */ |
|
| 2821 |
+ if (type_in == 's') {
|
|
| 2822 |
+ for (i = 0; i < ic->nb_streams; i++) {
|
|
| 2823 |
+ if ((ret = check_stream_specifier(ic, ic->streams[i], istream_spec)) > 0) {
|
|
| 2824 |
+ meta_in = &ic->streams[i]->metadata; |
|
| 2825 |
+ break; |
|
| 2826 |
+ } else if (ret < 0) |
|
| 2827 |
+ exit_program(1); |
|
| 2828 |
+ } |
|
| 2829 |
+ if (!meta_in) {
|
|
| 2830 |
+ av_log(NULL, AV_LOG_FATAL, "Stream specifier %s does not match any streams.\n", istream_spec); |
|
| 2831 |
+ exit_program(1); |
|
| 2832 |
+ } |
|
| 2833 |
+ } |
|
| 2834 |
+ |
|
| 2835 |
+ if (type_out == 's') {
|
|
| 2836 |
+ for (i = 0; i < oc->nb_streams; i++) {
|
|
| 2837 |
+ if ((ret = check_stream_specifier(oc, oc->streams[i], ostream_spec)) > 0) {
|
|
| 2838 |
+ meta_out = &oc->streams[i]->metadata; |
|
| 2839 |
+ av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); |
|
| 2840 |
+ } else if (ret < 0) |
|
| 2841 |
+ exit_program(1); |
|
| 2842 |
+ } |
|
| 2843 |
+ } else |
|
| 2844 |
+ av_dict_copy(meta_out, *meta_in, AV_DICT_DONT_OVERWRITE); |
|
| 2845 |
+ |
|
| 2801 | 2846 |
return 0; |
| 2802 | 2847 |
} |
| 2803 | 2848 |
|
| ... | ... |
@@ -3702,6 +3761,20 @@ static void opt_output_file(void *optctx, const char *filename) |
| 3702 | 3702 |
oc->max_delay = (int)(o->mux_max_delay * AV_TIME_BASE); |
| 3703 | 3703 |
oc->flags |= AVFMT_FLAG_NONBLOCK; |
| 3704 | 3704 |
|
| 3705 |
+ /* copy metadata */ |
|
| 3706 |
+ for (i = 0; i < o->nb_metadata_map; i++) {
|
|
| 3707 |
+ char *p; |
|
| 3708 |
+ int in_file_index = strtol(o->metadata_map[i].u.str, &p, 0); |
|
| 3709 |
+ |
|
| 3710 |
+ if (in_file_index < 0) |
|
| 3711 |
+ continue; |
|
| 3712 |
+ if (in_file_index >= nb_input_files) {
|
|
| 3713 |
+ av_log(NULL, AV_LOG_FATAL, "Invalid input file index %d while processing metadata maps\n", in_file_index); |
|
| 3714 |
+ exit_program(1); |
|
| 3715 |
+ } |
|
| 3716 |
+ copy_metadata(o->metadata_map[i].specifier, *p ? p + 1 : p, oc, input_files[in_file_index].ctx, o); |
|
| 3717 |
+ } |
|
| 3718 |
+ |
|
| 3705 | 3719 |
/* copy chapters */ |
| 3706 | 3720 |
if (o->chapters_input_file >= nb_input_files) {
|
| 3707 | 3721 |
if (o->chapters_input_file == INT_MAX) {
|
| ... | ... |
@@ -3722,52 +3795,6 @@ static void opt_output_file(void *optctx, const char *filename) |
| 3722 | 3722 |
copy_chapters(&input_files[o->chapters_input_file], &output_files[nb_output_files - 1], |
| 3723 | 3723 |
!o->metadata_chapters_manual); |
| 3724 | 3724 |
|
| 3725 |
- /* copy metadata */ |
|
| 3726 |
- for (i = 0; i < o->nb_meta_data_maps; i++) {
|
|
| 3727 |
- AVFormatContext *files[2]; |
|
| 3728 |
- AVDictionary **meta[2]; |
|
| 3729 |
- int j; |
|
| 3730 |
- |
|
| 3731 |
-#define METADATA_CHECK_INDEX(index, nb_elems, desc)\ |
|
| 3732 |
- if ((index) < 0 || (index) >= (nb_elems)) {\
|
|
| 3733 |
- av_log(NULL, AV_LOG_FATAL, "Invalid %s index %d while processing metadata maps\n",\ |
|
| 3734 |
- (desc), (index));\ |
|
| 3735 |
- exit_program(1);\ |
|
| 3736 |
- } |
|
| 3737 |
- |
|
| 3738 |
- int in_file_index = o->meta_data_maps[i][1].file; |
|
| 3739 |
- if (in_file_index < 0) |
|
| 3740 |
- continue; |
|
| 3741 |
- METADATA_CHECK_INDEX(in_file_index, nb_input_files, "input file") |
|
| 3742 |
- |
|
| 3743 |
- files[0] = oc; |
|
| 3744 |
- files[1] = input_files[in_file_index].ctx; |
|
| 3745 |
- |
|
| 3746 |
- for (j = 0; j < 2; j++) {
|
|
| 3747 |
- MetadataMap *map = &o->meta_data_maps[i][j]; |
|
| 3748 |
- |
|
| 3749 |
- switch (map->type) {
|
|
| 3750 |
- case 'g': |
|
| 3751 |
- meta[j] = &files[j]->metadata; |
|
| 3752 |
- break; |
|
| 3753 |
- case 's': |
|
| 3754 |
- METADATA_CHECK_INDEX(map->index, files[j]->nb_streams, "stream") |
|
| 3755 |
- meta[j] = &files[j]->streams[map->index]->metadata; |
|
| 3756 |
- break; |
|
| 3757 |
- case 'c': |
|
| 3758 |
- METADATA_CHECK_INDEX(map->index, files[j]->nb_chapters, "chapter") |
|
| 3759 |
- meta[j] = &files[j]->chapters[map->index]->metadata; |
|
| 3760 |
- break; |
|
| 3761 |
- case 'p': |
|
| 3762 |
- METADATA_CHECK_INDEX(map->index, files[j]->nb_programs, "program") |
|
| 3763 |
- meta[j] = &files[j]->programs[map->index]->metadata; |
|
| 3764 |
- break; |
|
| 3765 |
- } |
|
| 3766 |
- } |
|
| 3767 |
- |
|
| 3768 |
- av_dict_copy(meta[0], *meta[1], AV_DICT_DONT_OVERWRITE); |
|
| 3769 |
- } |
|
| 3770 |
- |
|
| 3771 | 3725 |
/* copy global metadata by default */ |
| 3772 | 3726 |
if (!o->metadata_global_manual && nb_input_files) |
| 3773 | 3727 |
av_dict_copy(&oc->metadata, input_files[0].ctx->metadata, |
| ... | ... |
@@ -3785,7 +3812,8 @@ static void opt_output_file(void *optctx, const char *filename) |
| 3785 | 3785 |
for (i = 0; i < o->nb_metadata; i++) {
|
| 3786 | 3786 |
AVDictionary **m; |
| 3787 | 3787 |
char type, *val; |
| 3788 |
- int index = 0; |
|
| 3788 |
+ const char *stream_spec; |
|
| 3789 |
+ int index = 0, j, ret; |
|
| 3789 | 3790 |
|
| 3790 | 3791 |
val = strchr(o->metadata[i].u.str, '='); |
| 3791 | 3792 |
if (!val) {
|
| ... | ... |
@@ -3795,31 +3823,34 @@ static void opt_output_file(void *optctx, const char *filename) |
| 3795 | 3795 |
} |
| 3796 | 3796 |
*val++ = 0; |
| 3797 | 3797 |
|
| 3798 |
- parse_meta_type(o->metadata[i].specifier, &type, &index); |
|
| 3799 |
- switch (type) {
|
|
| 3800 |
- case 'g': |
|
| 3801 |
- m = &oc->metadata; |
|
| 3802 |
- break; |
|
| 3803 |
- case 's': |
|
| 3804 |
- if (index < 0 || index >= oc->nb_streams) {
|
|
| 3805 |
- av_log(NULL, AV_LOG_FATAL, "Invalid stream index %d in metadata specifier.\n", index); |
|
| 3806 |
- exit_program(1); |
|
| 3798 |
+ parse_meta_type(o->metadata[i].specifier, &type, &index, &stream_spec); |
|
| 3799 |
+ if (type == 's') {
|
|
| 3800 |
+ for (j = 0; j < oc->nb_streams; j++) {
|
|
| 3801 |
+ if ((ret = check_stream_specifier(oc, oc->streams[j], stream_spec)) > 0) {
|
|
| 3802 |
+ av_dict_set(&oc->streams[j]->metadata, o->metadata[i].u.str, *val ? val : NULL, 0); |
|
| 3803 |
+ } else if (ret < 0) |
|
| 3804 |
+ exit_program(1); |
|
| 3807 | 3805 |
} |
| 3808 |
- m = &oc->streams[index]->metadata; |
|
| 3809 |
- break; |
|
| 3810 |
- case 'c': |
|
| 3811 |
- if (index < 0 || index >= oc->nb_chapters) {
|
|
| 3812 |
- av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); |
|
| 3806 |
+ printf("ret %d, stream_spec %s\n", ret, stream_spec);
|
|
| 3807 |
+ } |
|
| 3808 |
+ else {
|
|
| 3809 |
+ switch (type) {
|
|
| 3810 |
+ case 'g': |
|
| 3811 |
+ m = &oc->metadata; |
|
| 3812 |
+ break; |
|
| 3813 |
+ case 'c': |
|
| 3814 |
+ if (index < 0 || index >= oc->nb_chapters) {
|
|
| 3815 |
+ av_log(NULL, AV_LOG_FATAL, "Invalid chapter index %d in metadata specifier.\n", index); |
|
| 3816 |
+ exit_program(1); |
|
| 3817 |
+ } |
|
| 3818 |
+ m = &oc->chapters[index]->metadata; |
|
| 3819 |
+ break; |
|
| 3820 |
+ default: |
|
| 3821 |
+ av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); |
|
| 3813 | 3822 |
exit_program(1); |
| 3814 | 3823 |
} |
| 3815 |
- m = &oc->chapters[index]->metadata; |
|
| 3816 |
- break; |
|
| 3817 |
- default: |
|
| 3818 |
- av_log(NULL, AV_LOG_FATAL, "Invalid metadata specifier %s.\n", o->metadata[i].specifier); |
|
| 3819 |
- exit_program(1); |
|
| 3824 |
+ av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); |
|
| 3820 | 3825 |
} |
| 3821 |
- |
|
| 3822 |
- av_dict_set(m, o->metadata[i].u.str, *val ? val : NULL, 0); |
|
| 3823 | 3826 |
} |
| 3824 | 3827 |
|
| 3825 | 3828 |
reset_options(o); |
| ... | ... |
@@ -4114,7 +4145,7 @@ static const OptionDef options[] = {
|
| 4114 | 4114 |
{ "codec", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(codec_names)}, "codec name", "codec" },
|
| 4115 | 4115 |
{ "pre", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(presets)}, "preset name", "preset" },
|
| 4116 | 4116 |
{ "map", HAS_ARG | OPT_EXPERT | OPT_FUNC2, {(void*)opt_map}, "set input stream mapping", "[-]input_file_id[:stream_specifier][,sync_file_id[:stream_specifier]]" },
|
| 4117 |
- { "map_metadata", HAS_ARG | OPT_EXPERT | OPT_FUNC2, {(void*)opt_map_metadata}, "set metadata information of outfile from infile",
|
|
| 4117 |
+ { "map_metadata", HAS_ARG | OPT_STRING | OPT_SPEC, {.off = OFFSET(metadata_map)}, "set metadata information of outfile from infile",
|
|
| 4118 | 4118 |
"outfile[,metadata]:infile[,metadata]" }, |
| 4119 | 4119 |
{ "map_chapters", OPT_INT | HAS_ARG | OPT_EXPERT | OPT_OFFSET, {.off = OFFSET(chapters_input_file)}, "set chapters mapping", "input_file_index" },
|
| 4120 | 4120 |
{ "t", HAS_ARG | OPT_TIME | OPT_OFFSET, {.off = OFFSET(recording_time)}, "record or transcode \"duration\" seconds of audio/video", "duration" },
|
| ... | ... |
@@ -170,9 +170,9 @@ For example, for setting the title in the output file: |
| 170 | 170 |
avconv -i in.avi -metadata title="my title" out.flv |
| 171 | 171 |
@end example |
| 172 | 172 |
|
| 173 |
-To set the language of the second stream: |
|
| 173 |
+To set the language of the first audio stream: |
|
| 174 | 174 |
@example |
| 175 |
-avconv -i INPUT -metadata:s:1 language=eng OUTPUT |
|
| 175 |
+avconv -i INPUT -metadata:s:a:0 language=eng OUTPUT |
|
| 176 | 176 |
@end example |
| 177 | 177 |
|
| 178 | 178 |
@item -target @var{type} (@emph{output})
|
| ... | ... |
@@ -677,14 +677,28 @@ avconv -i INPUT -map 0 -map -0:a:1 OUTPUT |
| 677 | 677 |
|
| 678 | 678 |
Note that using this option disables the default mappings for this output file. |
| 679 | 679 |
|
| 680 |
-@item -map_metadata[:@var{metadata_type}][:@var{index}] @var{infile}[:@var{metadata_type}][:@var{index}] (@emph{output,per-metadata})
|
|
| 680 |
+@item -map_metadata[:@var{metadata_spec_out}] @var{infile}[:@var{metadata_spec_in}] (@emph{output,per-metadata})
|
|
| 681 | 681 |
Set metadata information of the next output file from @var{infile}. Note that
|
| 682 | 682 |
those are file indices (zero-based), not filenames. |
| 683 |
-Optional @var{metadata_type} parameters specify, which metadata to copy - (g)lobal
|
|
| 684 |
-(i.e. metadata that applies to the whole file), per-(s)tream, per-(c)hapter or |
|
| 685 |
-per-(p)rogram. All metadata specifiers other than global must be followed by the |
|
| 686 |
-stream/chapter/program index. If metadata specifier is omitted, it defaults to |
|
| 687 |
-global. |
|
| 683 |
+Optional @var{metadata_spec_in/out} parameters specify, which metadata to copy.
|
|
| 684 |
+A metadata specifier can have the following forms: |
|
| 685 |
+@table @option |
|
| 686 |
+@item @var{g}
|
|
| 687 |
+global metadata, i.e. metadata that applies to the whole file |
|
| 688 |
+ |
|
| 689 |
+@item @var{s}[:@var{stream_spec}]
|
|
| 690 |
+per-stream metadata. @var{stream_spec} is a stream specifier as described
|
|
| 691 |
+in the @ref{Stream specifiers} chapter. In an input metadata specifier, the first
|
|
| 692 |
+matching stream is copied from. In an output metadata specifier, all matching |
|
| 693 |
+streams are copied to. |
|
| 694 |
+ |
|
| 695 |
+@item @var{c}:@var{chapter_index}
|
|
| 696 |
+per-chapter metadata. @var{chapter_index} is the zero-based chapter index.
|
|
| 697 |
+ |
|
| 698 |
+@item @var{p}:@var{program_index}
|
|
| 699 |
+per-program metadata. @var{program_index} is the zero-based program index.
|
|
| 700 |
+@end table |
|
| 701 |
+If metadata specifier is omitted, it defaults to global. |
|
| 688 | 702 |
|
| 689 | 703 |
By default, global metadata is copied from the first input file, |
| 690 | 704 |
per-stream and per-chapter metadata is copied along with streams/chapters. These |
| ... | ... |
@@ -696,6 +710,14 @@ of the output file: |
| 696 | 696 |
@example |
| 697 | 697 |
avconv -i in.ogg -map_metadata 0:s:0 out.mp3 |
| 698 | 698 |
@end example |
| 699 |
+ |
|
| 700 |
+To do the reverse, i.e. copy global metadata to all audio streams: |
|
| 701 |
+@example |
|
| 702 |
+avconv -i in.mkv -map_metadata:s:a 0:g out.mkv |
|
| 703 |
+@end example |
|
| 704 |
+Note that simple @code{0} would work as well in this example, since global
|
|
| 705 |
+metadata is assumed by default. |
|
| 706 |
+ |
|
| 699 | 707 |
@item -map_chapters @var{input_file_index} (@emph{output})
|
| 700 | 708 |
Copy chapters from input file with index @var{input_file_index} to the next
|
| 701 | 709 |
output file. If no chapter mapping is specified, then chapters are copied from |
| ... | ... |
@@ -11,6 +11,7 @@ corresponding value to true. They can be set to false by prefixing |
| 11 | 11 |
with "no" the option name, for example using "-nofoo" in the |
| 12 | 12 |
command line will set to false the boolean option with name "foo". |
| 13 | 13 |
|
| 14 |
+@anchor{Stream specifiers}
|
|
| 14 | 15 |
@section Stream specifiers |
| 15 | 16 |
Some options are applied per-stream, e.g. bitrate or codec. Stream specifiers |
| 16 | 17 |
are used to precisely specify which stream(s) does a given option belong to. |