... | ... |
@@ -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("&"); break; |
|
956 |
+ case '<' : size += strlen("<"); break; |
|
957 |
+ case '>' : size += strlen(">"); break; |
|
958 |
+ case '\"': size += strlen("""); break; |
|
959 |
+ case '\'': size += strlen("'"); 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("&"); break; |
|
976 |
+ case '<' : COPY_STR("<"); break; |
|
977 |
+ case '>' : COPY_STR(">"); break; |
|
978 |
+ case '\"': COPY_STR("""); break; |
|
979 |
+ case '\'': COPY_STR("'"); 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" }, |