[FFmpeg-devel] [PATCH] ffprobe: add XML output

Stefano Sabatini stefasab at gmail.com
Sun Oct 9 13:42:38 CEST 2011


---
 Makefile         |    2 +-
 doc/ffprobe.texi |   23 +++++++
 doc/ffprobe.xsd  |   90 ++++++++++++++++++++++++++
 ffprobe.c        |  185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 299 insertions(+), 1 deletions(-)
 create mode 100644 doc/ffprobe.xsd

diff --git a/Makefile b/Makefile
index 167dd32..f111bb1 100644
--- a/Makefile
+++ b/Makefile
@@ -38,7 +38,7 @@ FFLIBS-$(CONFIG_SWSCALE)  += swscale
 
 FFLIBS := avutil
 
-DATA_FILES := $(wildcard $(SRC_PATH)/ffpresets/*.ffpreset)
+DATA_FILES := $(wildcard $(SRC_PATH)/ffpresets/*.ffpreset) doc/ffprobe.xsd
 
 SKIPHEADERS = cmdutils_common_opts.h
 
diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi
index 7c31d06..5c1a316 100644
--- a/doc/ffprobe.texi
+++ b/doc/ffprobe.texi
@@ -204,6 +204,29 @@ Each section is printed using JSON notation.
 
 For more information about JSON, see @url{http://www.json.org/}.
 
+ at section xml
+XML based format.
+
+The XML output is described in the XML schema description file
+ at file{ffprobe.xsd} installed in the FFmpeg datadir.
+
+This writer accepts options as a list of @var{key}=@var{value} pairs,
+separated by ":".
+
+The description of the accepted options follows.
+
+ at table @option
+
+ at item fully_qualified, q
+If set to 1 specify if the output should be fully qualified. Default
+value is 0.
+This is required for generating an XML file which can be validated
+through an XSD file.
+ at end table
+
+For more information about the XML format, see
+ at url{http://www.w3.org/XML/}.
+
 @c man end WRITERS
 
 @include decoders.texi
diff --git a/doc/ffprobe.xsd b/doc/ffprobe.xsd
new file mode 100644
index 0000000..bd091dd
--- /dev/null
+++ b/doc/ffprobe.xsd
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+    targetNamespace="http://www.ffmpeg.org/schema/ffprobe"
+    xmlns:ffprobe="http://www.ffmpeg.org/schema/ffprobe">
+
+    <xsd:element name="ffprobe" type="ffprobe:ffprobe"/>
+
+    <xsd:complexType name="ffprobe">
+        <xsd:sequence>
+            <xsd:element name="packets"  type="ffprobe:packets" minOccurs="0" maxOccurs="1" />
+            <xsd:element name="streams"  type="ffprobe:streams" minOccurs="0" maxOccurs="1" />
+            <xsd:element name="format"   type="ffprobe:format"  minOccurs="0" maxOccurs="1" />
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="packets">
+        <xsd:sequence>
+            <xsd:element name="packet" type="ffprobe:packet" minOccurs="0" maxOccurs="unbounded"/>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="packet">
+      <xsd:attribute name="codec_type"    type="xsd:string"  use="required" />
+      <xsd:attribute name="stream_index"  type="xsd:integer" use="required" />
+      <xsd:attribute name="pts"           type="xsd:integer" use="required" />
+      <xsd:attribute name="pts_time"      type="xsd:decimal" use="required" />
+      <xsd:attribute name="dts"           type="xsd:integer" use="required" />
+      <xsd:attribute name="dts_time"      type="xsd:decimal" use="required" />
+      <xsd:attribute name="duration"      type="xsd:integer" use="required" />
+      <xsd:attribute name="duration_time" type="xsd:decimal" use="required" />
+      <xsd:attribute name="size"          type="xsd:decimal" use="required" />
+      <xsd:attribute name="pos"           type="xsd:integer" use="required" />
+      <xsd:attribute name="flags"         type="xsd:string"  use="required" />
+    </xsd:complexType>
+
+    <xsd:complexType name="streams">
+        <xsd:sequence>
+            <xsd:element name="stream" type="ffprobe:stream" minOccurs="0" maxOccurs="unbounded"/>
+        </xsd:sequence>
+    </xsd:complexType>
+
+    <xsd:complexType name="stream">
+      <xsd:attribute name="index"            type="xsd:integer" use="required"/>
+      <xsd:attribute name="codec_name"       type="xsd:string"  use="required"/>
+      <xsd:attribute name="codec_long_name"  type="xsd:string"  use="required"/>
+      <xsd:attribute name="codec_type"       type="xsd:string"  use="required"/>
+      <xsd:attribute name="codec_time_base"  type="xsd:string"  use="required"/>
+      <xsd:attribute name="codec_tag"        type="xsd:string"  use="required"/>
+      <xsd:attribute name="codec_tag_string" type="xsd:string"  use="required"/>
+
+      <!-- audio attributes -->
+      <xsd:attribute name="sample_rate"      type="xsd:string" />
+      <xsd:attribute name="channels"         type="xsd:integer"/>
+      <xsd:attribute name="bits_per_sample"  type="xsd:integer"/>
+
+      <!-- video attributes -->
+      <xsd:attribute name="width"        type="xsd:integer"/>
+      <xsd:attribute name="height"       type="xsd:integer"/>
+      <xsd:attribute name="has_b_frames" type="xsd:integer"/>
+      <xsd:attribute name="pix_fmt"      type="xsd:string" />
+      <xsd:attribute name="level"        type="xsd:integer"/>
+
+      <xsd:attribute name="r_frame_rate"     type="xsd:string"  use="required"/>
+      <xsd:attribute name="avg_frame_rate"   type="xsd:string"  use="required"/>
+      <xsd:attribute name="time_base"        type="xsd:string"  use="required"/>
+      <xsd:attribute name="start_time"       type="xsd:decimal" use="required"/>
+      <xsd:attribute name="duration"         type="xsd:string"  use="required"/>
+    </xsd:complexType>
+
+    <xsd:complexType name="format">
+      <xsd:sequence>
+        <xsd:element name="tag" type="ffprobe:tag" minOccurs="0" maxOccurs="unbounded"/>
+      </xsd:sequence>
+
+      <xsd:attribute name="filename"         type="xsd:string"  use="required"/>
+      <xsd:attribute name="nb_streams"       type="xsd:integer" use="required"/>
+      <xsd:attribute name="format_name"      type="xsd:string"  use="required"/>
+      <xsd:attribute name="format_long_name" type="xsd:string"  use="required"/>
+      <xsd:attribute name="start_time"       type="xsd:decimal" use="required"/>
+      <xsd:attribute name="duration"         type="xsd:string"  use="required"/>
+      <xsd:attribute name="size"             type="xsd:decimal" use="required"/>
+      <xsd:attribute name="bit_rate"         type="xsd:string"  use="required"/>
+    </xsd:complexType>
+
+    <xsd:complexType name="tag">
+      <xsd:attribute name="key"   type="xsd:string" use="required"/>
+      <xsd:attribute name="value" type="xsd:string" use="required"/>
+    </xsd:complexType>
+</xsd:schema>
diff --git a/ffprobe.c b/ffprobe.c
index 5318855..7b76c23 100644
--- a/ffprobe.c
+++ b/ffprobe.c
@@ -704,6 +704,190 @@ static Writer json_writer = {
     .show_tags            = json_show_tags,
 };
 
+/* XML output */
+
+typedef struct {
+    const AVClass *class;
+    int within_tag;
+    int multiple_entries; ///< tells if the given chapter requires multiple entries
+    int indent_level;
+    int fully_qualified;
+} XMLContext;
+
+#undef OFFSET
+#define OFFSET(x) offsetof(XMLContext, x)
+
+static const AVOption xml_options[]= {
+    {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), FF_OPT_TYPE_INT, {.dbl=0},  0, 1 },
+    {"q",               "specify if the output should be fully qualified", OFFSET(fully_qualified), FF_OPT_TYPE_INT, {.dbl=0},  0, 1 },
+    {NULL},
+};
+
+static const char *xml_get_name(void *ctx)
+{
+    return "xml";
+}
+
+static const AVClass xml_class = {
+    "XMLContext",
+    xml_get_name,
+    xml_options
+};
+
+static av_cold int xml_init(WriterContext *wctx, const char *args, void *opaque)
+{
+    XMLContext *xml = wctx->priv;
+    int err;
+
+    xml->class = &xml_class;
+    av_opt_set_defaults(xml);
+
+    if (args &&
+        (err = (av_set_options_string(xml, args, "=", ":"))) < 0) {
+        av_log(wctx, AV_LOG_ERROR, "Error parsing options string: '%s'\n", args);
+        return err;
+    }
+
+    return 0;
+}
+
+static inline void print_xml_escaped_str(const char *s)
+{
+    while (*s) {
+        switch (*s) {
+        case '&' : printf("&");  break;
+        case '<' : printf("<");   break;
+        case '>' : printf(">");   break;
+        case '\"': printf("""); break;
+        case '\'': printf("'"); break;
+        default: printf("%c", *s);
+        }
+        s++;
+    }
+}
+
+static void xml_print_header(WriterContext *wctx)
+{
+    XMLContext *xml = wctx->priv;
+    const char *qual = " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
+        "xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' "
+        "xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'";
+
+    printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
+    printf("<%sffprobe%s>\n",
+           xml->fully_qualified ? "ffprobe:" : "",
+           xml->fully_qualified ? qual : "");
+
+    xml->indent_level++;
+}
+
+static void xml_print_footer(WriterContext *wctx)
+{
+    XMLContext *xml = wctx->priv;
+
+    xml->indent_level--;
+    printf("</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
+}
+
+#define XML_INDENT() { int i; for (i = 0; i < xml->indent_level; i++) printf(INDENT); }
+
+static void xml_print_chapter_header(WriterContext *wctx, const char *chapter)
+{
+    XMLContext *xml = wctx->priv;
+
+    if (wctx->nb_chapter)
+        printf("\n");
+    xml->multiple_entries = !strcmp(chapter, "packets") || !strcmp(chapter, "streams");
+
+    if (xml->multiple_entries) {
+        XML_INDENT(); printf("<%s>\n", chapter);
+        xml->indent_level++;
+    }
+}
+
+static void xml_print_chapter_footer(WriterContext *wctx, const char *chapter)
+{
+    XMLContext *xml = wctx->priv;
+
+    if (xml->multiple_entries) {
+        xml->indent_level--;
+        XML_INDENT(); printf("</%s>\n", chapter);
+    }
+}
+
+static void xml_print_section_header(WriterContext *wctx, const char *section)
+{
+    XMLContext *xml = wctx->priv;
+
+    XML_INDENT(); printf("<%s ", section);
+    xml->within_tag = 1;
+}
+
+static void xml_print_section_footer(WriterContext *wctx, const char *section)
+{
+    XMLContext *xml = wctx->priv;
+
+    if (xml->within_tag)
+        printf("/>\n");
+    else {
+        XML_INDENT(); printf("</%s>\n", section);
+    }
+}
+
+/* static inline void print_xml_escaped_str(const char *s, const char sep) */
+
+static void xml_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+    if (wctx->nb_item)
+        printf(" ");
+    printf("%s=\"", key); print_xml_escaped_str(value); printf("\"");
+}
+
+static void xml_print_int(WriterContext *wctx, const char *key, int value)
+{
+    if (wctx->nb_item)
+        printf(" ");
+    printf("%s=\"%d\"", key, value);
+}
+
+static void xml_show_tags(WriterContext *wctx, AVDictionary *dict)
+{
+    XMLContext *xml = wctx->priv;
+    AVDictionaryEntry *tag = NULL;
+    int is_first = 1;
+
+    xml->indent_level++;
+    while ((tag = av_dict_get(dict, "", tag, AV_DICT_IGNORE_SUFFIX))) {
+        if (is_first) {
+            /* close section tag */
+            printf(">\n");
+            xml->within_tag = 0;
+            is_first = 0;
+        }
+        XML_INDENT();
+        printf("<tag key=\""); print_xml_escaped_str(tag->key);
+        printf("\" value=\""); print_xml_escaped_str(tag->value);
+        printf("\"/>\n");
+    }
+    xml->indent_level--;
+}
+
+static Writer xml_writer = {
+    .name         = "xml",
+    .priv_size    = sizeof(XMLContext),
+
+    .init                 = xml_init,
+    .print_header         = xml_print_header,
+    .print_footer         = xml_print_footer,
+    .print_chapter_header = xml_print_chapter_header,
+    .print_chapter_footer = xml_print_chapter_footer,
+    .print_section_header = xml_print_section_header,
+    .print_section_footer = xml_print_section_footer,
+    .print_integer        = xml_print_int,
+    .print_string         = xml_print_str,
+    .show_tags            = xml_show_tags,
+};
+
 static void writer_register_all(void)
 {
     static int initialized;
@@ -715,6 +899,7 @@ static void writer_register_all(void)
     writer_register(&default_writer);
     writer_register(&compact_writer);
     writer_register(&json_writer);
+    writer_register(&xml_writer);
 }
 
 #define print_fmt(k, f, ...) do {              \
-- 
1.7.4.1



More information about the ffmpeg-devel mailing list