[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