Browse code

ffprobe: add XML writer

Stefano Sabatini authored on 2011/10/09 07:24:31
Showing 5 changed files
... ...
@@ -9,6 +9,7 @@ version next:
9 9
 - SMJPEG demuxer
10 10
 - dv: add timecode to metadata
11 11
 - thumbnail video filter
12
+- XML output in ffprobe
12 13
 
13 14
 
14 15
 version 0.9:
... ...
@@ -39,7 +39,7 @@ FFLIBS-$(CONFIG_SWSCALE)  += swscale
39 39
 
40 40
 FFLIBS := avutil
41 41
 
42
-DATA_FILES := $(wildcard $(SRC_PATH)/presets/*.ffpreset)
42
+DATA_FILES := $(wildcard $(SRC_PATH)/presets/*.ffpreset) doc/ffprobe.xsd
43 43
 
44 44
 SKIPHEADERS = cmdutils_common_opts.h
45 45
 
... ...
@@ -218,6 +218,39 @@ Each section is printed using JSON notation.
218 218
 
219 219
 For more information about JSON, see @url{http://www.json.org/}.
220 220
 
221
+@section xml
222
+XML based format.
223
+
224
+The XML output is described in the XML schema description file
225
+@file{ffprobe.xsd} installed in the FFmpeg datadir.
226
+
227
+Note that the output issued will be compliant to the
228
+@file{ffprobe.xsd} schema only when no special global output options
229
+(@option{unit}, @option{prefix}, @option{byte_binary_prefix},
230
+@option{sexagesimal} etc.) are specified.
231
+
232
+This writer accepts options as a list of @var{key}=@var{value} pairs,
233
+separated by ":".
234
+
235
+The description of the accepted options follows.
236
+
237
+@table @option
238
+
239
+@item fully_qualified, q
240
+If set to 1 specify if the output should be fully qualified. Default
241
+value is 0.
242
+This is required for generating an XML file which can be validated
243
+through an XSD file.
244
+
245
+@item xsd_compliant, x
246
+If set to 1 perform more checks for ensuring that the output is XSD
247
+compliant. Default value is 0.
248
+This option automatically sets @option{fully_qualified} to 1.
249
+@end table
250
+
251
+For more information about the XML format, see
252
+@url{http://www.w3.org/XML/}.
253
+
221 254
 @c man end WRITERS
222 255
 
223 256
 @include decoders.texi
224 257
new file mode 100644
... ...
@@ -0,0 +1,96 @@
0
+<?xml version="1.0" encoding="UTF-8"?>
1
+
2
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
3
+    targetNamespace="http://www.ffmpeg.org/schema/ffprobe"
4
+    xmlns:ffprobe="http://www.ffmpeg.org/schema/ffprobe">
5
+
6
+    <xsd:element name="ffprobe" type="ffprobe:ffprobeType"/>
7
+
8
+    <xsd:complexType name="ffprobeType">
9
+        <xsd:sequence>
10
+            <xsd:element name="packets"  type="ffprobe:packetsType" minOccurs="0" maxOccurs="1" />
11
+            <xsd:element name="streams"  type="ffprobe:streamsType" minOccurs="0" maxOccurs="1" />
12
+            <xsd:element name="format"   type="ffprobe:formatType"  minOccurs="0" maxOccurs="1" />
13
+        </xsd:sequence>
14
+    </xsd:complexType>
15
+
16
+    <xsd:complexType name="packetsType">
17
+        <xsd:sequence>
18
+            <xsd:element name="packet" type="ffprobe:packetType" minOccurs="0" maxOccurs="unbounded"/>
19
+        </xsd:sequence>
20
+    </xsd:complexType>
21
+
22
+    <xsd:complexType name="packetType">
23
+      <xsd:attribute name="codec_type"    type="xsd:string" use="required" />
24
+      <xsd:attribute name="stream_index"  type="xsd:int" use="required" />
25
+      <xsd:attribute name="pts"           type="xsd:long"  />
26
+      <xsd:attribute name="pts_time"      type="xsd:float" />
27
+      <xsd:attribute name="dts"           type="xsd:long"  />
28
+      <xsd:attribute name="dts_time"      type="xsd:float" />
29
+      <xsd:attribute name="duration"      type="xsd:long"  />
30
+      <xsd:attribute name="duration_time" type="xsd:float" />
31
+      <xsd:attribute name="size"          type="xsd:long" use="required" />
32
+      <xsd:attribute name="pos"           type="xsd:long"  />
33
+      <xsd:attribute name="flags"         type="xsd:string" use="required" />
34
+    </xsd:complexType>
35
+
36
+    <xsd:complexType name="streamsType">
37
+        <xsd:sequence>
38
+            <xsd:element name="stream" type="ffprobe:streamType" minOccurs="0" maxOccurs="unbounded"/>
39
+        </xsd:sequence>
40
+    </xsd:complexType>
41
+
42
+    <xsd:complexType name="streamType">
43
+      <xsd:attribute name="index"            type="xsd:int" use="required"/>
44
+      <xsd:attribute name="codec_name"       type="xsd:string" />
45
+      <xsd:attribute name="codec_long_name"  type="xsd:string" />
46
+      <xsd:attribute name="codec_type"       type="xsd:string" />
47
+      <xsd:attribute name="codec_time_base"  type="xsd:string" use="required"/>
48
+      <xsd:attribute name="codec_tag"        type="xsd:string" use="required"/>
49
+      <xsd:attribute name="codec_tag_string" type="xsd:string" use="required"/>
50
+
51
+      <!-- video attributes -->
52
+      <xsd:attribute name="width"                type="xsd:int"/>
53
+      <xsd:attribute name="height"               type="xsd:int"/>
54
+      <xsd:attribute name="has_b_frames"         type="xsd:int"/>
55
+      <xsd:attribute name="sample_aspect_ratio"  type="xsd:string"/>
56
+      <xsd:attribute name="display_aspect_ratio" type="xsd:string"/>
57
+      <xsd:attribute name="pix_fmt"              type="xsd:string"/>
58
+      <xsd:attribute name="level"                type="xsd:int"/>
59
+      <xsd:attribute name="timecode"             type="xsd:string"/>
60
+
61
+      <!-- audio attributes -->
62
+      <xsd:attribute name="sample_fmt"       type="xsd:string"/>
63
+      <xsd:attribute name="sample_rate"      type="xsd:int"/>
64
+      <xsd:attribute name="channels"         type="xsd:int"/>
65
+      <xsd:attribute name="bits_per_sample"  type="xsd:int"/>
66
+
67
+      <xsd:attribute name="id"               type="xsd:string"/>
68
+      <xsd:attribute name="r_frame_rate"     type="xsd:string" use="required"/>
69
+      <xsd:attribute name="avg_frame_rate"   type="xsd:string" use="required"/>
70
+      <xsd:attribute name="time_base"        type="xsd:string" use="required"/>
71
+      <xsd:attribute name="start_time"       type="xsd:float"/>
72
+      <xsd:attribute name="duration"         type="xsd:float"/>
73
+      <xsd:attribute name="nb_frames"        type="xsd:int"/>
74
+    </xsd:complexType>
75
+
76
+    <xsd:complexType name="formatType">
77
+      <xsd:sequence>
78
+        <xsd:element name="tag" type="ffprobe:tagType" minOccurs="0" maxOccurs="unbounded"/>
79
+      </xsd:sequence>
80
+
81
+      <xsd:attribute name="filename"         type="xsd:string" use="required"/>
82
+      <xsd:attribute name="nb_streams"       type="xsd:int"    use="required"/>
83
+      <xsd:attribute name="format_name"      type="xsd:string" use="required"/>
84
+      <xsd:attribute name="format_long_name" type="xsd:string" use="required"/>
85
+      <xsd:attribute name="start_time"       type="xsd:float"/>
86
+      <xsd:attribute name="duration"         type="xsd:float"/>
87
+      <xsd:attribute name="size"             type="xsd:long"/>
88
+      <xsd:attribute name="bit_rate"         type="xsd:long"/>
89
+    </xsd:complexType>
90
+
91
+    <xsd:complexType name="tagType">
92
+      <xsd:attribute name="key"   type="xsd:string" use="required"/>
93
+      <xsd:attribute name="value" type="xsd:string" use="required"/>
94
+    </xsd:complexType>
95
+</xsd:schema>
... ...
@@ -867,6 +867,248 @@ static const Writer json_writer = {
867 867
     .show_tags            = json_show_tags,
868 868
 };
869 869
 
870
+/* XML output */
871
+
872
+typedef struct {
873
+    const AVClass *class;
874
+    int within_tag;
875
+    int multiple_entries; ///< tells if the given chapter requires multiple entries
876
+    int indent_level;
877
+    int fully_qualified;
878
+    int xsd_strict;
879
+    char *buf;
880
+    size_t buf_size;
881
+} XMLContext;
882
+
883
+#undef OFFSET
884
+#define OFFSET(x) offsetof(XMLContext, x)
885
+
886
+static const AVOption xml_options[] = {
887
+    {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.dbl=0},  0, 1 },
888
+    {"q",               "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.dbl=0},  0, 1 },
889
+    {"xsd_strict",      "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_INT, {.dbl=0},  0, 1 },
890
+    {"x",               "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_INT, {.dbl=0},  0, 1 },
891
+    {NULL},
892
+};
893
+
894
+static const char *xml_get_name(void *ctx)
895
+{
896
+    return "xml";
897
+}
898
+
899
+static const AVClass xml_class = {
900
+    "XMLContext",
901
+    xml_get_name,
902
+    xml_options
903
+};
904
+
905
+static av_cold int xml_init(WriterContext *wctx, const char *args, void *opaque)
906
+{
907
+    XMLContext *xml = wctx->priv;
908
+    int err;
909
+
910
+    xml->class = &xml_class;
911
+    av_opt_set_defaults(xml);
912
+
913
+    if (args &&
914
+        (err = (av_set_options_string(xml, args, "=", ":"))) < 0) {
915
+        av_log(wctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
916
+        return err;
917
+    }
918
+
919
+    if (xml->xsd_strict) {
920
+        xml->fully_qualified = 1;
921
+#define CHECK_COMPLIANCE(opt, opt_name)                                 \
922
+        if (opt) {                                                      \
923
+            av_log(wctx, AV_LOG_ERROR,                                  \
924
+                   "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
925
+                   "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
926
+        }
927
+        CHECK_COMPLIANCE(show_private_data, "private");
928
+        CHECK_COMPLIANCE(show_value_unit,   "unit");
929
+        CHECK_COMPLIANCE(use_value_prefix,  "prefix");
930
+    }
931
+
932
+    xml->buf_size = ESCAPE_INIT_BUF_SIZE;
933
+    if (!(xml->buf = av_malloc(xml->buf_size)))
934
+        return AVERROR(ENOMEM);
935
+    return 0;
936
+}
937
+
938
+static av_cold void xml_uninit(WriterContext *wctx)
939
+{
940
+    XMLContext *xml = wctx->priv;
941
+    av_freep(&xml->buf);
942
+}
943
+
944
+static const char *xml_escape_str(char **dst, size_t *dst_size, const char *src,
945
+                                  void *log_ctx)
946
+{
947
+    const char *p;
948
+    char *q;
949
+    size_t size = 1;
950
+
951
+    /* precompute size */
952
+    for (p = src; *p; p++, size++) {
953
+        ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-10);
954
+        switch (*p) {
955
+        case '&' : size += strlen("&amp;");  break;
956
+        case '<' : size += strlen("&lt;");   break;
957
+        case '>' : size += strlen("&gt;");   break;
958
+        case '\"': size += strlen("&quot;"); break;
959
+        case '\'': size += strlen("&apos;"); break;
960
+        default: size++;
961
+        }
962
+    }
963
+    ESCAPE_REALLOC_BUF(dst_size, dst, src, size);
964
+
965
+#define COPY_STR(str) {      \
966
+        const char *s = str; \
967
+        while (*s)           \
968
+            *q++ = *s++;     \
969
+    }
970
+
971
+    p = src;
972
+    q = *dst;
973
+    while (*p) {
974
+        switch (*p) {
975
+        case '&' : COPY_STR("&amp;");  break;
976
+        case '<' : COPY_STR("&lt;");   break;
977
+        case '>' : COPY_STR("&gt;");   break;
978
+        case '\"': COPY_STR("&quot;"); break;
979
+        case '\'': COPY_STR("&apos;"); break;
980
+        default: *q++ = *p;
981
+        }
982
+        p++;
983
+    }
984
+    *q = 0;
985
+
986
+    return *dst;
987
+}
988
+
989
+static void xml_print_header(WriterContext *wctx)
990
+{
991
+    XMLContext *xml = wctx->priv;
992
+    const char *qual = " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
993
+        "xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' "
994
+        "xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'";
995
+
996
+    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
997
+    printf("<%sffprobe%s>\n",
998
+           xml->fully_qualified ? "ffprobe:" : "",
999
+           xml->fully_qualified ? qual : "");
1000
+
1001
+    xml->indent_level++;
1002
+}
1003
+
1004
+static void xml_print_footer(WriterContext *wctx)
1005
+{
1006
+    XMLContext *xml = wctx->priv;
1007
+
1008
+    xml->indent_level--;
1009
+    printf("</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
1010
+}
1011
+
1012
+#define XML_INDENT() { int i; for (i = 0; i < xml->indent_level; i++) printf(INDENT); }
1013
+
1014
+static void xml_print_chapter_header(WriterContext *wctx, const char *chapter)
1015
+{
1016
+    XMLContext *xml = wctx->priv;
1017
+
1018
+    if (wctx->nb_chapter)
1019
+        printf("\n");
1020
+    xml->multiple_entries = !strcmp(chapter, "packets") || !strcmp(chapter, "streams");
1021
+
1022
+    if (xml->multiple_entries) {
1023
+        XML_INDENT(); printf("<%s>\n", chapter);
1024
+        xml->indent_level++;
1025
+    }
1026
+}
1027
+
1028
+static void xml_print_chapter_footer(WriterContext *wctx, const char *chapter)
1029
+{
1030
+    XMLContext *xml = wctx->priv;
1031
+
1032
+    if (xml->multiple_entries) {
1033
+        xml->indent_level--;
1034
+        XML_INDENT(); printf("</%s>\n", chapter);
1035
+    }
1036
+}
1037
+
1038
+static void xml_print_section_header(WriterContext *wctx, const char *section)
1039
+{
1040
+    XMLContext *xml = wctx->priv;
1041
+
1042
+    XML_INDENT(); printf("<%s ", section);
1043
+    xml->within_tag = 1;
1044
+}
1045
+
1046
+static void xml_print_section_footer(WriterContext *wctx, const char *section)
1047
+{
1048
+    XMLContext *xml = wctx->priv;
1049
+
1050
+    if (xml->within_tag)
1051
+        printf("/>\n");
1052
+    else {
1053
+        XML_INDENT(); printf("</%s>\n", section);
1054
+    }
1055
+}
1056
+
1057
+static void xml_print_str(WriterContext *wctx, const char *key, const char *value)
1058
+{
1059
+    XMLContext *xml = wctx->priv;
1060
+
1061
+    if (wctx->nb_item)
1062
+        printf(" ");
1063
+    printf("%s=\"%s\"", key, xml_escape_str(&xml->buf, &xml->buf_size, value, wctx));
1064
+}
1065
+
1066
+static void xml_print_int(WriterContext *wctx, const char *key, long long int value)
1067
+{
1068
+    if (wctx->nb_item)
1069
+        printf(" ");
1070
+    printf("%s=\"%lld\"", key, value);
1071
+}
1072
+
1073
+static void xml_show_tags(WriterContext *wctx, AVDictionary *dict)
1074
+{
1075
+    XMLContext *xml = wctx->priv;
1076
+    AVDictionaryEntry *tag = NULL;
1077
+    int is_first = 1;
1078
+
1079
+    xml->indent_level++;
1080
+    while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) {
1081
+        if (is_first) {
1082
+            /* close section tag */
1083
+            printf(">\n");
1084
+            xml->within_tag = 0;
1085
+            is_first = 0;
1086
+        }
1087
+        XML_INDENT();
1088
+        printf("<tag key=\"%s\"",
1089
+               xml_escape_str(&xml->buf, &xml->buf_size, tag->key,   wctx));
1090
+        printf(" value=\"%s\"/>\n",
1091
+               xml_escape_str(&xml->buf, &xml->buf_size, tag->value, wctx));
1092
+    }
1093
+    xml->indent_level--;
1094
+}
1095
+
1096
+static Writer xml_writer = {
1097
+    .name                 = "xml",
1098
+    .priv_size            = sizeof(XMLContext),
1099
+    .init                 = xml_init,
1100
+    .uninit               = xml_uninit,
1101
+    .print_header         = xml_print_header,
1102
+    .print_footer         = xml_print_footer,
1103
+    .print_chapter_header = xml_print_chapter_header,
1104
+    .print_chapter_footer = xml_print_chapter_footer,
1105
+    .print_section_header = xml_print_section_header,
1106
+    .print_section_footer = xml_print_section_footer,
1107
+    .print_integer        = xml_print_int,
1108
+    .print_string         = xml_print_str,
1109
+    .show_tags            = xml_show_tags,
1110
+};
1111
+
870 1112
 static void writer_register_all(void)
871 1113
 {
872 1114
     static int initialized;
... ...
@@ -879,6 +1121,7 @@ static void writer_register_all(void)
879 879
     writer_register(&compact_writer);
880 880
     writer_register(&csv_writer);
881 881
     writer_register(&json_writer);
882
+    writer_register(&xml_writer);
882 883
 }
883 884
 
884 885
 #define print_fmt(k, f, ...) do {              \
... ...
@@ -1236,7 +1479,7 @@ static const OptionDef options[] = {
1236 1236
     { "pretty", 0, {(void*)&opt_pretty},
1237 1237
       "prettify the format of displayed values, make it more human readable" },
1238 1238
     { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format},
1239
-      "set the output printing format (available formats are: default, compact, csv, json)", "format" },
1239
+      "set the output printing format (available formats are: default, compact, csv, json, xml)", "format" },
1240 1240
     { "show_format",  OPT_BOOL, {(void*)&do_show_format} , "show format/container info" },
1241 1241
     { "show_packets", OPT_BOOL, {(void*)&do_show_packets}, "show packets info" },
1242 1242
     { "show_streams", OPT_BOOL, {(void*)&do_show_streams}, "show streams info" },