summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefano Sabatini <stefasab@gmail.com>2011-10-09 00:24:31 +0200
committerStefano Sabatini <stefasab@gmail.com>2011-12-27 12:58:24 +0100
commit20ac5849f82282e0566fab6f5a03aedde1b1b7fe (patch)
tree8f35455a0b4e6635db3c6ba3804da5a43a6dce60
parentf1a4182e8ea70d36c82c2a88e197144715dfd23d (diff)
ffprobe: add XML writer
-rw-r--r--Changelog1
-rw-r--r--Makefile2
-rw-r--r--doc/ffprobe.texi33
-rw-r--r--doc/ffprobe.xsd96
-rw-r--r--ffprobe.c245
5 files changed, 375 insertions, 2 deletions
diff --git a/Changelog b/Changelog
index 247c97b209..4a0cc3041c 100644
--- a/Changelog
+++ b/Changelog
@@ -9,6 +9,7 @@ version next:
- SMJPEG demuxer
- dv: add timecode to metadata
- thumbnail video filter
+- XML output in ffprobe
version 0.9:
diff --git a/Makefile b/Makefile
index 5622aa8a2f..52645a366e 100644
--- a/Makefile
+++ b/Makefile
@@ -39,7 +39,7 @@ FFLIBS-$(CONFIG_SWSCALE) += swscale
FFLIBS := avutil
-DATA_FILES := $(wildcard $(SRC_PATH)/presets/*.ffpreset)
+DATA_FILES := $(wildcard $(SRC_PATH)/presets/*.ffpreset) doc/ffprobe.xsd
SKIPHEADERS = cmdutils_common_opts.h
diff --git a/doc/ffprobe.texi b/doc/ffprobe.texi
index 4af13d69e5..736c7f06c0 100644
--- a/doc/ffprobe.texi
+++ b/doc/ffprobe.texi
@@ -218,6 +218,39 @@ Each section is printed using JSON notation.
For more information about JSON, see @url{http://www.json.org/}.
+@section xml
+XML based format.
+
+The XML output is described in the XML schema description file
+@file{ffprobe.xsd} installed in the FFmpeg datadir.
+
+Note that the output issued will be compliant to the
+@file{ffprobe.xsd} schema only when no special global output options
+(@option{unit}, @option{prefix}, @option{byte_binary_prefix},
+@option{sexagesimal} etc.) are specified.
+
+This writer accepts options as a list of @var{key}=@var{value} pairs,
+separated by ":".
+
+The description of the accepted options follows.
+
+@table @option
+
+@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.
+
+@item xsd_compliant, x
+If set to 1 perform more checks for ensuring that the output is XSD
+compliant. Default value is 0.
+This option automatically sets @option{fully_qualified} to 1.
+@end table
+
+For more information about the XML format, see
+@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 0000000000..92bc2b1b55
--- /dev/null
+++ b/doc/ffprobe.xsd
@@ -0,0 +1,96 @@
+<?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:ffprobeType"/>
+
+ <xsd:complexType name="ffprobeType">
+ <xsd:sequence>
+ <xsd:element name="packets" type="ffprobe:packetsType" minOccurs="0" maxOccurs="1" />
+ <xsd:element name="streams" type="ffprobe:streamsType" minOccurs="0" maxOccurs="1" />
+ <xsd:element name="format" type="ffprobe:formatType" minOccurs="0" maxOccurs="1" />
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="packetsType">
+ <xsd:sequence>
+ <xsd:element name="packet" type="ffprobe:packetType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="packetType">
+ <xsd:attribute name="codec_type" type="xsd:string" use="required" />
+ <xsd:attribute name="stream_index" type="xsd:int" use="required" />
+ <xsd:attribute name="pts" type="xsd:long" />
+ <xsd:attribute name="pts_time" type="xsd:float" />
+ <xsd:attribute name="dts" type="xsd:long" />
+ <xsd:attribute name="dts_time" type="xsd:float" />
+ <xsd:attribute name="duration" type="xsd:long" />
+ <xsd:attribute name="duration_time" type="xsd:float" />
+ <xsd:attribute name="size" type="xsd:long" use="required" />
+ <xsd:attribute name="pos" type="xsd:long" />
+ <xsd:attribute name="flags" type="xsd:string" use="required" />
+ </xsd:complexType>
+
+ <xsd:complexType name="streamsType">
+ <xsd:sequence>
+ <xsd:element name="stream" type="ffprobe:streamType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="streamType">
+ <xsd:attribute name="index" type="xsd:int" use="required"/>
+ <xsd:attribute name="codec_name" type="xsd:string" />
+ <xsd:attribute name="codec_long_name" type="xsd:string" />
+ <xsd:attribute name="codec_type" type="xsd:string" />
+ <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"/>
+
+ <!-- video attributes -->
+ <xsd:attribute name="width" type="xsd:int"/>
+ <xsd:attribute name="height" type="xsd:int"/>
+ <xsd:attribute name="has_b_frames" type="xsd:int"/>
+ <xsd:attribute name="sample_aspect_ratio" type="xsd:string"/>
+ <xsd:attribute name="display_aspect_ratio" type="xsd:string"/>
+ <xsd:attribute name="pix_fmt" type="xsd:string"/>
+ <xsd:attribute name="level" type="xsd:int"/>
+ <xsd:attribute name="timecode" type="xsd:string"/>
+
+ <!-- audio attributes -->
+ <xsd:attribute name="sample_fmt" type="xsd:string"/>
+ <xsd:attribute name="sample_rate" type="xsd:int"/>
+ <xsd:attribute name="channels" type="xsd:int"/>
+ <xsd:attribute name="bits_per_sample" type="xsd:int"/>
+
+ <xsd:attribute name="id" type="xsd:string"/>
+ <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:float"/>
+ <xsd:attribute name="duration" type="xsd:float"/>
+ <xsd:attribute name="nb_frames" type="xsd:int"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="formatType">
+ <xsd:sequence>
+ <xsd:element name="tag" type="ffprobe:tagType" minOccurs="0" maxOccurs="unbounded"/>
+ </xsd:sequence>
+
+ <xsd:attribute name="filename" type="xsd:string" use="required"/>
+ <xsd:attribute name="nb_streams" type="xsd:int" 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:float"/>
+ <xsd:attribute name="duration" type="xsd:float"/>
+ <xsd:attribute name="size" type="xsd:long"/>
+ <xsd:attribute name="bit_rate" type="xsd:long"/>
+ </xsd:complexType>
+
+ <xsd:complexType name="tagType">
+ <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 e377c3c767..bf9b580f18 100644
--- a/ffprobe.c
+++ b/ffprobe.c
@@ -867,6 +867,248 @@ static const 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;
+ int xsd_strict;
+ char *buf;
+ size_t buf_size;
+} 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), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 },
+ {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 },
+ {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_INT, {.dbl=0}, 0, 1 },
+ {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_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;
+ }
+
+ if (xml->xsd_strict) {
+ xml->fully_qualified = 1;
+#define CHECK_COMPLIANCE(opt, opt_name) \
+ if (opt) { \
+ av_log(wctx, AV_LOG_ERROR, \
+ "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
+ "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
+ }
+ CHECK_COMPLIANCE(show_private_data, "private");
+ CHECK_COMPLIANCE(show_value_unit, "unit");
+ CHECK_COMPLIANCE(use_value_prefix, "prefix");
+ }
+
+ xml->buf_size = ESCAPE_INIT_BUF_SIZE;
+ if (!(xml->buf = av_malloc(xml->buf_size)))
+ return AVERROR(ENOMEM);
+ return 0;
+}
+
+static av_cold void xml_uninit(WriterContext *wctx)
+{
+ XMLContext *xml = wctx->priv;
+ av_freep(&xml->buf);
+}
+
+static const char *xml_escape_str(char **dst, size_t *dst_size, const char *src,
+ void *log_ctx)
+{
+ const char *p;
+ char *q;
+ size_t size = 1;
+
+ /* precompute size */
+ for (p = src; *p; p++, size++) {
+ ESCAPE_CHECK_SIZE(src, size, SIZE_MAX-10);
+ switch (*p) {
+ case '&' : size += strlen("&amp;"); break;
+ case '<' : size += strlen("&lt;"); break;
+ case '>' : size += strlen("&gt;"); break;
+ case '\"': size += strlen("&quot;"); break;
+ case '\'': size += strlen("&apos;"); break;
+ default: size++;
+ }
+ }
+ ESCAPE_REALLOC_BUF(dst_size, dst, src, size);
+
+#define COPY_STR(str) { \
+ const char *s = str; \
+ while (*s) \
+ *q++ = *s++; \
+ }
+
+ p = src;
+ q = *dst;
+ while (*p) {
+ switch (*p) {
+ case '&' : COPY_STR("&amp;"); break;
+ case '<' : COPY_STR("&lt;"); break;
+ case '>' : COPY_STR("&gt;"); break;
+ case '\"': COPY_STR("&quot;"); break;
+ case '\'': COPY_STR("&apos;"); break;
+ default: *q++ = *p;
+ }
+ p++;
+ }
+ *q = 0;
+
+ return *dst;
+}
+
+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 void xml_print_str(WriterContext *wctx, const char *key, const char *value)
+{
+ XMLContext *xml = wctx->priv;
+
+ if (wctx->nb_item)
+ printf(" ");
+ printf("%s=\"%s\"", key, xml_escape_str(&xml->buf, &xml->buf_size, value, wctx));
+}
+
+static void xml_print_int(WriterContext *wctx, const char *key, long long int value)
+{
+ if (wctx->nb_item)
+ printf(" ");
+ printf("%s=\"%lld\"", 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=\"%s\"",
+ xml_escape_str(&xml->buf, &xml->buf_size, tag->key, wctx));
+ printf(" value=\"%s\"/>\n",
+ xml_escape_str(&xml->buf, &xml->buf_size, tag->value, wctx));
+ }
+ xml->indent_level--;
+}
+
+static Writer xml_writer = {
+ .name = "xml",
+ .priv_size = sizeof(XMLContext),
+ .init = xml_init,
+ .uninit = xml_uninit,
+ .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;
@@ -879,6 +1121,7 @@ static void writer_register_all(void)
writer_register(&compact_writer);
writer_register(&csv_writer);
writer_register(&json_writer);
+ writer_register(&xml_writer);
}
#define print_fmt(k, f, ...) do { \
@@ -1236,7 +1479,7 @@ static const OptionDef options[] = {
{ "pretty", 0, {(void*)&opt_pretty},
"prettify the format of displayed values, make it more human readable" },
{ "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format},
- "set the output printing format (available formats are: default, compact, csv, json)", "format" },
+ "set the output printing format (available formats are: default, compact, csv, json, xml)", "format" },
{ "show_format", OPT_BOOL, {(void*)&do_show_format} , "show format/container info" },
{ "show_packets", OPT_BOOL, {(void*)&do_show_packets}, "show packets info" },
{ "show_streams", OPT_BOOL, {(void*)&do_show_streams}, "show streams info" },