mirror of https://git.ffmpeg.org/ffmpeg.git
ffprobe: add XML writer
This commit is contained in:
parent
f1a4182e8e
commit
20ac5849f8
|
@ -9,6 +9,7 @@ version next:
|
|||
- SMJPEG demuxer
|
||||
- dv: add timecode to metadata
|
||||
- thumbnail video filter
|
||||
- XML output in ffprobe
|
||||
|
||||
|
||||
version 0.9:
|
||||
|
|
2
Makefile
2
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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
245
ffprobe.c
245
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("&"); break;
|
||||
case '<' : size += strlen("<"); break;
|
||||
case '>' : size += strlen(">"); break;
|
||||
case '\"': size += strlen("""); break;
|
||||
case '\'': size += strlen("'"); 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("&"); break;
|
||||
case '<' : COPY_STR("<"); break;
|
||||
case '>' : COPY_STR(">"); break;
|
||||
case '\"': COPY_STR("""); break;
|
||||
case '\'': COPY_STR("'"); 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" },
|
||||
|
|
Loading…
Reference in New Issue