Browse code

avconv: support stream specifiers in -metadata and -map_metadata

Signed-off-by: Anton Khirnov <anton@khirnov.net>

Alexandra Khirnova authored on 2011/12/13 19:23:06
Showing 3 changed files
... ...
@@ -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.