Details | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
6148 | serge | 1 | /* |
2 | * Copyright (c) 2007-2010 Stefano Sabatini |
||
3 | * |
||
4 | * This file is part of FFmpeg. |
||
5 | * |
||
6 | * FFmpeg is free software; you can redistribute it and/or |
||
7 | * modify it under the terms of the GNU Lesser General Public |
||
8 | * License as published by the Free Software Foundation; either |
||
9 | * version 2.1 of the License, or (at your option) any later version. |
||
10 | * |
||
11 | * FFmpeg is distributed in the hope that it will be useful, |
||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
||
14 | * Lesser General Public License for more details. |
||
15 | * |
||
16 | * You should have received a copy of the GNU Lesser General Public |
||
17 | * License along with FFmpeg; if not, write to the Free Software |
||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
||
19 | */ |
||
20 | |||
21 | /** |
||
22 | * @file |
||
23 | * simple media prober based on the FFmpeg libraries |
||
24 | */ |
||
25 | |||
26 | #include "config.h" |
||
27 | #include "version.h" |
||
28 | |||
29 | #include |
||
30 | |||
31 | #include "libavformat/avformat.h" |
||
32 | #include "libavcodec/avcodec.h" |
||
33 | #include "libavutil/avassert.h" |
||
34 | #include "libavutil/avstring.h" |
||
35 | #include "libavutil/bprint.h" |
||
36 | #include "libavutil/opt.h" |
||
37 | #include "libavutil/pixdesc.h" |
||
38 | #include "libavutil/dict.h" |
||
39 | #include "libavutil/libm.h" |
||
40 | #include "libavutil/parseutils.h" |
||
41 | #include "libavutil/timecode.h" |
||
42 | #include "libavutil/timestamp.h" |
||
43 | #include "libavdevice/avdevice.h" |
||
44 | #include "libswscale/swscale.h" |
||
45 | #include "libswresample/swresample.h" |
||
46 | #include "libpostproc/postprocess.h" |
||
47 | #include "cmdutils.h" |
||
48 | |||
49 | const char program_name[] = "ffprobe"; |
||
50 | const int program_birth_year = 2007; |
||
51 | |||
52 | static int do_bitexact = 0; |
||
53 | static int do_count_frames = 0; |
||
54 | static int do_count_packets = 0; |
||
55 | static int do_read_frames = 0; |
||
56 | static int do_read_packets = 0; |
||
57 | static int do_show_chapters = 0; |
||
58 | static int do_show_error = 0; |
||
59 | static int do_show_format = 0; |
||
60 | static int do_show_frames = 0; |
||
61 | static int do_show_packets = 0; |
||
62 | static int do_show_programs = 0; |
||
63 | static int do_show_streams = 0; |
||
64 | static int do_show_stream_disposition = 0; |
||
65 | static int do_show_data = 0; |
||
66 | static int do_show_program_version = 0; |
||
67 | static int do_show_library_versions = 0; |
||
68 | |||
69 | static int show_value_unit = 0; |
||
70 | static int use_value_prefix = 0; |
||
71 | static int use_byte_value_binary_prefix = 0; |
||
72 | static int use_value_sexagesimal_format = 0; |
||
73 | static int show_private_data = 1; |
||
74 | |||
75 | static char *print_format; |
||
76 | static char *stream_specifier; |
||
77 | |||
78 | typedef struct { |
||
79 | int id; ///< identifier |
||
80 | int64_t start, end; ///< start, end in second/AV_TIME_BASE units |
||
81 | int has_start, has_end; |
||
82 | int start_is_offset, end_is_offset; |
||
83 | int duration_frames; |
||
84 | } ReadInterval; |
||
85 | |||
86 | static ReadInterval *read_intervals; |
||
87 | static int read_intervals_nb = 0; |
||
88 | |||
89 | /* section structure definition */ |
||
90 | |||
91 | #define SECTION_MAX_NB_CHILDREN 10 |
||
92 | |||
93 | struct section { |
||
94 | int id; ///< unique id identifying a section |
||
95 | const char *name; |
||
96 | |||
97 | #define SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level |
||
98 | #define SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type |
||
99 | #define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. |
||
100 | /// For these sections the element_name field is mandatory. |
||
101 | int flags; |
||
102 | int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 |
||
103 | const char *element_name; ///< name of the contained element, if provided |
||
104 | const char *unique_name; ///< unique section name, in case the name is ambiguous |
||
105 | AVDictionary *entries_to_show; |
||
106 | int show_all_entries; |
||
107 | }; |
||
108 | |||
109 | typedef enum { |
||
110 | SECTION_ID_NONE = -1, |
||
111 | SECTION_ID_CHAPTER, |
||
112 | SECTION_ID_CHAPTER_TAGS, |
||
113 | SECTION_ID_CHAPTERS, |
||
114 | SECTION_ID_ERROR, |
||
115 | SECTION_ID_FORMAT, |
||
116 | SECTION_ID_FORMAT_TAGS, |
||
117 | SECTION_ID_FRAME, |
||
118 | SECTION_ID_FRAMES, |
||
119 | SECTION_ID_FRAME_TAGS, |
||
120 | SECTION_ID_LIBRARY_VERSION, |
||
121 | SECTION_ID_LIBRARY_VERSIONS, |
||
122 | SECTION_ID_PACKET, |
||
123 | SECTION_ID_PACKETS, |
||
124 | SECTION_ID_PACKETS_AND_FRAMES, |
||
125 | SECTION_ID_PROGRAM_STREAM_DISPOSITION, |
||
126 | SECTION_ID_PROGRAM_STREAM_TAGS, |
||
127 | SECTION_ID_PROGRAM, |
||
128 | SECTION_ID_PROGRAM_STREAMS, |
||
129 | SECTION_ID_PROGRAM_STREAM, |
||
130 | SECTION_ID_PROGRAM_TAGS, |
||
131 | SECTION_ID_PROGRAM_VERSION, |
||
132 | SECTION_ID_PROGRAMS, |
||
133 | SECTION_ID_ROOT, |
||
134 | SECTION_ID_STREAM, |
||
135 | SECTION_ID_STREAM_DISPOSITION, |
||
136 | SECTION_ID_STREAMS, |
||
137 | SECTION_ID_STREAM_TAGS, |
||
138 | } SectionID; |
||
139 | |||
140 | static struct section sections[] = { |
||
141 | [SECTION_ID_CHAPTERS] = { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } }, |
||
142 | [SECTION_ID_CHAPTER] = { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } }, |
||
143 | [SECTION_ID_CHAPTER_TAGS] = { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" }, |
||
144 | [SECTION_ID_ERROR] = { SECTION_ID_ERROR, "error", 0, { -1 } }, |
||
145 | [SECTION_ID_FORMAT] = { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } }, |
||
146 | [SECTION_ID_FORMAT_TAGS] = { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" }, |
||
147 | [SECTION_ID_FRAMES] = { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, -1 } }, |
||
148 | [SECTION_ID_FRAME] = { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, -1 } }, |
||
149 | [SECTION_ID_FRAME_TAGS] = { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" }, |
||
150 | [SECTION_ID_LIBRARY_VERSIONS] = { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } }, |
||
151 | [SECTION_ID_LIBRARY_VERSION] = { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } }, |
||
152 | [SECTION_ID_PACKETS] = { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, |
||
153 | [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} }, |
||
154 | [SECTION_ID_PACKET] = { SECTION_ID_PACKET, "packet", 0, { -1 } }, |
||
155 | [SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" }, |
||
156 | [SECTION_ID_PROGRAM_STREAM_TAGS] = { SECTION_ID_PROGRAM_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_stream_tags" }, |
||
157 | [SECTION_ID_PROGRAM] = { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } }, |
||
158 | [SECTION_ID_PROGRAM_STREAMS] = { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" }, |
||
159 | [SECTION_ID_PROGRAM_STREAM] = { SECTION_ID_PROGRAM_STREAM, "stream", 0, { SECTION_ID_PROGRAM_STREAM_DISPOSITION, SECTION_ID_PROGRAM_STREAM_TAGS, -1 }, .unique_name = "program_stream" }, |
||
160 | [SECTION_ID_PROGRAM_TAGS] = { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" }, |
||
161 | [SECTION_ID_PROGRAM_VERSION] = { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } }, |
||
162 | [SECTION_ID_PROGRAMS] = { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } }, |
||
163 | [SECTION_ID_ROOT] = { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER, |
||
164 | { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS, |
||
165 | SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, -1} }, |
||
166 | [SECTION_ID_STREAMS] = { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } }, |
||
167 | [SECTION_ID_STREAM] = { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, -1 } }, |
||
168 | [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" }, |
||
169 | [SECTION_ID_STREAM_TAGS] = { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" }, |
||
170 | }; |
||
171 | |||
172 | static const OptionDef *options; |
||
173 | |||
174 | /* FFprobe context */ |
||
175 | static const char *input_filename; |
||
176 | static AVInputFormat *iformat = NULL; |
||
177 | |||
178 | static const char *const binary_unit_prefixes [] = { "", "Ki", "Mi", "Gi", "Ti", "Pi" }; |
||
179 | static const char *const decimal_unit_prefixes[] = { "", "K" , "M" , "G" , "T" , "P" }; |
||
180 | |||
181 | static const char unit_second_str[] = "s" ; |
||
182 | static const char unit_hertz_str[] = "Hz" ; |
||
183 | static const char unit_byte_str[] = "byte" ; |
||
184 | static const char unit_bit_per_second_str[] = "bit/s"; |
||
185 | |||
186 | static uint64_t *nb_streams_packets; |
||
187 | static uint64_t *nb_streams_frames; |
||
188 | static int *selected_streams; |
||
189 | |||
190 | static void ffprobe_cleanup(int ret) |
||
191 | { |
||
192 | int i; |
||
193 | for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) |
||
194 | av_dict_free(&(sections[i].entries_to_show)); |
||
195 | } |
||
196 | |||
197 | struct unit_value { |
||
198 | union { double d; long long int i; } val; |
||
199 | const char *unit; |
||
200 | }; |
||
201 | |||
202 | static char *value_string(char *buf, int buf_size, struct unit_value uv) |
||
203 | { |
||
204 | double vald; |
||
205 | long long int vali; |
||
206 | int show_float = 0; |
||
207 | |||
208 | if (uv.unit == unit_second_str) { |
||
209 | vald = uv.val.d; |
||
210 | show_float = 1; |
||
211 | } else { |
||
212 | vald = vali = uv.val.i; |
||
213 | } |
||
214 | |||
215 | if (uv.unit == unit_second_str && use_value_sexagesimal_format) { |
||
216 | double secs; |
||
217 | int hours, mins; |
||
218 | secs = vald; |
||
219 | mins = (int)secs / 60; |
||
220 | secs = secs - mins * 60; |
||
221 | hours = mins / 60; |
||
222 | mins %= 60; |
||
223 | snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); |
||
224 | } else { |
||
225 | const char *prefix_string = ""; |
||
226 | |||
227 | if (use_value_prefix && vald > 1) { |
||
228 | long long int index; |
||
229 | |||
230 | if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) { |
||
231 | index = (long long int) (log2(vald)) / 10; |
||
232 | index = av_clip(index, 0, FF_ARRAY_ELEMS(binary_unit_prefixes) - 1); |
||
233 | vald /= exp2(index * 10); |
||
234 | prefix_string = binary_unit_prefixes[index]; |
||
235 | } else { |
||
236 | index = (long long int) (log10(vald)) / 3; |
||
237 | index = av_clip(index, 0, FF_ARRAY_ELEMS(decimal_unit_prefixes) - 1); |
||
238 | vald /= pow(10, index * 3); |
||
239 | prefix_string = decimal_unit_prefixes[index]; |
||
240 | } |
||
241 | } |
||
242 | |||
243 | if (show_float || (use_value_prefix && vald != (long long int)vald)) |
||
244 | snprintf(buf, buf_size, "%f", vald); |
||
245 | else |
||
246 | snprintf(buf, buf_size, "%lld", vali); |
||
247 | av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "", |
||
248 | prefix_string, show_value_unit ? uv.unit : ""); |
||
249 | } |
||
250 | |||
251 | return buf; |
||
252 | } |
||
253 | |||
254 | /* WRITERS API */ |
||
255 | |||
256 | typedef struct WriterContext WriterContext; |
||
257 | |||
258 | #define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1 |
||
259 | #define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2 |
||
260 | |||
261 | typedef struct Writer { |
||
262 | const AVClass *priv_class; ///< private class of the writer, if any |
||
263 | int priv_size; ///< private size for the writer context |
||
264 | const char *name; |
||
265 | |||
266 | int (*init) (WriterContext *wctx); |
||
267 | void (*uninit)(WriterContext *wctx); |
||
268 | |||
269 | void (*print_section_header)(WriterContext *wctx); |
||
270 | void (*print_section_footer)(WriterContext *wctx); |
||
271 | void (*print_integer) (WriterContext *wctx, const char *, long long int); |
||
272 | void (*print_rational) (WriterContext *wctx, AVRational *q, char *sep); |
||
273 | void (*print_string) (WriterContext *wctx, const char *, const char *); |
||
274 | int flags; ///< a combination or WRITER_FLAG_* |
||
275 | } Writer; |
||
276 | |||
277 | #define SECTION_MAX_NB_LEVELS 10 |
||
278 | |||
279 | struct WriterContext { |
||
280 | const AVClass *class; ///< class of the writer |
||
281 | const Writer *writer; ///< the Writer of which this is an instance |
||
282 | char *name; ///< name of this writer instance |
||
283 | void *priv; ///< private data for use by the filter |
||
284 | |||
285 | const struct section *sections; ///< array containing all sections |
||
286 | int nb_sections; ///< number of sections |
||
287 | |||
288 | int level; ///< current level, starting from 0 |
||
289 | |||
290 | /** number of the item printed in the given section, starting from 0 */ |
||
291 | unsigned int nb_item[SECTION_MAX_NB_LEVELS]; |
||
292 | |||
293 | /** section per each level */ |
||
294 | const struct section *section[SECTION_MAX_NB_LEVELS]; |
||
295 | AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, |
||
296 | /// used by various writers |
||
297 | |||
298 | unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section |
||
299 | unsigned int nb_section_frame; ///< number of the frame section in case we are in "packets_and_frames" section |
||
300 | unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames |
||
301 | }; |
||
302 | |||
303 | static const char *writer_get_name(void *p) |
||
304 | { |
||
305 | WriterContext *wctx = p; |
||
306 | return wctx->writer->name; |
||
307 | } |
||
308 | |||
309 | static const AVClass writer_class = { |
||
310 | "Writer", |
||
311 | writer_get_name, |
||
312 | NULL, |
||
313 | LIBAVUTIL_VERSION_INT, |
||
314 | }; |
||
315 | |||
316 | static void writer_close(WriterContext **wctx) |
||
317 | { |
||
318 | int i; |
||
319 | |||
320 | if (!*wctx) |
||
321 | return; |
||
322 | |||
323 | if ((*wctx)->writer->uninit) |
||
324 | (*wctx)->writer->uninit(*wctx); |
||
325 | for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) |
||
326 | av_bprint_finalize(&(*wctx)->section_pbuf[i], NULL); |
||
327 | if ((*wctx)->writer->priv_class) |
||
328 | av_opt_free((*wctx)->priv); |
||
329 | av_freep(&((*wctx)->priv)); |
||
330 | av_freep(wctx); |
||
331 | } |
||
332 | |||
333 | static int writer_open(WriterContext **wctx, const Writer *writer, const char *args, |
||
334 | const struct section *sections, int nb_sections) |
||
335 | { |
||
336 | int i, ret = 0; |
||
337 | |||
338 | if (!(*wctx = av_mallocz(sizeof(WriterContext)))) { |
||
339 | ret = AVERROR(ENOMEM); |
||
340 | goto fail; |
||
341 | } |
||
342 | |||
343 | if (!((*wctx)->priv = av_mallocz(writer->priv_size))) { |
||
344 | ret = AVERROR(ENOMEM); |
||
345 | goto fail; |
||
346 | } |
||
347 | |||
348 | (*wctx)->class = &writer_class; |
||
349 | (*wctx)->writer = writer; |
||
350 | (*wctx)->level = -1; |
||
351 | (*wctx)->sections = sections; |
||
352 | (*wctx)->nb_sections = nb_sections; |
||
353 | |||
354 | if (writer->priv_class) { |
||
355 | void *priv_ctx = (*wctx)->priv; |
||
356 | *((const AVClass **)priv_ctx) = writer->priv_class; |
||
357 | av_opt_set_defaults(priv_ctx); |
||
358 | |||
359 | if (args && |
||
360 | (ret = av_set_options_string(priv_ctx, args, "=", ":")) < 0) |
||
361 | goto fail; |
||
362 | } |
||
363 | |||
364 | for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) |
||
365 | av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED); |
||
366 | |||
367 | if ((*wctx)->writer->init) |
||
368 | ret = (*wctx)->writer->init(*wctx); |
||
369 | if (ret < 0) |
||
370 | goto fail; |
||
371 | |||
372 | return 0; |
||
373 | |||
374 | fail: |
||
375 | writer_close(wctx); |
||
376 | return ret; |
||
377 | } |
||
378 | |||
379 | static inline void writer_print_section_header(WriterContext *wctx, |
||
380 | int section_id) |
||
381 | { |
||
382 | int parent_section_id; |
||
383 | wctx->level++; |
||
384 | av_assert0(wctx->level < SECTION_MAX_NB_LEVELS); |
||
385 | parent_section_id = wctx->level ? |
||
386 | (wctx->section[wctx->level-1])->id : SECTION_ID_NONE; |
||
387 | |||
388 | wctx->nb_item[wctx->level] = 0; |
||
389 | wctx->section[wctx->level] = &wctx->sections[section_id]; |
||
390 | |||
391 | if (section_id == SECTION_ID_PACKETS_AND_FRAMES) { |
||
392 | wctx->nb_section_packet = wctx->nb_section_frame = |
||
393 | wctx->nb_section_packet_frame = 0; |
||
394 | } else if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) { |
||
395 | wctx->nb_section_packet_frame = section_id == SECTION_ID_PACKET ? |
||
396 | wctx->nb_section_packet : wctx->nb_section_frame; |
||
397 | } |
||
398 | |||
399 | if (wctx->writer->print_section_header) |
||
400 | wctx->writer->print_section_header(wctx); |
||
401 | } |
||
402 | |||
403 | static inline void writer_print_section_footer(WriterContext *wctx) |
||
404 | { |
||
405 | int section_id = wctx->section[wctx->level]->id; |
||
406 | int parent_section_id = wctx->level ? |
||
407 | wctx->section[wctx->level-1]->id : SECTION_ID_NONE; |
||
408 | |||
409 | if (parent_section_id != SECTION_ID_NONE) |
||
410 | wctx->nb_item[wctx->level-1]++; |
||
411 | if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) { |
||
412 | if (section_id == SECTION_ID_PACKET) wctx->nb_section_packet++; |
||
413 | else wctx->nb_section_frame++; |
||
414 | } |
||
415 | if (wctx->writer->print_section_footer) |
||
416 | wctx->writer->print_section_footer(wctx); |
||
417 | wctx->level--; |
||
418 | } |
||
419 | |||
420 | static inline void writer_print_integer(WriterContext *wctx, |
||
421 | const char *key, long long int val) |
||
422 | { |
||
423 | const struct section *section = wctx->section[wctx->level]; |
||
424 | |||
425 | if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { |
||
426 | wctx->writer->print_integer(wctx, key, val); |
||
427 | wctx->nb_item[wctx->level]++; |
||
428 | } |
||
429 | } |
||
430 | |||
431 | static inline void writer_print_string(WriterContext *wctx, |
||
432 | const char *key, const char *val, int opt) |
||
433 | { |
||
434 | const struct section *section = wctx->section[wctx->level]; |
||
435 | |||
436 | if (opt && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS)) |
||
437 | return; |
||
438 | |||
439 | if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { |
||
440 | wctx->writer->print_string(wctx, key, val); |
||
441 | wctx->nb_item[wctx->level]++; |
||
442 | } |
||
443 | } |
||
444 | |||
445 | static inline void writer_print_rational(WriterContext *wctx, |
||
446 | const char *key, AVRational q, char sep) |
||
447 | { |
||
448 | AVBPrint buf; |
||
449 | av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); |
||
450 | av_bprintf(&buf, "%d%c%d", q.num, sep, q.den); |
||
451 | writer_print_string(wctx, key, buf.str, 0); |
||
452 | } |
||
453 | |||
454 | static void writer_print_time(WriterContext *wctx, const char *key, |
||
455 | int64_t ts, const AVRational *time_base, int is_duration) |
||
456 | { |
||
457 | char buf[128]; |
||
458 | |||
459 | if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { |
||
460 | writer_print_string(wctx, key, "N/A", 1); |
||
461 | } else { |
||
462 | double d = ts * av_q2d(*time_base); |
||
463 | struct unit_value uv; |
||
464 | uv.val.d = d; |
||
465 | uv.unit = unit_second_str; |
||
466 | value_string(buf, sizeof(buf), uv); |
||
467 | writer_print_string(wctx, key, buf, 0); |
||
468 | } |
||
469 | } |
||
470 | |||
471 | static void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration) |
||
472 | { |
||
473 | if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { |
||
474 | writer_print_string(wctx, key, "N/A", 1); |
||
475 | } else { |
||
476 | writer_print_integer(wctx, key, ts); |
||
477 | } |
||
478 | } |
||
479 | |||
480 | static void writer_print_data(WriterContext *wctx, const char *name, |
||
481 | uint8_t *data, int size) |
||
482 | { |
||
483 | AVBPrint bp; |
||
484 | int offset = 0, l, i; |
||
485 | |||
486 | av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); |
||
487 | av_bprintf(&bp, "\n"); |
||
488 | while (size) { |
||
489 | av_bprintf(&bp, "%08x: ", offset); |
||
490 | l = FFMIN(size, 16); |
||
491 | for (i = 0; i < l; i++) { |
||
492 | av_bprintf(&bp, "%02x", data[i]); |
||
493 | if (i & 1) |
||
494 | av_bprintf(&bp, " "); |
||
495 | } |
||
496 | av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2); |
||
497 | for (i = 0; i < l; i++) |
||
498 | av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1); |
||
499 | av_bprintf(&bp, "\n"); |
||
500 | offset += l; |
||
501 | data += l; |
||
502 | size -= l; |
||
503 | } |
||
504 | writer_print_string(wctx, name, bp.str, 0); |
||
505 | av_bprint_finalize(&bp, NULL); |
||
506 | } |
||
507 | |||
508 | #define MAX_REGISTERED_WRITERS_NB 64 |
||
509 | |||
510 | static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1]; |
||
511 | |||
512 | static int writer_register(const Writer *writer) |
||
513 | { |
||
514 | static int next_registered_writer_idx = 0; |
||
515 | |||
516 | if (next_registered_writer_idx == MAX_REGISTERED_WRITERS_NB) |
||
517 | return AVERROR(ENOMEM); |
||
518 | |||
519 | registered_writers[next_registered_writer_idx++] = writer; |
||
520 | return 0; |
||
521 | } |
||
522 | |||
523 | static const Writer *writer_get_by_name(const char *name) |
||
524 | { |
||
525 | int i; |
||
526 | |||
527 | for (i = 0; registered_writers[i]; i++) |
||
528 | if (!strcmp(registered_writers[i]->name, name)) |
||
529 | return registered_writers[i]; |
||
530 | |||
531 | return NULL; |
||
532 | } |
||
533 | |||
534 | |||
535 | /* WRITERS */ |
||
536 | |||
537 | #define DEFINE_WRITER_CLASS(name) \ |
||
538 | static const char *name##_get_name(void *ctx) \ |
||
539 | { \ |
||
540 | return #name ; \ |
||
541 | } \ |
||
542 | static const AVClass name##_class = { \ |
||
543 | #name, \ |
||
544 | name##_get_name, \ |
||
545 | name##_options \ |
||
546 | } |
||
547 | |||
548 | /* Default output */ |
||
549 | |||
550 | typedef struct DefaultContext { |
||
551 | const AVClass *class; |
||
552 | int nokey; |
||
553 | int noprint_wrappers; |
||
554 | int nested_section[SECTION_MAX_NB_LEVELS]; |
||
555 | } DefaultContext; |
||
556 | |||
557 | #define OFFSET(x) offsetof(DefaultContext, x) |
||
558 | |||
559 | static const AVOption default_options[] = { |
||
560 | { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
561 | { "nw", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
562 | { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
563 | { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
564 | {NULL}, |
||
565 | }; |
||
566 | |||
567 | DEFINE_WRITER_CLASS(default); |
||
568 | |||
569 | /* lame uppercasing routine, assumes the string is lower case ASCII */ |
||
570 | static inline char *upcase_string(char *dst, size_t dst_size, const char *src) |
||
571 | { |
||
572 | int i; |
||
573 | for (i = 0; src[i] && i < dst_size-1; i++) |
||
574 | dst[i] = av_toupper(src[i]); |
||
575 | dst[i] = 0; |
||
576 | return dst; |
||
577 | } |
||
578 | |||
579 | static void default_print_section_header(WriterContext *wctx) |
||
580 | { |
||
581 | DefaultContext *def = wctx->priv; |
||
582 | char buf[32]; |
||
583 | const struct section *section = wctx->section[wctx->level]; |
||
584 | const struct section *parent_section = wctx->level ? |
||
585 | wctx->section[wctx->level-1] : NULL; |
||
586 | |||
587 | av_bprint_clear(&wctx->section_pbuf[wctx->level]); |
||
588 | if (parent_section && |
||
589 | !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) { |
||
590 | def->nested_section[wctx->level] = 1; |
||
591 | av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", |
||
592 | wctx->section_pbuf[wctx->level-1].str, |
||
593 | upcase_string(buf, sizeof(buf), |
||
594 | av_x_if_null(section->element_name, section->name))); |
||
595 | } |
||
596 | |||
597 | if (def->noprint_wrappers || def->nested_section[wctx->level]) |
||
598 | return; |
||
599 | |||
600 | if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) |
||
601 | printf("[%s]\n", upcase_string(buf, sizeof(buf), section->name)); |
||
602 | } |
||
603 | |||
604 | static void default_print_section_footer(WriterContext *wctx) |
||
605 | { |
||
606 | DefaultContext *def = wctx->priv; |
||
607 | const struct section *section = wctx->section[wctx->level]; |
||
608 | char buf[32]; |
||
609 | |||
610 | if (def->noprint_wrappers || def->nested_section[wctx->level]) |
||
611 | return; |
||
612 | |||
613 | if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) |
||
614 | printf("[/%s]\n", upcase_string(buf, sizeof(buf), section->name)); |
||
615 | } |
||
616 | |||
617 | static void default_print_str(WriterContext *wctx, const char *key, const char *value) |
||
618 | { |
||
619 | DefaultContext *def = wctx->priv; |
||
620 | |||
621 | if (!def->nokey) |
||
622 | printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); |
||
623 | printf("%s\n", value); |
||
624 | } |
||
625 | |||
626 | static void default_print_int(WriterContext *wctx, const char *key, long long int value) |
||
627 | { |
||
628 | DefaultContext *def = wctx->priv; |
||
629 | |||
630 | if (!def->nokey) |
||
631 | printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); |
||
632 | printf("%lld\n", value); |
||
633 | } |
||
634 | |||
635 | static const Writer default_writer = { |
||
636 | .name = "default", |
||
637 | .priv_size = sizeof(DefaultContext), |
||
638 | .print_section_header = default_print_section_header, |
||
639 | .print_section_footer = default_print_section_footer, |
||
640 | .print_integer = default_print_int, |
||
641 | .print_string = default_print_str, |
||
642 | .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, |
||
643 | .priv_class = &default_class, |
||
644 | }; |
||
645 | |||
646 | /* Compact output */ |
||
647 | |||
648 | /** |
||
649 | * Apply C-language-like string escaping. |
||
650 | */ |
||
651 | static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) |
||
652 | { |
||
653 | const char *p; |
||
654 | |||
655 | for (p = src; *p; p++) { |
||
656 | switch (*p) { |
||
657 | case '\b': av_bprintf(dst, "%s", "\\b"); break; |
||
658 | case '\f': av_bprintf(dst, "%s", "\\f"); break; |
||
659 | case '\n': av_bprintf(dst, "%s", "\\n"); break; |
||
660 | case '\r': av_bprintf(dst, "%s", "\\r"); break; |
||
661 | case '\\': av_bprintf(dst, "%s", "\\\\"); break; |
||
662 | default: |
||
663 | if (*p == sep) |
||
664 | av_bprint_chars(dst, '\\', 1); |
||
665 | av_bprint_chars(dst, *p, 1); |
||
666 | } |
||
667 | } |
||
668 | return dst->str; |
||
669 | } |
||
670 | |||
671 | /** |
||
672 | * Quote fields containing special characters, check RFC4180. |
||
673 | */ |
||
674 | static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) |
||
675 | { |
||
676 | char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; |
||
677 | int needs_quoting = !!src[strcspn(src, meta_chars)]; |
||
678 | |||
679 | if (needs_quoting) |
||
680 | av_bprint_chars(dst, '"', 1); |
||
681 | |||
682 | for (; *src; src++) { |
||
683 | if (*src == '"') |
||
684 | av_bprint_chars(dst, '"', 1); |
||
685 | av_bprint_chars(dst, *src, 1); |
||
686 | } |
||
687 | if (needs_quoting) |
||
688 | av_bprint_chars(dst, '"', 1); |
||
689 | return dst->str; |
||
690 | } |
||
691 | |||
692 | static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) |
||
693 | { |
||
694 | return src; |
||
695 | } |
||
696 | |||
697 | typedef struct CompactContext { |
||
698 | const AVClass *class; |
||
699 | char *item_sep_str; |
||
700 | char item_sep; |
||
701 | int nokey; |
||
702 | int print_section; |
||
703 | char *escape_mode_str; |
||
704 | const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); |
||
705 | int nested_section[SECTION_MAX_NB_LEVELS]; |
||
706 | int has_nested_elems[SECTION_MAX_NB_LEVELS]; |
||
707 | int terminate_line[SECTION_MAX_NB_LEVELS]; |
||
708 | } CompactContext; |
||
709 | |||
710 | #undef OFFSET |
||
711 | #define OFFSET(x) offsetof(CompactContext, x) |
||
712 | |||
713 | static const AVOption compact_options[]= { |
||
714 | {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, |
||
715 | {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, CHAR_MIN, CHAR_MAX }, |
||
716 | {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
717 | {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
718 | {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, |
||
719 | {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, CHAR_MIN, CHAR_MAX }, |
||
720 | {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
721 | {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
722 | {NULL}, |
||
723 | }; |
||
724 | |||
725 | DEFINE_WRITER_CLASS(compact); |
||
726 | |||
727 | static av_cold int compact_init(WriterContext *wctx) |
||
728 | { |
||
729 | CompactContext *compact = wctx->priv; |
||
730 | |||
731 | if (strlen(compact->item_sep_str) != 1) { |
||
732 | av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", |
||
733 | compact->item_sep_str); |
||
734 | return AVERROR(EINVAL); |
||
735 | } |
||
736 | compact->item_sep = compact->item_sep_str[0]; |
||
737 | |||
738 | if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; |
||
739 | else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; |
||
740 | else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; |
||
741 | else { |
||
742 | av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); |
||
743 | return AVERROR(EINVAL); |
||
744 | } |
||
745 | |||
746 | return 0; |
||
747 | } |
||
748 | |||
749 | static void compact_print_section_header(WriterContext *wctx) |
||
750 | { |
||
751 | CompactContext *compact = wctx->priv; |
||
752 | const struct section *section = wctx->section[wctx->level]; |
||
753 | const struct section *parent_section = wctx->level ? |
||
754 | wctx->section[wctx->level-1] : NULL; |
||
755 | compact->terminate_line[wctx->level] = 1; |
||
756 | compact->has_nested_elems[wctx->level] = 0; |
||
757 | |||
758 | av_bprint_clear(&wctx->section_pbuf[wctx->level]); |
||
759 | if (!(section->flags & SECTION_FLAG_IS_ARRAY) && parent_section && |
||
760 | !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) { |
||
761 | compact->nested_section[wctx->level] = 1; |
||
762 | compact->has_nested_elems[wctx->level-1] = 1; |
||
763 | av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", |
||
764 | wctx->section_pbuf[wctx->level-1].str, |
||
765 | (char *)av_x_if_null(section->element_name, section->name)); |
||
766 | wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; |
||
767 | } else { |
||
768 | if (parent_section && compact->has_nested_elems[wctx->level-1] && |
||
769 | (section->flags & SECTION_FLAG_IS_ARRAY)) { |
||
770 | compact->terminate_line[wctx->level-1] = 0; |
||
771 | printf("\n"); |
||
772 | } |
||
773 | if (compact->print_section && |
||
774 | !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) |
||
775 | printf("%s%c", section->name, compact->item_sep); |
||
776 | } |
||
777 | } |
||
778 | |||
779 | static void compact_print_section_footer(WriterContext *wctx) |
||
780 | { |
||
781 | CompactContext *compact = wctx->priv; |
||
782 | |||
783 | if (!compact->nested_section[wctx->level] && |
||
784 | compact->terminate_line[wctx->level] && |
||
785 | !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) |
||
786 | printf("\n"); |
||
787 | } |
||
788 | |||
789 | static void compact_print_str(WriterContext *wctx, const char *key, const char *value) |
||
790 | { |
||
791 | CompactContext *compact = wctx->priv; |
||
792 | AVBPrint buf; |
||
793 | |||
794 | if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); |
||
795 | if (!compact->nokey) |
||
796 | printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); |
||
797 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
798 | printf("%s", compact->escape_str(&buf, value, compact->item_sep, wctx)); |
||
799 | av_bprint_finalize(&buf, NULL); |
||
800 | } |
||
801 | |||
802 | static void compact_print_int(WriterContext *wctx, const char *key, long long int value) |
||
803 | { |
||
804 | CompactContext *compact = wctx->priv; |
||
805 | |||
806 | if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep); |
||
807 | if (!compact->nokey) |
||
808 | printf("%s%s=", wctx->section_pbuf[wctx->level].str, key); |
||
809 | printf("%lld", value); |
||
810 | } |
||
811 | |||
812 | static const Writer compact_writer = { |
||
813 | .name = "compact", |
||
814 | .priv_size = sizeof(CompactContext), |
||
815 | .init = compact_init, |
||
816 | .print_section_header = compact_print_section_header, |
||
817 | .print_section_footer = compact_print_section_footer, |
||
818 | .print_integer = compact_print_int, |
||
819 | .print_string = compact_print_str, |
||
820 | .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, |
||
821 | .priv_class = &compact_class, |
||
822 | }; |
||
823 | |||
824 | /* CSV output */ |
||
825 | |||
826 | #undef OFFSET |
||
827 | #define OFFSET(x) offsetof(CompactContext, x) |
||
828 | |||
829 | static const AVOption csv_options[] = { |
||
830 | {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, CHAR_MIN, CHAR_MAX }, |
||
831 | {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, CHAR_MIN, CHAR_MAX }, |
||
832 | {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
833 | {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
834 | {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX }, |
||
835 | {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX }, |
||
836 | {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
837 | {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
838 | {NULL}, |
||
839 | }; |
||
840 | |||
841 | DEFINE_WRITER_CLASS(csv); |
||
842 | |||
843 | static const Writer csv_writer = { |
||
844 | .name = "csv", |
||
845 | .priv_size = sizeof(CompactContext), |
||
846 | .init = compact_init, |
||
847 | .print_section_header = compact_print_section_header, |
||
848 | .print_section_footer = compact_print_section_footer, |
||
849 | .print_integer = compact_print_int, |
||
850 | .print_string = compact_print_str, |
||
851 | .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS, |
||
852 | .priv_class = &csv_class, |
||
853 | }; |
||
854 | |||
855 | /* Flat output */ |
||
856 | |||
857 | typedef struct FlatContext { |
||
858 | const AVClass *class; |
||
859 | const char *sep_str; |
||
860 | char sep; |
||
861 | int hierarchical; |
||
862 | } FlatContext; |
||
863 | |||
864 | #undef OFFSET |
||
865 | #define OFFSET(x) offsetof(FlatContext, x) |
||
866 | |||
867 | static const AVOption flat_options[]= { |
||
868 | {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, CHAR_MIN, CHAR_MAX }, |
||
869 | {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, CHAR_MIN, CHAR_MAX }, |
||
870 | {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
871 | {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
872 | {NULL}, |
||
873 | }; |
||
874 | |||
875 | DEFINE_WRITER_CLASS(flat); |
||
876 | |||
877 | static av_cold int flat_init(WriterContext *wctx) |
||
878 | { |
||
879 | FlatContext *flat = wctx->priv; |
||
880 | |||
881 | if (strlen(flat->sep_str) != 1) { |
||
882 | av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", |
||
883 | flat->sep_str); |
||
884 | return AVERROR(EINVAL); |
||
885 | } |
||
886 | flat->sep = flat->sep_str[0]; |
||
887 | |||
888 | return 0; |
||
889 | } |
||
890 | |||
891 | static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep) |
||
892 | { |
||
893 | const char *p; |
||
894 | |||
895 | for (p = src; *p; p++) { |
||
896 | if (!((*p >= '0' && *p <= '9') || |
||
897 | (*p >= 'a' && *p <= 'z') || |
||
898 | (*p >= 'A' && *p <= 'Z'))) |
||
899 | av_bprint_chars(dst, '_', 1); |
||
900 | else |
||
901 | av_bprint_chars(dst, *p, 1); |
||
902 | } |
||
903 | return dst->str; |
||
904 | } |
||
905 | |||
906 | static const char *flat_escape_value_str(AVBPrint *dst, const char *src) |
||
907 | { |
||
908 | const char *p; |
||
909 | |||
910 | for (p = src; *p; p++) { |
||
911 | switch (*p) { |
||
912 | case '\n': av_bprintf(dst, "%s", "\\n"); break; |
||
913 | case '\r': av_bprintf(dst, "%s", "\\r"); break; |
||
914 | case '\\': av_bprintf(dst, "%s", "\\\\"); break; |
||
915 | case '"': av_bprintf(dst, "%s", "\\\""); break; |
||
916 | case '`': av_bprintf(dst, "%s", "\\`"); break; |
||
917 | case '$': av_bprintf(dst, "%s", "\\$"); break; |
||
918 | default: av_bprint_chars(dst, *p, 1); break; |
||
919 | } |
||
920 | } |
||
921 | return dst->str; |
||
922 | } |
||
923 | |||
924 | static void flat_print_section_header(WriterContext *wctx) |
||
925 | { |
||
926 | FlatContext *flat = wctx->priv; |
||
927 | AVBPrint *buf = &wctx->section_pbuf[wctx->level]; |
||
928 | const struct section *section = wctx->section[wctx->level]; |
||
929 | const struct section *parent_section = wctx->level ? |
||
930 | wctx->section[wctx->level-1] : NULL; |
||
931 | |||
932 | /* build section header */ |
||
933 | av_bprint_clear(buf); |
||
934 | if (!parent_section) |
||
935 | return; |
||
936 | av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); |
||
937 | |||
938 | if (flat->hierarchical || |
||
939 | !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) { |
||
940 | av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str); |
||
941 | |||
942 | if (parent_section->flags & SECTION_FLAG_IS_ARRAY) { |
||
943 | int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ? |
||
944 | wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1]; |
||
945 | av_bprintf(buf, "%d%s", n, flat->sep_str); |
||
946 | } |
||
947 | } |
||
948 | } |
||
949 | |||
950 | static void flat_print_int(WriterContext *wctx, const char *key, long long int value) |
||
951 | { |
||
952 | printf("%s%s=%lld\n", wctx->section_pbuf[wctx->level].str, key, value); |
||
953 | } |
||
954 | |||
955 | static void flat_print_str(WriterContext *wctx, const char *key, const char *value) |
||
956 | { |
||
957 | FlatContext *flat = wctx->priv; |
||
958 | AVBPrint buf; |
||
959 | |||
960 | printf("%s", wctx->section_pbuf[wctx->level].str); |
||
961 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
962 | printf("%s=", flat_escape_key_str(&buf, key, flat->sep)); |
||
963 | av_bprint_clear(&buf); |
||
964 | printf("\"%s\"\n", flat_escape_value_str(&buf, value)); |
||
965 | av_bprint_finalize(&buf, NULL); |
||
966 | } |
||
967 | |||
968 | static const Writer flat_writer = { |
||
969 | .name = "flat", |
||
970 | .priv_size = sizeof(FlatContext), |
||
971 | .init = flat_init, |
||
972 | .print_section_header = flat_print_section_header, |
||
973 | .print_integer = flat_print_int, |
||
974 | .print_string = flat_print_str, |
||
975 | .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, |
||
976 | .priv_class = &flat_class, |
||
977 | }; |
||
978 | |||
979 | /* INI format output */ |
||
980 | |||
981 | typedef struct { |
||
982 | const AVClass *class; |
||
983 | int hierarchical; |
||
984 | } INIContext; |
||
985 | |||
986 | #undef OFFSET |
||
987 | #define OFFSET(x) offsetof(INIContext, x) |
||
988 | |||
989 | static const AVOption ini_options[] = { |
||
990 | {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
991 | {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 }, |
||
992 | {NULL}, |
||
993 | }; |
||
994 | |||
995 | DEFINE_WRITER_CLASS(ini); |
||
996 | |||
997 | static char *ini_escape_str(AVBPrint *dst, const char *src) |
||
998 | { |
||
999 | int i = 0; |
||
1000 | char c = 0; |
||
1001 | |||
1002 | while (c = src[i++]) { |
||
1003 | switch (c) { |
||
1004 | case '\b': av_bprintf(dst, "%s", "\\b"); break; |
||
1005 | case '\f': av_bprintf(dst, "%s", "\\f"); break; |
||
1006 | case '\n': av_bprintf(dst, "%s", "\\n"); break; |
||
1007 | case '\r': av_bprintf(dst, "%s", "\\r"); break; |
||
1008 | case '\t': av_bprintf(dst, "%s", "\\t"); break; |
||
1009 | case '\\': |
||
1010 | case '#' : |
||
1011 | case '=' : |
||
1012 | case ':' : av_bprint_chars(dst, '\\', 1); |
||
1013 | default: |
||
1014 | if ((unsigned char)c < 32) |
||
1015 | av_bprintf(dst, "\\x00%02x", c & 0xff); |
||
1016 | else |
||
1017 | av_bprint_chars(dst, c, 1); |
||
1018 | break; |
||
1019 | } |
||
1020 | } |
||
1021 | return dst->str; |
||
1022 | } |
||
1023 | |||
1024 | static void ini_print_section_header(WriterContext *wctx) |
||
1025 | { |
||
1026 | INIContext *ini = wctx->priv; |
||
1027 | AVBPrint *buf = &wctx->section_pbuf[wctx->level]; |
||
1028 | const struct section *section = wctx->section[wctx->level]; |
||
1029 | const struct section *parent_section = wctx->level ? |
||
1030 | wctx->section[wctx->level-1] : NULL; |
||
1031 | |||
1032 | av_bprint_clear(buf); |
||
1033 | if (!parent_section) { |
||
1034 | printf("# ffprobe output\n\n"); |
||
1035 | return; |
||
1036 | } |
||
1037 | |||
1038 | if (wctx->nb_item[wctx->level-1]) |
||
1039 | printf("\n"); |
||
1040 | |||
1041 | av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); |
||
1042 | if (ini->hierarchical || |
||
1043 | !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) { |
||
1044 | av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name); |
||
1045 | |||
1046 | if (parent_section->flags & SECTION_FLAG_IS_ARRAY) { |
||
1047 | int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ? |
||
1048 | wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1]; |
||
1049 | av_bprintf(buf, ".%d", n); |
||
1050 | } |
||
1051 | } |
||
1052 | |||
1053 | if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) |
||
1054 | printf("[%s]\n", buf->str); |
||
1055 | } |
||
1056 | |||
1057 | static void ini_print_str(WriterContext *wctx, const char *key, const char *value) |
||
1058 | { |
||
1059 | AVBPrint buf; |
||
1060 | |||
1061 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1062 | printf("%s=", ini_escape_str(&buf, key)); |
||
1063 | av_bprint_clear(&buf); |
||
1064 | printf("%s\n", ini_escape_str(&buf, value)); |
||
1065 | av_bprint_finalize(&buf, NULL); |
||
1066 | } |
||
1067 | |||
1068 | static void ini_print_int(WriterContext *wctx, const char *key, long long int value) |
||
1069 | { |
||
1070 | printf("%s=%lld\n", key, value); |
||
1071 | } |
||
1072 | |||
1073 | static const Writer ini_writer = { |
||
1074 | .name = "ini", |
||
1075 | .priv_size = sizeof(INIContext), |
||
1076 | .print_section_header = ini_print_section_header, |
||
1077 | .print_integer = ini_print_int, |
||
1078 | .print_string = ini_print_str, |
||
1079 | .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, |
||
1080 | .priv_class = &ini_class, |
||
1081 | }; |
||
1082 | |||
1083 | /* JSON output */ |
||
1084 | |||
1085 | typedef struct { |
||
1086 | const AVClass *class; |
||
1087 | int indent_level; |
||
1088 | int compact; |
||
1089 | const char *item_sep, *item_start_end; |
||
1090 | } JSONContext; |
||
1091 | |||
1092 | #undef OFFSET |
||
1093 | #define OFFSET(x) offsetof(JSONContext, x) |
||
1094 | |||
1095 | static const AVOption json_options[]= { |
||
1096 | { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1097 | { "c", "enable compact output", OFFSET(compact), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1098 | { NULL } |
||
1099 | }; |
||
1100 | |||
1101 | DEFINE_WRITER_CLASS(json); |
||
1102 | |||
1103 | static av_cold int json_init(WriterContext *wctx) |
||
1104 | { |
||
1105 | JSONContext *json = wctx->priv; |
||
1106 | |||
1107 | json->item_sep = json->compact ? ", " : ",\n"; |
||
1108 | json->item_start_end = json->compact ? " " : "\n"; |
||
1109 | |||
1110 | return 0; |
||
1111 | } |
||
1112 | |||
1113 | static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx) |
||
1114 | { |
||
1115 | static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0}; |
||
1116 | static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0}; |
||
1117 | const char *p; |
||
1118 | |||
1119 | for (p = src; *p; p++) { |
||
1120 | char *s = strchr(json_escape, *p); |
||
1121 | if (s) { |
||
1122 | av_bprint_chars(dst, '\\', 1); |
||
1123 | av_bprint_chars(dst, json_subst[s - json_escape], 1); |
||
1124 | } else if ((unsigned char)*p < 32) { |
||
1125 | av_bprintf(dst, "\\u00%02x", *p & 0xff); |
||
1126 | } else { |
||
1127 | av_bprint_chars(dst, *p, 1); |
||
1128 | } |
||
1129 | } |
||
1130 | return dst->str; |
||
1131 | } |
||
1132 | |||
1133 | #define JSON_INDENT() printf("%*c", json->indent_level * 4, ' ') |
||
1134 | |||
1135 | static void json_print_section_header(WriterContext *wctx) |
||
1136 | { |
||
1137 | JSONContext *json = wctx->priv; |
||
1138 | AVBPrint buf; |
||
1139 | const struct section *section = wctx->section[wctx->level]; |
||
1140 | const struct section *parent_section = wctx->level ? |
||
1141 | wctx->section[wctx->level-1] : NULL; |
||
1142 | |||
1143 | if (wctx->level && wctx->nb_item[wctx->level-1]) |
||
1144 | printf(",\n"); |
||
1145 | |||
1146 | if (section->flags & SECTION_FLAG_IS_WRAPPER) { |
||
1147 | printf("{\n"); |
||
1148 | json->indent_level++; |
||
1149 | } else { |
||
1150 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1151 | json_escape_str(&buf, section->name, wctx); |
||
1152 | JSON_INDENT(); |
||
1153 | |||
1154 | json->indent_level++; |
||
1155 | if (section->flags & SECTION_FLAG_IS_ARRAY) { |
||
1156 | printf("\"%s\": [\n", buf.str); |
||
1157 | } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) { |
||
1158 | printf("\"%s\": {%s", buf.str, json->item_start_end); |
||
1159 | } else { |
||
1160 | printf("{%s", json->item_start_end); |
||
1161 | |||
1162 | /* this is required so the parser can distinguish between packets and frames */ |
||
1163 | if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) { |
||
1164 | if (!json->compact) |
||
1165 | JSON_INDENT(); |
||
1166 | printf("\"type\": \"%s\"%s", section->name, json->item_sep); |
||
1167 | } |
||
1168 | } |
||
1169 | av_bprint_finalize(&buf, NULL); |
||
1170 | } |
||
1171 | } |
||
1172 | |||
1173 | static void json_print_section_footer(WriterContext *wctx) |
||
1174 | { |
||
1175 | JSONContext *json = wctx->priv; |
||
1176 | const struct section *section = wctx->section[wctx->level]; |
||
1177 | |||
1178 | if (wctx->level == 0) { |
||
1179 | json->indent_level--; |
||
1180 | printf("\n}\n"); |
||
1181 | } else if (section->flags & SECTION_FLAG_IS_ARRAY) { |
||
1182 | printf("\n"); |
||
1183 | json->indent_level--; |
||
1184 | JSON_INDENT(); |
||
1185 | printf("]"); |
||
1186 | } else { |
||
1187 | printf("%s", json->item_start_end); |
||
1188 | json->indent_level--; |
||
1189 | if (!json->compact) |
||
1190 | JSON_INDENT(); |
||
1191 | printf("}"); |
||
1192 | } |
||
1193 | } |
||
1194 | |||
1195 | static inline void json_print_item_str(WriterContext *wctx, |
||
1196 | const char *key, const char *value) |
||
1197 | { |
||
1198 | AVBPrint buf; |
||
1199 | |||
1200 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1201 | printf("\"%s\":", json_escape_str(&buf, key, wctx)); |
||
1202 | av_bprint_clear(&buf); |
||
1203 | printf(" \"%s\"", json_escape_str(&buf, value, wctx)); |
||
1204 | av_bprint_finalize(&buf, NULL); |
||
1205 | } |
||
1206 | |||
1207 | static void json_print_str(WriterContext *wctx, const char *key, const char *value) |
||
1208 | { |
||
1209 | JSONContext *json = wctx->priv; |
||
1210 | |||
1211 | if (wctx->nb_item[wctx->level]) |
||
1212 | printf("%s", json->item_sep); |
||
1213 | if (!json->compact) |
||
1214 | JSON_INDENT(); |
||
1215 | json_print_item_str(wctx, key, value); |
||
1216 | } |
||
1217 | |||
1218 | static void json_print_int(WriterContext *wctx, const char *key, long long int value) |
||
1219 | { |
||
1220 | JSONContext *json = wctx->priv; |
||
1221 | AVBPrint buf; |
||
1222 | |||
1223 | if (wctx->nb_item[wctx->level]) |
||
1224 | printf("%s", json->item_sep); |
||
1225 | if (!json->compact) |
||
1226 | JSON_INDENT(); |
||
1227 | |||
1228 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1229 | printf("\"%s\": %lld", json_escape_str(&buf, key, wctx), value); |
||
1230 | av_bprint_finalize(&buf, NULL); |
||
1231 | } |
||
1232 | |||
1233 | static const Writer json_writer = { |
||
1234 | .name = "json", |
||
1235 | .priv_size = sizeof(JSONContext), |
||
1236 | .init = json_init, |
||
1237 | .print_section_header = json_print_section_header, |
||
1238 | .print_section_footer = json_print_section_footer, |
||
1239 | .print_integer = json_print_int, |
||
1240 | .print_string = json_print_str, |
||
1241 | .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, |
||
1242 | .priv_class = &json_class, |
||
1243 | }; |
||
1244 | |||
1245 | /* XML output */ |
||
1246 | |||
1247 | typedef struct { |
||
1248 | const AVClass *class; |
||
1249 | int within_tag; |
||
1250 | int indent_level; |
||
1251 | int fully_qualified; |
||
1252 | int xsd_strict; |
||
1253 | } XMLContext; |
||
1254 | |||
1255 | #undef OFFSET |
||
1256 | #define OFFSET(x) offsetof(XMLContext, x) |
||
1257 | |||
1258 | static const AVOption xml_options[] = { |
||
1259 | {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1260 | {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1261 | {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1262 | {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 }, |
||
1263 | {NULL}, |
||
1264 | }; |
||
1265 | |||
1266 | DEFINE_WRITER_CLASS(xml); |
||
1267 | |||
1268 | static av_cold int xml_init(WriterContext *wctx) |
||
1269 | { |
||
1270 | XMLContext *xml = wctx->priv; |
||
1271 | |||
1272 | if (xml->xsd_strict) { |
||
1273 | xml->fully_qualified = 1; |
||
1274 | #define CHECK_COMPLIANCE(opt, opt_name) \ |
||
1275 | if (opt) { \ |
||
1276 | av_log(wctx, AV_LOG_ERROR, \ |
||
1277 | "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \ |
||
1278 | "You need to disable such option with '-no%s'\n", opt_name, opt_name); \ |
||
1279 | return AVERROR(EINVAL); \ |
||
1280 | } |
||
1281 | CHECK_COMPLIANCE(show_private_data, "private"); |
||
1282 | CHECK_COMPLIANCE(show_value_unit, "unit"); |
||
1283 | CHECK_COMPLIANCE(use_value_prefix, "prefix"); |
||
1284 | |||
1285 | if (do_show_frames && do_show_packets) { |
||
1286 | av_log(wctx, AV_LOG_ERROR, |
||
1287 | "Interleaved frames and packets are not allowed in XSD. " |
||
1288 | "Select only one between the -show_frames and the -show_packets options.\n"); |
||
1289 | return AVERROR(EINVAL); |
||
1290 | } |
||
1291 | } |
||
1292 | |||
1293 | return 0; |
||
1294 | } |
||
1295 | |||
1296 | static const char *xml_escape_str(AVBPrint *dst, const char *src, void *log_ctx) |
||
1297 | { |
||
1298 | const char *p; |
||
1299 | |||
1300 | for (p = src; *p; p++) { |
||
1301 | switch (*p) { |
||
1302 | case '&' : av_bprintf(dst, "%s", "&"); break; |
||
1303 | case '<' : av_bprintf(dst, "%s", "<"); break; |
||
1304 | case '>' : av_bprintf(dst, "%s", ">"); break; |
||
1305 | case '"' : av_bprintf(dst, "%s", """); break; |
||
1306 | case '\'': av_bprintf(dst, "%s", "'"); break; |
||
1307 | default: av_bprint_chars(dst, *p, 1); |
||
1308 | } |
||
1309 | } |
||
1310 | |||
1311 | return dst->str; |
||
1312 | } |
||
1313 | |||
1314 | #define XML_INDENT() printf("%*c", xml->indent_level * 4, ' ') |
||
1315 | |||
1316 | static void xml_print_section_header(WriterContext *wctx) |
||
1317 | { |
||
1318 | XMLContext *xml = wctx->priv; |
||
1319 | const struct section *section = wctx->section[wctx->level]; |
||
1320 | const struct section *parent_section = wctx->level ? |
||
1321 | wctx->section[wctx->level-1] : NULL; |
||
1322 | |||
1323 | if (wctx->level == 0) { |
||
1324 | const char *qual = " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " |
||
1325 | "xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' " |
||
1326 | "xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'"; |
||
1327 | |||
1328 | printf("\n"); |
||
1329 | printf("<%sffprobe%s>\n", |
||
1330 | xml->fully_qualified ? "ffprobe:" : "", |
||
1331 | xml->fully_qualified ? qual : ""); |
||
1332 | return; |
||
1333 | } |
||
1334 | |||
1335 | if (xml->within_tag) { |
||
1336 | xml->within_tag = 0; |
||
1337 | printf(">\n"); |
||
1338 | } |
||
1339 | if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { |
||
1340 | xml->indent_level++; |
||
1341 | } else { |
||
1342 | if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) && |
||
1343 | wctx->level && wctx->nb_item[wctx->level-1]) |
||
1344 | printf("\n"); |
||
1345 | xml->indent_level++; |
||
1346 | |||
1347 | if (section->flags & SECTION_FLAG_IS_ARRAY) { |
||
1348 | XML_INDENT(); printf("<%s>\n", section->name); |
||
1349 | } else { |
||
1350 | XML_INDENT(); printf("<%s ", section->name); |
||
1351 | xml->within_tag = 1; |
||
1352 | } |
||
1353 | } |
||
1354 | } |
||
1355 | |||
1356 | static void xml_print_section_footer(WriterContext *wctx) |
||
1357 | { |
||
1358 | XMLContext *xml = wctx->priv; |
||
1359 | const struct section *section = wctx->section[wctx->level]; |
||
1360 | |||
1361 | if (wctx->level == 0) { |
||
1362 | printf("%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : ""); |
||
1363 | } else if (xml->within_tag) { |
||
1364 | xml->within_tag = 0; |
||
1365 | printf("/>\n"); |
||
1366 | xml->indent_level--; |
||
1367 | } else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { |
||
1368 | xml->indent_level--; |
||
1369 | } else { |
||
1370 | XML_INDENT(); printf("%s>\n", section->name); |
||
1371 | xml->indent_level--; |
||
1372 | } |
||
1373 | } |
||
1374 | |||
1375 | static void xml_print_str(WriterContext *wctx, const char *key, const char *value) |
||
1376 | { |
||
1377 | AVBPrint buf; |
||
1378 | XMLContext *xml = wctx->priv; |
||
1379 | const struct section *section = wctx->section[wctx->level]; |
||
1380 | |||
1381 | av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1382 | |||
1383 | if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) { |
||
1384 | XML_INDENT(); |
||
1385 | printf("<%s key=\"%s\"", |
||
1386 | section->element_name, xml_escape_str(&buf, key, wctx)); |
||
1387 | av_bprint_clear(&buf); |
||
1388 | printf(" value=\"%s\"/>\n", xml_escape_str(&buf, value, wctx)); |
||
1389 | } else { |
||
1390 | if (wctx->nb_item[wctx->level]) |
||
1391 | printf(" "); |
||
1392 | printf("%s=\"%s\"", key, xml_escape_str(&buf, value, wctx)); |
||
1393 | } |
||
1394 | |||
1395 | av_bprint_finalize(&buf, NULL); |
||
1396 | } |
||
1397 | |||
1398 | static void xml_print_int(WriterContext *wctx, const char *key, long long int value) |
||
1399 | { |
||
1400 | if (wctx->nb_item[wctx->level]) |
||
1401 | printf(" "); |
||
1402 | printf("%s=\"%lld\"", key, value); |
||
1403 | } |
||
1404 | |||
1405 | static Writer xml_writer = { |
||
1406 | .name = "xml", |
||
1407 | .priv_size = sizeof(XMLContext), |
||
1408 | .init = xml_init, |
||
1409 | .print_section_header = xml_print_section_header, |
||
1410 | .print_section_footer = xml_print_section_footer, |
||
1411 | .print_integer = xml_print_int, |
||
1412 | .print_string = xml_print_str, |
||
1413 | .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER, |
||
1414 | .priv_class = &xml_class, |
||
1415 | }; |
||
1416 | |||
1417 | static void writer_register_all(void) |
||
1418 | { |
||
1419 | static int initialized; |
||
1420 | |||
1421 | if (initialized) |
||
1422 | return; |
||
1423 | initialized = 1; |
||
1424 | |||
1425 | writer_register(&default_writer); |
||
1426 | writer_register(&compact_writer); |
||
1427 | writer_register(&csv_writer); |
||
1428 | writer_register(&flat_writer); |
||
1429 | writer_register(&ini_writer); |
||
1430 | writer_register(&json_writer); |
||
1431 | writer_register(&xml_writer); |
||
1432 | } |
||
1433 | |||
1434 | #define print_fmt(k, f, ...) do { \ |
||
1435 | av_bprint_clear(&pbuf); \ |
||
1436 | av_bprintf(&pbuf, f, __VA_ARGS__); \ |
||
1437 | writer_print_string(w, k, pbuf.str, 0); \ |
||
1438 | } while (0) |
||
1439 | |||
1440 | #define print_int(k, v) writer_print_integer(w, k, v) |
||
1441 | #define print_q(k, v, s) writer_print_rational(w, k, v, s) |
||
1442 | #define print_str(k, v) writer_print_string(w, k, v, 0) |
||
1443 | #define print_str_opt(k, v) writer_print_string(w, k, v, 1) |
||
1444 | #define print_time(k, v, tb) writer_print_time(w, k, v, tb, 0) |
||
1445 | #define print_ts(k, v) writer_print_ts(w, k, v, 0) |
||
1446 | #define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1) |
||
1447 | #define print_duration_ts(k, v) writer_print_ts(w, k, v, 1) |
||
1448 | #define print_val(k, v, u) do { \ |
||
1449 | struct unit_value uv; \ |
||
1450 | uv.val.i = v; \ |
||
1451 | uv.unit = u; \ |
||
1452 | writer_print_string(w, k, value_string(val_str, sizeof(val_str), uv), 0); \ |
||
1453 | } while (0) |
||
1454 | |||
1455 | #define print_section_header(s) writer_print_section_header(w, s) |
||
1456 | #define print_section_footer(s) writer_print_section_footer(w, s) |
||
1457 | |||
1458 | static inline void show_tags(WriterContext *wctx, AVDictionary *tags, int section_id) |
||
1459 | { |
||
1460 | AVDictionaryEntry *tag = NULL; |
||
1461 | |||
1462 | if (!tags) |
||
1463 | return; |
||
1464 | writer_print_section_header(wctx, section_id); |
||
1465 | while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) |
||
1466 | writer_print_string(wctx, tag->key, tag->value, 0); |
||
1467 | writer_print_section_footer(wctx); |
||
1468 | } |
||
1469 | |||
1470 | static void show_packet(WriterContext *w, AVFormatContext *fmt_ctx, AVPacket *pkt, int packet_idx) |
||
1471 | { |
||
1472 | char val_str[128]; |
||
1473 | AVStream *st = fmt_ctx->streams[pkt->stream_index]; |
||
1474 | AVBPrint pbuf; |
||
1475 | const char *s; |
||
1476 | |||
1477 | av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1478 | |||
1479 | writer_print_section_header(w, SECTION_ID_PACKET); |
||
1480 | |||
1481 | s = av_get_media_type_string(st->codec->codec_type); |
||
1482 | if (s) print_str ("codec_type", s); |
||
1483 | else print_str_opt("codec_type", "unknown"); |
||
1484 | print_int("stream_index", pkt->stream_index); |
||
1485 | print_ts ("pts", pkt->pts); |
||
1486 | print_time("pts_time", pkt->pts, &st->time_base); |
||
1487 | print_ts ("dts", pkt->dts); |
||
1488 | print_time("dts_time", pkt->dts, &st->time_base); |
||
1489 | print_duration_ts("duration", pkt->duration); |
||
1490 | print_duration_time("duration_time", pkt->duration, &st->time_base); |
||
1491 | print_duration_ts("convergence_duration", pkt->convergence_duration); |
||
1492 | print_duration_time("convergence_duration_time", pkt->convergence_duration, &st->time_base); |
||
1493 | print_val("size", pkt->size, unit_byte_str); |
||
1494 | if (pkt->pos != -1) print_fmt ("pos", "%"PRId64, pkt->pos); |
||
1495 | else print_str_opt("pos", "N/A"); |
||
1496 | print_fmt("flags", "%c", pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_'); |
||
1497 | if (do_show_data) |
||
1498 | writer_print_data(w, "data", pkt->data, pkt->size); |
||
1499 | writer_print_section_footer(w); |
||
1500 | |||
1501 | av_bprint_finalize(&pbuf, NULL); |
||
1502 | fflush(stdout); |
||
1503 | } |
||
1504 | |||
1505 | static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream, |
||
1506 | AVFormatContext *fmt_ctx) |
||
1507 | { |
||
1508 | AVBPrint pbuf; |
||
1509 | const char *s; |
||
1510 | |||
1511 | av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1512 | |||
1513 | writer_print_section_header(w, SECTION_ID_FRAME); |
||
1514 | |||
1515 | s = av_get_media_type_string(stream->codec->codec_type); |
||
1516 | if (s) print_str ("media_type", s); |
||
1517 | else print_str_opt("media_type", "unknown"); |
||
1518 | print_int("key_frame", frame->key_frame); |
||
1519 | print_ts ("pkt_pts", frame->pkt_pts); |
||
1520 | print_time("pkt_pts_time", frame->pkt_pts, &stream->time_base); |
||
1521 | print_ts ("pkt_dts", frame->pkt_dts); |
||
1522 | print_time("pkt_dts_time", frame->pkt_dts, &stream->time_base); |
||
1523 | print_duration_ts ("pkt_duration", av_frame_get_pkt_duration(frame)); |
||
1524 | print_duration_time("pkt_duration_time", av_frame_get_pkt_duration(frame), &stream->time_base); |
||
1525 | if (av_frame_get_pkt_pos (frame) != -1) print_fmt ("pkt_pos", "%"PRId64, av_frame_get_pkt_pos(frame)); |
||
1526 | else print_str_opt("pkt_pos", "N/A"); |
||
1527 | if (av_frame_get_pkt_size(frame) != -1) print_fmt ("pkt_size", "%d", av_frame_get_pkt_size(frame)); |
||
1528 | else print_str_opt("pkt_size", "N/A"); |
||
1529 | |||
1530 | switch (stream->codec->codec_type) { |
||
1531 | AVRational sar; |
||
1532 | |||
1533 | case AVMEDIA_TYPE_VIDEO: |
||
1534 | print_int("width", frame->width); |
||
1535 | print_int("height", frame->height); |
||
1536 | s = av_get_pix_fmt_name(frame->format); |
||
1537 | if (s) print_str ("pix_fmt", s); |
||
1538 | else print_str_opt("pix_fmt", "unknown"); |
||
1539 | sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, frame); |
||
1540 | if (sar.num) { |
||
1541 | print_q("sample_aspect_ratio", sar, ':'); |
||
1542 | } else { |
||
1543 | print_str_opt("sample_aspect_ratio", "N/A"); |
||
1544 | } |
||
1545 | print_fmt("pict_type", "%c", av_get_picture_type_char(frame->pict_type)); |
||
1546 | print_int("coded_picture_number", frame->coded_picture_number); |
||
1547 | print_int("display_picture_number", frame->display_picture_number); |
||
1548 | print_int("interlaced_frame", frame->interlaced_frame); |
||
1549 | print_int("top_field_first", frame->top_field_first); |
||
1550 | print_int("repeat_pict", frame->repeat_pict); |
||
1551 | break; |
||
1552 | |||
1553 | case AVMEDIA_TYPE_AUDIO: |
||
1554 | s = av_get_sample_fmt_name(frame->format); |
||
1555 | if (s) print_str ("sample_fmt", s); |
||
1556 | else print_str_opt("sample_fmt", "unknown"); |
||
1557 | print_int("nb_samples", frame->nb_samples); |
||
1558 | print_int("channels", av_frame_get_channels(frame)); |
||
1559 | if (av_frame_get_channel_layout(frame)) { |
||
1560 | av_bprint_clear(&pbuf); |
||
1561 | av_bprint_channel_layout(&pbuf, av_frame_get_channels(frame), |
||
1562 | av_frame_get_channel_layout(frame)); |
||
1563 | print_str ("channel_layout", pbuf.str); |
||
1564 | } else |
||
1565 | print_str_opt("channel_layout", "unknown"); |
||
1566 | break; |
||
1567 | } |
||
1568 | show_tags(w, av_frame_get_metadata(frame), SECTION_ID_FRAME_TAGS); |
||
1569 | |||
1570 | writer_print_section_footer(w); |
||
1571 | |||
1572 | av_bprint_finalize(&pbuf, NULL); |
||
1573 | fflush(stdout); |
||
1574 | } |
||
1575 | |||
1576 | static av_always_inline int process_frame(WriterContext *w, |
||
1577 | AVFormatContext *fmt_ctx, |
||
1578 | AVFrame *frame, AVPacket *pkt) |
||
1579 | { |
||
1580 | AVCodecContext *dec_ctx = fmt_ctx->streams[pkt->stream_index]->codec; |
||
1581 | int ret = 0, got_frame = 0; |
||
1582 | |||
1583 | avcodec_get_frame_defaults(frame); |
||
1584 | if (dec_ctx->codec) { |
||
1585 | switch (dec_ctx->codec_type) { |
||
1586 | case AVMEDIA_TYPE_VIDEO: |
||
1587 | ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, pkt); |
||
1588 | break; |
||
1589 | |||
1590 | case AVMEDIA_TYPE_AUDIO: |
||
1591 | ret = avcodec_decode_audio4(dec_ctx, frame, &got_frame, pkt); |
||
1592 | break; |
||
1593 | } |
||
1594 | } |
||
1595 | |||
1596 | if (ret < 0) |
||
1597 | return ret; |
||
1598 | ret = FFMIN(ret, pkt->size); /* guard against bogus return values */ |
||
1599 | pkt->data += ret; |
||
1600 | pkt->size -= ret; |
||
1601 | if (got_frame) { |
||
1602 | nb_streams_frames[pkt->stream_index]++; |
||
1603 | if (do_show_frames) |
||
1604 | show_frame(w, frame, fmt_ctx->streams[pkt->stream_index], fmt_ctx); |
||
1605 | } |
||
1606 | return got_frame; |
||
1607 | } |
||
1608 | |||
1609 | static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level) |
||
1610 | { |
||
1611 | av_log(log_ctx, log_level, "id:%d", interval->id); |
||
1612 | |||
1613 | if (interval->has_start) { |
||
1614 | av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "", |
||
1615 | av_ts2timestr(interval->start, &AV_TIME_BASE_Q)); |
||
1616 | } else { |
||
1617 | av_log(log_ctx, log_level, " start:N/A"); |
||
1618 | } |
||
1619 | |||
1620 | if (interval->has_end) { |
||
1621 | av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : ""); |
||
1622 | if (interval->duration_frames) |
||
1623 | av_log(log_ctx, log_level, "#%"PRId64, interval->end); |
||
1624 | else |
||
1625 | av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q)); |
||
1626 | } else { |
||
1627 | av_log(log_ctx, log_level, " end:N/A"); |
||
1628 | } |
||
1629 | |||
1630 | av_log(log_ctx, log_level, "\n"); |
||
1631 | } |
||
1632 | |||
1633 | static int read_interval_packets(WriterContext *w, AVFormatContext *fmt_ctx, |
||
1634 | const ReadInterval *interval, int64_t *cur_ts) |
||
1635 | { |
||
1636 | AVPacket pkt, pkt1; |
||
1637 | AVFrame frame; |
||
1638 | int ret = 0, i = 0, frame_count = 0; |
||
1639 | int64_t start = -INT64_MAX, end = interval->end; |
||
1640 | int has_start = 0, has_end = interval->has_end && !interval->end_is_offset; |
||
1641 | |||
1642 | av_init_packet(&pkt); |
||
1643 | |||
1644 | av_log(NULL, AV_LOG_VERBOSE, "Processing read interval "); |
||
1645 | log_read_interval(interval, NULL, AV_LOG_VERBOSE); |
||
1646 | |||
1647 | if (interval->has_start) { |
||
1648 | int64_t target; |
||
1649 | if (interval->start_is_offset) { |
||
1650 | if (*cur_ts == AV_NOPTS_VALUE) { |
||
1651 | av_log(NULL, AV_LOG_ERROR, |
||
1652 | "Could not seek to relative position since current " |
||
1653 | "timestamp is not defined\n"); |
||
1654 | ret = AVERROR(EINVAL); |
||
1655 | goto end; |
||
1656 | } |
||
1657 | target = *cur_ts + interval->start; |
||
1658 | } else { |
||
1659 | target = interval->start; |
||
1660 | } |
||
1661 | |||
1662 | av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n", |
||
1663 | av_ts2timestr(target, &AV_TIME_BASE_Q)); |
||
1664 | if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) { |
||
1665 | av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n", |
||
1666 | interval->start, av_err2str(ret)); |
||
1667 | goto end; |
||
1668 | } |
||
1669 | } |
||
1670 | |||
1671 | while (!av_read_frame(fmt_ctx, &pkt)) { |
||
1672 | if (selected_streams[pkt.stream_index]) { |
||
1673 | AVRational tb = fmt_ctx->streams[pkt.stream_index]->time_base; |
||
1674 | |||
1675 | if (pkt.pts != AV_NOPTS_VALUE) |
||
1676 | *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q); |
||
1677 | |||
1678 | if (!has_start && *cur_ts != AV_NOPTS_VALUE) { |
||
1679 | start = *cur_ts; |
||
1680 | has_start = 1; |
||
1681 | } |
||
1682 | |||
1683 | if (has_start && !has_end && interval->end_is_offset) { |
||
1684 | end = start + interval->end; |
||
1685 | has_end = 1; |
||
1686 | } |
||
1687 | |||
1688 | if (interval->end_is_offset && interval->duration_frames) { |
||
1689 | if (frame_count >= interval->end) |
||
1690 | break; |
||
1691 | } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) { |
||
1692 | break; |
||
1693 | } |
||
1694 | |||
1695 | frame_count++; |
||
1696 | if (do_read_packets) { |
||
1697 | if (do_show_packets) |
||
1698 | show_packet(w, fmt_ctx, &pkt, i++); |
||
1699 | nb_streams_packets[pkt.stream_index]++; |
||
1700 | } |
||
1701 | if (do_read_frames) { |
||
1702 | pkt1 = pkt; |
||
1703 | while (pkt1.size && process_frame(w, fmt_ctx, &frame, &pkt1) > 0); |
||
1704 | } |
||
1705 | } |
||
1706 | av_free_packet(&pkt); |
||
1707 | } |
||
1708 | av_init_packet(&pkt); |
||
1709 | pkt.data = NULL; |
||
1710 | pkt.size = 0; |
||
1711 | //Flush remaining frames that are cached in the decoder |
||
1712 | for (i = 0; i < fmt_ctx->nb_streams; i++) { |
||
1713 | pkt.stream_index = i; |
||
1714 | if (do_read_frames) |
||
1715 | while (process_frame(w, fmt_ctx, &frame, &pkt) > 0); |
||
1716 | } |
||
1717 | |||
1718 | end: |
||
1719 | if (ret < 0) { |
||
1720 | av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval "); |
||
1721 | log_read_interval(interval, NULL, AV_LOG_ERROR); |
||
1722 | } |
||
1723 | return ret; |
||
1724 | } |
||
1725 | |||
1726 | static void read_packets(WriterContext *w, AVFormatContext *fmt_ctx) |
||
1727 | { |
||
1728 | int i, ret = 0; |
||
1729 | int64_t cur_ts = fmt_ctx->start_time; |
||
1730 | |||
1731 | if (read_intervals_nb == 0) { |
||
1732 | ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 }; |
||
1733 | ret = read_interval_packets(w, fmt_ctx, &interval, &cur_ts); |
||
1734 | } else { |
||
1735 | for (i = 0; i < read_intervals_nb; i++) { |
||
1736 | ret = read_interval_packets(w, fmt_ctx, &read_intervals[i], &cur_ts); |
||
1737 | if (ret < 0) |
||
1738 | break; |
||
1739 | } |
||
1740 | } |
||
1741 | } |
||
1742 | |||
1743 | static void show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program) |
||
1744 | { |
||
1745 | AVStream *stream = fmt_ctx->streams[stream_idx]; |
||
1746 | AVCodecContext *dec_ctx; |
||
1747 | const AVCodec *dec; |
||
1748 | char val_str[128]; |
||
1749 | const char *s; |
||
1750 | AVRational sar, dar; |
||
1751 | AVBPrint pbuf; |
||
1752 | |||
1753 | av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
1754 | |||
1755 | writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM); |
||
1756 | |||
1757 | print_int("index", stream->index); |
||
1758 | |||
1759 | if ((dec_ctx = stream->codec)) { |
||
1760 | const char *profile = NULL; |
||
1761 | dec = dec_ctx->codec; |
||
1762 | if (dec) { |
||
1763 | print_str("codec_name", dec->name); |
||
1764 | if (!do_bitexact) { |
||
1765 | if (dec->long_name) print_str ("codec_long_name", dec->long_name); |
||
1766 | else print_str_opt("codec_long_name", "unknown"); |
||
1767 | } |
||
1768 | } else { |
||
1769 | print_str_opt("codec_name", "unknown"); |
||
1770 | if (!do_bitexact) { |
||
1771 | print_str_opt("codec_long_name", "unknown"); |
||
1772 | } |
||
1773 | } |
||
1774 | |||
1775 | if (dec && (profile = av_get_profile_name(dec, dec_ctx->profile))) |
||
1776 | print_str("profile", profile); |
||
1777 | else |
||
1778 | print_str_opt("profile", "unknown"); |
||
1779 | |||
1780 | s = av_get_media_type_string(dec_ctx->codec_type); |
||
1781 | if (s) print_str ("codec_type", s); |
||
1782 | else print_str_opt("codec_type", "unknown"); |
||
1783 | print_q("codec_time_base", dec_ctx->time_base, '/'); |
||
1784 | |||
1785 | /* print AVI/FourCC tag */ |
||
1786 | av_get_codec_tag_string(val_str, sizeof(val_str), dec_ctx->codec_tag); |
||
1787 | print_str("codec_tag_string", val_str); |
||
1788 | print_fmt("codec_tag", "0x%04x", dec_ctx->codec_tag); |
||
1789 | |||
1790 | switch (dec_ctx->codec_type) { |
||
1791 | case AVMEDIA_TYPE_VIDEO: |
||
1792 | print_int("width", dec_ctx->width); |
||
1793 | print_int("height", dec_ctx->height); |
||
1794 | print_int("has_b_frames", dec_ctx->has_b_frames); |
||
1795 | sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, NULL); |
||
1796 | if (sar.den) { |
||
1797 | print_q("sample_aspect_ratio", sar, ':'); |
||
1798 | av_reduce(&dar.num, &dar.den, |
||
1799 | dec_ctx->width * sar.num, |
||
1800 | dec_ctx->height * sar.den, |
||
1801 | 1024*1024); |
||
1802 | print_q("display_aspect_ratio", dar, ':'); |
||
1803 | } else { |
||
1804 | print_str_opt("sample_aspect_ratio", "N/A"); |
||
1805 | print_str_opt("display_aspect_ratio", "N/A"); |
||
1806 | } |
||
1807 | s = av_get_pix_fmt_name(dec_ctx->pix_fmt); |
||
1808 | if (s) print_str ("pix_fmt", s); |
||
1809 | else print_str_opt("pix_fmt", "unknown"); |
||
1810 | print_int("level", dec_ctx->level); |
||
1811 | if (dec_ctx->timecode_frame_start >= 0) { |
||
1812 | char tcbuf[AV_TIMECODE_STR_SIZE]; |
||
1813 | av_timecode_make_mpeg_tc_string(tcbuf, dec_ctx->timecode_frame_start); |
||
1814 | print_str("timecode", tcbuf); |
||
1815 | } else { |
||
1816 | print_str_opt("timecode", "N/A"); |
||
1817 | } |
||
1818 | break; |
||
1819 | |||
1820 | case AVMEDIA_TYPE_AUDIO: |
||
1821 | s = av_get_sample_fmt_name(dec_ctx->sample_fmt); |
||
1822 | if (s) print_str ("sample_fmt", s); |
||
1823 | else print_str_opt("sample_fmt", "unknown"); |
||
1824 | print_val("sample_rate", dec_ctx->sample_rate, unit_hertz_str); |
||
1825 | print_int("channels", dec_ctx->channels); |
||
1826 | |||
1827 | if (dec_ctx->channel_layout) { |
||
1828 | av_bprint_clear(&pbuf); |
||
1829 | av_bprint_channel_layout(&pbuf, dec_ctx->channels, dec_ctx->channel_layout); |
||
1830 | print_str ("channel_layout", pbuf.str); |
||
1831 | } else { |
||
1832 | print_str_opt("channel_layout", "unknown"); |
||
1833 | } |
||
1834 | |||
1835 | print_int("bits_per_sample", av_get_bits_per_sample(dec_ctx->codec_id)); |
||
1836 | break; |
||
1837 | |||
1838 | case AVMEDIA_TYPE_SUBTITLE: |
||
1839 | if (dec_ctx->width) |
||
1840 | print_int("width", dec_ctx->width); |
||
1841 | else |
||
1842 | print_str_opt("width", "N/A"); |
||
1843 | if (dec_ctx->height) |
||
1844 | print_int("height", dec_ctx->height); |
||
1845 | else |
||
1846 | print_str_opt("height", "N/A"); |
||
1847 | break; |
||
1848 | } |
||
1849 | } else { |
||
1850 | print_str_opt("codec_type", "unknown"); |
||
1851 | } |
||
1852 | if (dec_ctx->codec && dec_ctx->codec->priv_class && show_private_data) { |
||
1853 | const AVOption *opt = NULL; |
||
1854 | while (opt = av_opt_next(dec_ctx->priv_data,opt)) { |
||
1855 | uint8_t *str; |
||
1856 | if (opt->flags) continue; |
||
1857 | if (av_opt_get(dec_ctx->priv_data, opt->name, 0, &str) >= 0) { |
||
1858 | print_str(opt->name, str); |
||
1859 | av_free(str); |
||
1860 | } |
||
1861 | } |
||
1862 | } |
||
1863 | |||
1864 | if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt ("id", "0x%x", stream->id); |
||
1865 | else print_str_opt("id", "N/A"); |
||
1866 | print_q("r_frame_rate", stream->r_frame_rate, '/'); |
||
1867 | print_q("avg_frame_rate", stream->avg_frame_rate, '/'); |
||
1868 | print_q("time_base", stream->time_base, '/'); |
||
1869 | print_ts ("start_pts", stream->start_time); |
||
1870 | print_time("start_time", stream->start_time, &stream->time_base); |
||
1871 | print_ts ("duration_ts", stream->duration); |
||
1872 | print_time("duration", stream->duration, &stream->time_base); |
||
1873 | if (dec_ctx->bit_rate > 0) print_val ("bit_rate", dec_ctx->bit_rate, unit_bit_per_second_str); |
||
1874 | else print_str_opt("bit_rate", "N/A"); |
||
1875 | if (stream->nb_frames) print_fmt ("nb_frames", "%"PRId64, stream->nb_frames); |
||
1876 | else print_str_opt("nb_frames", "N/A"); |
||
1877 | if (nb_streams_frames[stream_idx]) print_fmt ("nb_read_frames", "%"PRIu64, nb_streams_frames[stream_idx]); |
||
1878 | else print_str_opt("nb_read_frames", "N/A"); |
||
1879 | if (nb_streams_packets[stream_idx]) print_fmt ("nb_read_packets", "%"PRIu64, nb_streams_packets[stream_idx]); |
||
1880 | else print_str_opt("nb_read_packets", "N/A"); |
||
1881 | if (do_show_data) |
||
1882 | writer_print_data(w, "extradata", dec_ctx->extradata, |
||
1883 | dec_ctx->extradata_size); |
||
1884 | |||
1885 | /* Print disposition information */ |
||
1886 | #define PRINT_DISPOSITION(flagname, name) do { \ |
||
1887 | print_int(name, !!(stream->disposition & AV_DISPOSITION_##flagname)); \ |
||
1888 | } while (0) |
||
1889 | |||
1890 | if (do_show_stream_disposition) { |
||
1891 | writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM_DISPOSITION : SECTION_ID_STREAM_DISPOSITION); |
||
1892 | PRINT_DISPOSITION(DEFAULT, "default"); |
||
1893 | PRINT_DISPOSITION(DUB, "dub"); |
||
1894 | PRINT_DISPOSITION(ORIGINAL, "original"); |
||
1895 | PRINT_DISPOSITION(COMMENT, "comment"); |
||
1896 | PRINT_DISPOSITION(LYRICS, "lyrics"); |
||
1897 | PRINT_DISPOSITION(KARAOKE, "karaoke"); |
||
1898 | PRINT_DISPOSITION(FORCED, "forced"); |
||
1899 | PRINT_DISPOSITION(HEARING_IMPAIRED, "hearing_impaired"); |
||
1900 | PRINT_DISPOSITION(VISUAL_IMPAIRED, "visual_impaired"); |
||
1901 | PRINT_DISPOSITION(CLEAN_EFFECTS, "clean_effects"); |
||
1902 | PRINT_DISPOSITION(ATTACHED_PIC, "attached_pic"); |
||
1903 | writer_print_section_footer(w); |
||
1904 | } |
||
1905 | |||
1906 | show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS); |
||
1907 | |||
1908 | writer_print_section_footer(w); |
||
1909 | av_bprint_finalize(&pbuf, NULL); |
||
1910 | fflush(stdout); |
||
1911 | } |
||
1912 | |||
1913 | static void show_streams(WriterContext *w, AVFormatContext *fmt_ctx) |
||
1914 | { |
||
1915 | int i; |
||
1916 | writer_print_section_header(w, SECTION_ID_STREAMS); |
||
1917 | for (i = 0; i < fmt_ctx->nb_streams; i++) |
||
1918 | if (selected_streams[i]) |
||
1919 | show_stream(w, fmt_ctx, i, 0); |
||
1920 | writer_print_section_footer(w); |
||
1921 | } |
||
1922 | |||
1923 | static void show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram *program) |
||
1924 | { |
||
1925 | int i; |
||
1926 | |||
1927 | writer_print_section_header(w, SECTION_ID_PROGRAM); |
||
1928 | print_int("program_id", program->id); |
||
1929 | print_int("program_num", program->program_num); |
||
1930 | print_int("nb_streams", program->nb_stream_indexes); |
||
1931 | print_int("pmt_pid", program->pmt_pid); |
||
1932 | print_int("pcr_pid", program->pcr_pid); |
||
1933 | print_ts("start_pts", program->start_time); |
||
1934 | print_time("start_time", program->start_time, &AV_TIME_BASE_Q); |
||
1935 | print_ts("end_pts", program->end_time); |
||
1936 | print_time("end_time", program->end_time, &AV_TIME_BASE_Q); |
||
1937 | show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS); |
||
1938 | |||
1939 | writer_print_section_header(w, SECTION_ID_PROGRAM_STREAMS); |
||
1940 | for (i = 0; i < program->nb_stream_indexes; i++) { |
||
1941 | if (selected_streams[program->stream_index[i]]) |
||
1942 | show_stream(w, fmt_ctx, program->stream_index[i], 1); |
||
1943 | } |
||
1944 | writer_print_section_footer(w); |
||
1945 | |||
1946 | writer_print_section_footer(w); |
||
1947 | } |
||
1948 | |||
1949 | static void show_programs(WriterContext *w, AVFormatContext *fmt_ctx) |
||
1950 | { |
||
1951 | int i; |
||
1952 | |||
1953 | writer_print_section_header(w, SECTION_ID_PROGRAMS); |
||
1954 | for (i = 0; i < fmt_ctx->nb_programs; i++) { |
||
1955 | AVProgram *program = fmt_ctx->programs[i]; |
||
1956 | if (!program) |
||
1957 | continue; |
||
1958 | show_program(w, fmt_ctx, program); |
||
1959 | } |
||
1960 | writer_print_section_footer(w); |
||
1961 | } |
||
1962 | |||
1963 | static void show_chapters(WriterContext *w, AVFormatContext *fmt_ctx) |
||
1964 | { |
||
1965 | int i; |
||
1966 | |||
1967 | writer_print_section_header(w, SECTION_ID_CHAPTERS); |
||
1968 | for (i = 0; i < fmt_ctx->nb_chapters; i++) { |
||
1969 | AVChapter *chapter = fmt_ctx->chapters[i]; |
||
1970 | |||
1971 | writer_print_section_header(w, SECTION_ID_CHAPTER); |
||
1972 | print_int("id", chapter->id); |
||
1973 | print_q ("time_base", chapter->time_base, '/'); |
||
1974 | print_int("start", chapter->start); |
||
1975 | print_time("start_time", chapter->start, &chapter->time_base); |
||
1976 | print_int("end", chapter->end); |
||
1977 | print_time("end_time", chapter->end, &chapter->time_base); |
||
1978 | show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS); |
||
1979 | writer_print_section_footer(w); |
||
1980 | } |
||
1981 | writer_print_section_footer(w); |
||
1982 | } |
||
1983 | |||
1984 | static void show_format(WriterContext *w, AVFormatContext *fmt_ctx) |
||
1985 | { |
||
1986 | char val_str[128]; |
||
1987 | int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1; |
||
1988 | |||
1989 | writer_print_section_header(w, SECTION_ID_FORMAT); |
||
1990 | print_str("filename", fmt_ctx->filename); |
||
1991 | print_int("nb_streams", fmt_ctx->nb_streams); |
||
1992 | print_int("nb_programs", fmt_ctx->nb_programs); |
||
1993 | print_str("format_name", fmt_ctx->iformat->name); |
||
1994 | if (!do_bitexact) { |
||
1995 | if (fmt_ctx->iformat->long_name) print_str ("format_long_name", fmt_ctx->iformat->long_name); |
||
1996 | else print_str_opt("format_long_name", "unknown"); |
||
1997 | } |
||
1998 | print_time("start_time", fmt_ctx->start_time, &AV_TIME_BASE_Q); |
||
1999 | print_time("duration", fmt_ctx->duration, &AV_TIME_BASE_Q); |
||
2000 | if (size >= 0) print_val ("size", size, unit_byte_str); |
||
2001 | else print_str_opt("size", "N/A"); |
||
2002 | if (fmt_ctx->bit_rate > 0) print_val ("bit_rate", fmt_ctx->bit_rate, unit_bit_per_second_str); |
||
2003 | else print_str_opt("bit_rate", "N/A"); |
||
2004 | print_int("probe_score", av_format_get_probe_score(fmt_ctx)); |
||
2005 | show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS); |
||
2006 | |||
2007 | writer_print_section_footer(w); |
||
2008 | fflush(stdout); |
||
2009 | } |
||
2010 | |||
2011 | static void show_error(WriterContext *w, int err) |
||
2012 | { |
||
2013 | char errbuf[128]; |
||
2014 | const char *errbuf_ptr = errbuf; |
||
2015 | |||
2016 | if (av_strerror(err, errbuf, sizeof(errbuf)) < 0) |
||
2017 | errbuf_ptr = strerror(AVUNERROR(err)); |
||
2018 | |||
2019 | writer_print_section_header(w, SECTION_ID_ERROR); |
||
2020 | print_int("code", err); |
||
2021 | print_str("string", errbuf_ptr); |
||
2022 | writer_print_section_footer(w); |
||
2023 | } |
||
2024 | |||
2025 | static int open_input_file(AVFormatContext **fmt_ctx_ptr, const char *filename) |
||
2026 | { |
||
2027 | int err, i, orig_nb_streams; |
||
2028 | AVFormatContext *fmt_ctx = NULL; |
||
2029 | AVDictionaryEntry *t; |
||
2030 | AVDictionary **opts; |
||
2031 | |||
2032 | if ((err = avformat_open_input(&fmt_ctx, filename, |
||
2033 | iformat, &format_opts)) < 0) { |
||
2034 | print_error(filename, err); |
||
2035 | return err; |
||
2036 | } |
||
2037 | if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { |
||
2038 | av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key); |
||
2039 | return AVERROR_OPTION_NOT_FOUND; |
||
2040 | } |
||
2041 | |||
2042 | /* fill the streams in the format context */ |
||
2043 | opts = setup_find_stream_info_opts(fmt_ctx, codec_opts); |
||
2044 | orig_nb_streams = fmt_ctx->nb_streams; |
||
2045 | |||
2046 | if ((err = avformat_find_stream_info(fmt_ctx, opts)) < 0) { |
||
2047 | print_error(filename, err); |
||
2048 | return err; |
||
2049 | } |
||
2050 | for (i = 0; i < orig_nb_streams; i++) |
||
2051 | av_dict_free(&opts[i]); |
||
2052 | av_freep(&opts); |
||
2053 | |||
2054 | av_dump_format(fmt_ctx, 0, filename, 0); |
||
2055 | |||
2056 | /* bind a decoder to each input stream */ |
||
2057 | for (i = 0; i < fmt_ctx->nb_streams; i++) { |
||
2058 | AVStream *stream = fmt_ctx->streams[i]; |
||
2059 | AVCodec *codec; |
||
2060 | |||
2061 | if (stream->codec->codec_id == AV_CODEC_ID_PROBE) { |
||
2062 | av_log(NULL, AV_LOG_WARNING, |
||
2063 | "Failed to probe codec for input stream %d\n", |
||
2064 | stream->index); |
||
2065 | } else if (!(codec = avcodec_find_decoder(stream->codec->codec_id))) { |
||
2066 | av_log(NULL, AV_LOG_WARNING, |
||
2067 | "Unsupported codec with id %d for input stream %d\n", |
||
2068 | stream->codec->codec_id, stream->index); |
||
2069 | } else { |
||
2070 | AVDictionary *opts = filter_codec_opts(codec_opts, stream->codec->codec_id, |
||
2071 | fmt_ctx, stream, codec); |
||
2072 | if (avcodec_open2(stream->codec, codec, &opts) < 0) { |
||
2073 | av_log(NULL, AV_LOG_WARNING, "Could not open codec for input stream %d\n", |
||
2074 | stream->index); |
||
2075 | } |
||
2076 | if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) { |
||
2077 | av_log(NULL, AV_LOG_ERROR, "Option %s for input stream %d not found\n", |
||
2078 | t->key, stream->index); |
||
2079 | return AVERROR_OPTION_NOT_FOUND; |
||
2080 | } |
||
2081 | } |
||
2082 | } |
||
2083 | |||
2084 | *fmt_ctx_ptr = fmt_ctx; |
||
2085 | return 0; |
||
2086 | } |
||
2087 | |||
2088 | static void close_input_file(AVFormatContext **ctx_ptr) |
||
2089 | { |
||
2090 | int i; |
||
2091 | AVFormatContext *fmt_ctx = *ctx_ptr; |
||
2092 | |||
2093 | /* close decoder for each stream */ |
||
2094 | for (i = 0; i < fmt_ctx->nb_streams; i++) |
||
2095 | if (fmt_ctx->streams[i]->codec->codec_id != AV_CODEC_ID_NONE) |
||
2096 | avcodec_close(fmt_ctx->streams[i]->codec); |
||
2097 | |||
2098 | avformat_close_input(ctx_ptr); |
||
2099 | } |
||
2100 | |||
2101 | static int probe_file(WriterContext *wctx, const char *filename) |
||
2102 | { |
||
2103 | AVFormatContext *fmt_ctx; |
||
2104 | int ret, i; |
||
2105 | int section_id; |
||
2106 | |||
2107 | do_read_frames = do_show_frames || do_count_frames; |
||
2108 | do_read_packets = do_show_packets || do_count_packets; |
||
2109 | |||
2110 | ret = open_input_file(&fmt_ctx, filename); |
||
2111 | if (ret < 0) |
||
2112 | return ret; |
||
2113 | |||
2114 | nb_streams_frames = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_frames)); |
||
2115 | nb_streams_packets = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_packets)); |
||
2116 | selected_streams = av_calloc(fmt_ctx->nb_streams, sizeof(*selected_streams)); |
||
2117 | |||
2118 | for (i = 0; i < fmt_ctx->nb_streams; i++) { |
||
2119 | if (stream_specifier) { |
||
2120 | ret = avformat_match_stream_specifier(fmt_ctx, |
||
2121 | fmt_ctx->streams[i], |
||
2122 | stream_specifier); |
||
2123 | if (ret < 0) |
||
2124 | goto end; |
||
2125 | else |
||
2126 | selected_streams[i] = ret; |
||
2127 | ret = 0; |
||
2128 | } else { |
||
2129 | selected_streams[i] = 1; |
||
2130 | } |
||
2131 | } |
||
2132 | |||
2133 | if (do_read_frames || do_read_packets) { |
||
2134 | if (do_show_frames && do_show_packets && |
||
2135 | wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER) |
||
2136 | section_id = SECTION_ID_PACKETS_AND_FRAMES; |
||
2137 | else if (do_show_packets && !do_show_frames) |
||
2138 | section_id = SECTION_ID_PACKETS; |
||
2139 | else // (!do_show_packets && do_show_frames) |
||
2140 | section_id = SECTION_ID_FRAMES; |
||
2141 | if (do_show_frames || do_show_packets) |
||
2142 | writer_print_section_header(wctx, section_id); |
||
2143 | read_packets(wctx, fmt_ctx); |
||
2144 | if (do_show_frames || do_show_packets) |
||
2145 | writer_print_section_footer(wctx); |
||
2146 | } |
||
2147 | if (do_show_programs) |
||
2148 | show_programs(wctx, fmt_ctx); |
||
2149 | if (do_show_streams) |
||
2150 | show_streams(wctx, fmt_ctx); |
||
2151 | if (do_show_chapters) |
||
2152 | show_chapters(wctx, fmt_ctx); |
||
2153 | if (do_show_format) |
||
2154 | show_format(wctx, fmt_ctx); |
||
2155 | |||
2156 | end: |
||
2157 | close_input_file(&fmt_ctx); |
||
2158 | av_freep(&nb_streams_frames); |
||
2159 | av_freep(&nb_streams_packets); |
||
2160 | av_freep(&selected_streams); |
||
2161 | |||
2162 | return ret; |
||
2163 | } |
||
2164 | |||
2165 | static void show_usage(void) |
||
2166 | { |
||
2167 | av_log(NULL, AV_LOG_INFO, "Simple multimedia streams analyzer\n"); |
||
2168 | av_log(NULL, AV_LOG_INFO, "usage: %s [OPTIONS] [INPUT_FILE]\n", program_name); |
||
2169 | av_log(NULL, AV_LOG_INFO, "\n"); |
||
2170 | } |
||
2171 | |||
2172 | static void ffprobe_show_program_version(WriterContext *w) |
||
2173 | { |
||
2174 | AVBPrint pbuf; |
||
2175 | av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED); |
||
2176 | |||
2177 | writer_print_section_header(w, SECTION_ID_PROGRAM_VERSION); |
||
2178 | print_str("version", FFMPEG_VERSION); |
||
2179 | print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers", |
||
2180 | program_birth_year, this_year); |
||
2181 | print_str("build_date", __DATE__); |
||
2182 | print_str("build_time", __TIME__); |
||
2183 | print_str("compiler_ident", CC_IDENT); |
||
2184 | print_str("configuration", FFMPEG_CONFIGURATION); |
||
2185 | writer_print_section_footer(w); |
||
2186 | |||
2187 | av_bprint_finalize(&pbuf, NULL); |
||
2188 | } |
||
2189 | |||
2190 | #define SHOW_LIB_VERSION(libname, LIBNAME) \ |
||
2191 | do { \ |
||
2192 | if (CONFIG_##LIBNAME) { \ |
||
2193 | unsigned int version = libname##_version(); \ |
||
2194 | writer_print_section_header(w, SECTION_ID_LIBRARY_VERSION); \ |
||
2195 | print_str("name", "lib" #libname); \ |
||
2196 | print_int("major", LIB##LIBNAME##_VERSION_MAJOR); \ |
||
2197 | print_int("minor", LIB##LIBNAME##_VERSION_MINOR); \ |
||
2198 | print_int("micro", LIB##LIBNAME##_VERSION_MICRO); \ |
||
2199 | print_int("version", version); \ |
||
2200 | print_str("ident", LIB##LIBNAME##_IDENT); \ |
||
2201 | writer_print_section_footer(w); \ |
||
2202 | } \ |
||
2203 | } while (0) |
||
2204 | |||
2205 | static void ffprobe_show_library_versions(WriterContext *w) |
||
2206 | { |
||
2207 | writer_print_section_header(w, SECTION_ID_LIBRARY_VERSIONS); |
||
2208 | SHOW_LIB_VERSION(avutil, AVUTIL); |
||
2209 | SHOW_LIB_VERSION(avcodec, AVCODEC); |
||
2210 | SHOW_LIB_VERSION(avformat, AVFORMAT); |
||
2211 | SHOW_LIB_VERSION(avdevice, AVDEVICE); |
||
2212 | SHOW_LIB_VERSION(avfilter, AVFILTER); |
||
2213 | SHOW_LIB_VERSION(swscale, SWSCALE); |
||
2214 | SHOW_LIB_VERSION(swresample, SWRESAMPLE); |
||
2215 | SHOW_LIB_VERSION(postproc, POSTPROC); |
||
2216 | writer_print_section_footer(w); |
||
2217 | } |
||
2218 | |||
2219 | static int opt_format(void *optctx, const char *opt, const char *arg) |
||
2220 | { |
||
2221 | iformat = av_find_input_format(arg); |
||
2222 | if (!iformat) { |
||
2223 | av_log(NULL, AV_LOG_ERROR, "Unknown input format: %s\n", arg); |
||
2224 | return AVERROR(EINVAL); |
||
2225 | } |
||
2226 | return 0; |
||
2227 | } |
||
2228 | |||
2229 | static inline void mark_section_show_entries(SectionID section_id, |
||
2230 | int show_all_entries, AVDictionary *entries) |
||
2231 | { |
||
2232 | struct section *section = §ions[section_id]; |
||
2233 | |||
2234 | section->show_all_entries = show_all_entries; |
||
2235 | if (show_all_entries) { |
||
2236 | SectionID *id; |
||
2237 | for (id = section->children_ids; *id != -1; id++) |
||
2238 | mark_section_show_entries(*id, show_all_entries, entries); |
||
2239 | } else { |
||
2240 | av_dict_copy(§ion->entries_to_show, entries, 0); |
||
2241 | } |
||
2242 | } |
||
2243 | |||
2244 | static int match_section(const char *section_name, |
||
2245 | int show_all_entries, AVDictionary *entries) |
||
2246 | { |
||
2247 | int i, ret = 0; |
||
2248 | |||
2249 | for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) { |
||
2250 | const struct section *section = §ions[i]; |
||
2251 | if (!strcmp(section_name, section->name) || |
||
2252 | (section->unique_name && !strcmp(section_name, section->unique_name))) { |
||
2253 | av_log(NULL, AV_LOG_DEBUG, |
||
2254 | "'%s' matches section with unique name '%s'\n", section_name, |
||
2255 | (char *)av_x_if_null(section->unique_name, section->name)); |
||
2256 | ret++; |
||
2257 | mark_section_show_entries(section->id, show_all_entries, entries); |
||
2258 | } |
||
2259 | } |
||
2260 | return ret; |
||
2261 | } |
||
2262 | |||
2263 | static int opt_show_entries(void *optctx, const char *opt, const char *arg) |
||
2264 | { |
||
2265 | const char *p = arg; |
||
2266 | int ret = 0; |
||
2267 | |||
2268 | while (*p) { |
||
2269 | AVDictionary *entries = NULL; |
||
2270 | char *section_name = av_get_token(&p, "=:"); |
||
2271 | int show_all_entries = 0; |
||
2272 | |||
2273 | if (!section_name) { |
||
2274 | av_log(NULL, AV_LOG_ERROR, |
||
2275 | "Missing section name for option '%s'\n", opt); |
||
2276 | return AVERROR(EINVAL); |
||
2277 | } |
||
2278 | |||
2279 | if (*p == '=') { |
||
2280 | p++; |
||
2281 | while (*p && *p != ':') { |
||
2282 | char *entry = av_get_token(&p, ",:"); |
||
2283 | if (!entry) |
||
2284 | break; |
||
2285 | av_log(NULL, AV_LOG_VERBOSE, |
||
2286 | "Adding '%s' to the entries to show in section '%s'\n", |
||
2287 | entry, section_name); |
||
2288 | av_dict_set(&entries, entry, "", AV_DICT_DONT_STRDUP_KEY); |
||
2289 | if (*p == ',') |
||
2290 | p++; |
||
2291 | } |
||
2292 | } else { |
||
2293 | show_all_entries = 1; |
||
2294 | } |
||
2295 | |||
2296 | ret = match_section(section_name, show_all_entries, entries); |
||
2297 | if (ret == 0) { |
||
2298 | av_log(NULL, AV_LOG_ERROR, "No match for section '%s'\n", section_name); |
||
2299 | ret = AVERROR(EINVAL); |
||
2300 | } |
||
2301 | av_dict_free(&entries); |
||
2302 | av_free(section_name); |
||
2303 | |||
2304 | if (ret <= 0) |
||
2305 | break; |
||
2306 | if (*p) |
||
2307 | p++; |
||
2308 | } |
||
2309 | |||
2310 | return ret; |
||
2311 | } |
||
2312 | |||
2313 | static int opt_show_format_entry(void *optctx, const char *opt, const char *arg) |
||
2314 | { |
||
2315 | char *buf = av_asprintf("format=%s", arg); |
||
2316 | int ret; |
||
2317 | |||
2318 | av_log(NULL, AV_LOG_WARNING, |
||
2319 | "Option '%s' is deprecated, use '-show_entries format=%s' instead\n", |
||
2320 | opt, arg); |
||
2321 | ret = opt_show_entries(optctx, opt, buf); |
||
2322 | av_free(buf); |
||
2323 | return ret; |
||
2324 | } |
||
2325 | |||
2326 | static void opt_input_file(void *optctx, const char *arg) |
||
2327 | { |
||
2328 | if (input_filename) { |
||
2329 | av_log(NULL, AV_LOG_ERROR, |
||
2330 | "Argument '%s' provided as input filename, but '%s' was already specified.\n", |
||
2331 | arg, input_filename); |
||
2332 | exit_program(1); |
||
2333 | } |
||
2334 | if (!strcmp(arg, "-")) |
||
2335 | arg = "pipe:"; |
||
2336 | input_filename = arg; |
||
2337 | } |
||
2338 | |||
2339 | static int opt_input_file_i(void *optctx, const char *opt, const char *arg) |
||
2340 | { |
||
2341 | opt_input_file(optctx, arg); |
||
2342 | return 0; |
||
2343 | } |
||
2344 | |||
2345 | void show_help_default(const char *opt, const char *arg) |
||
2346 | { |
||
2347 | av_log_set_callback(log_callback_help); |
||
2348 | show_usage(); |
||
2349 | show_help_options(options, "Main options:", 0, 0, 0); |
||
2350 | printf("\n"); |
||
2351 | |||
2352 | show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM); |
||
2353 | } |
||
2354 | |||
2355 | /** |
||
2356 | * Parse interval specification, according to the format: |
||
2357 | * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]] |
||
2358 | * INTERVALS ::= INTERVAL[,INTERVALS] |
||
2359 | */ |
||
2360 | static int parse_read_interval(const char *interval_spec, |
||
2361 | ReadInterval *interval) |
||
2362 | { |
||
2363 | int ret = 0; |
||
2364 | char *next, *p, *spec = av_strdup(interval_spec); |
||
2365 | if (!spec) |
||
2366 | return AVERROR(ENOMEM); |
||
2367 | |||
2368 | if (!*spec) { |
||
2369 | av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n"); |
||
2370 | ret = AVERROR(EINVAL); |
||
2371 | goto end; |
||
2372 | } |
||
2373 | |||
2374 | p = spec; |
||
2375 | next = strchr(spec, '%'); |
||
2376 | if (next) |
||
2377 | *next++ = 0; |
||
2378 | |||
2379 | /* parse first part */ |
||
2380 | if (*p) { |
||
2381 | interval->has_start = 1; |
||
2382 | |||
2383 | if (*p == '+') { |
||
2384 | interval->start_is_offset = 1; |
||
2385 | p++; |
||
2386 | } else { |
||
2387 | interval->start_is_offset = 0; |
||
2388 | } |
||
2389 | |||
2390 | ret = av_parse_time(&interval->start, p, 1); |
||
2391 | if (ret < 0) { |
||
2392 | av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p); |
||
2393 | goto end; |
||
2394 | } |
||
2395 | } else { |
||
2396 | interval->has_start = 0; |
||
2397 | } |
||
2398 | |||
2399 | /* parse second part */ |
||
2400 | p = next; |
||
2401 | if (p && *p) { |
||
2402 | int64_t us; |
||
2403 | interval->has_end = 1; |
||
2404 | |||
2405 | if (*p == '+') { |
||
2406 | interval->end_is_offset = 1; |
||
2407 | p++; |
||
2408 | } else { |
||
2409 | interval->end_is_offset = 0; |
||
2410 | } |
||
2411 | |||
2412 | if (interval->end_is_offset && *p == '#') { |
||
2413 | long long int lli; |
||
2414 | char *tail; |
||
2415 | interval->duration_frames = 1; |
||
2416 | p++; |
||
2417 | lli = strtoll(p, &tail, 10); |
||
2418 | if (*tail || lli < 0) { |
||
2419 | av_log(NULL, AV_LOG_ERROR, |
||
2420 | "Invalid or negative value '%s' for duration number of frames\n", p); |
||
2421 | goto end; |
||
2422 | } |
||
2423 | interval->end = lli; |
||
2424 | } else { |
||
2425 | ret = av_parse_time(&us, p, 1); |
||
2426 | if (ret < 0) { |
||
2427 | av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p); |
||
2428 | goto end; |
||
2429 | } |
||
2430 | interval->end = us; |
||
2431 | } |
||
2432 | } else { |
||
2433 | interval->has_end = 0; |
||
2434 | } |
||
2435 | |||
2436 | end: |
||
2437 | av_free(spec); |
||
2438 | return ret; |
||
2439 | } |
||
2440 | |||
2441 | static int parse_read_intervals(const char *intervals_spec) |
||
2442 | { |
||
2443 | int ret, n, i; |
||
2444 | char *p, *spec = av_strdup(intervals_spec); |
||
2445 | if (!spec) |
||
2446 | return AVERROR(ENOMEM); |
||
2447 | |||
2448 | /* preparse specification, get number of intervals */ |
||
2449 | for (n = 0, p = spec; *p; p++) |
||
2450 | if (*p == ',') |
||
2451 | n++; |
||
2452 | n++; |
||
2453 | |||
2454 | read_intervals = av_malloc(n * sizeof(*read_intervals)); |
||
2455 | if (!read_intervals) { |
||
2456 | ret = AVERROR(ENOMEM); |
||
2457 | goto end; |
||
2458 | } |
||
2459 | read_intervals_nb = n; |
||
2460 | |||
2461 | /* parse intervals */ |
||
2462 | p = spec; |
||
2463 | for (i = 0; p; i++) { |
||
2464 | char *next; |
||
2465 | |||
2466 | av_assert0(i < read_intervals_nb); |
||
2467 | next = strchr(p, ','); |
||
2468 | if (next) |
||
2469 | *next++ = 0; |
||
2470 | |||
2471 | read_intervals[i].id = i; |
||
2472 | ret = parse_read_interval(p, &read_intervals[i]); |
||
2473 | if (ret < 0) { |
||
2474 | av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n", |
||
2475 | i, p); |
||
2476 | goto end; |
||
2477 | } |
||
2478 | av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval "); |
||
2479 | log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE); |
||
2480 | p = next; |
||
2481 | } |
||
2482 | av_assert0(i == read_intervals_nb); |
||
2483 | |||
2484 | end: |
||
2485 | av_free(spec); |
||
2486 | return ret; |
||
2487 | } |
||
2488 | |||
2489 | static int opt_read_intervals(void *optctx, const char *opt, const char *arg) |
||
2490 | { |
||
2491 | return parse_read_intervals(arg); |
||
2492 | } |
||
2493 | |||
2494 | static int opt_pretty(void *optctx, const char *opt, const char *arg) |
||
2495 | { |
||
2496 | show_value_unit = 1; |
||
2497 | use_value_prefix = 1; |
||
2498 | use_byte_value_binary_prefix = 1; |
||
2499 | use_value_sexagesimal_format = 1; |
||
2500 | return 0; |
||
2501 | } |
||
2502 | |||
2503 | static void print_section(SectionID id, int level) |
||
2504 | { |
||
2505 | const SectionID *pid; |
||
2506 | const struct section *section = §ions[id]; |
||
2507 | printf("%c%c%c", |
||
2508 | section->flags & SECTION_FLAG_IS_WRAPPER ? 'W' : '.', |
||
2509 | section->flags & SECTION_FLAG_IS_ARRAY ? 'A' : '.', |
||
2510 | section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS ? 'V' : '.'); |
||
2511 | printf("%*c %s", level * 4, ' ', section->name); |
||
2512 | if (section->unique_name) |
||
2513 | printf("/%s", section->unique_name); |
||
2514 | printf("\n"); |
||
2515 | |||
2516 | for (pid = section->children_ids; *pid != -1; pid++) |
||
2517 | print_section(*pid, level+1); |
||
2518 | } |
||
2519 | |||
2520 | static int opt_sections(void *optctx, const char *opt, const char *arg) |
||
2521 | { |
||
2522 | printf("Sections:\n" |
||
2523 | "W.. = Section is a wrapper (contains other sections, no local entries)\n" |
||
2524 | ".A. = Section contains an array of elements of the same type\n" |
||
2525 | "..V = Section may contain a variable number of fields with variable keys\n" |
||
2526 | "FLAGS NAME/UNIQUE_NAME\n" |
||
2527 | "---\n"); |
||
2528 | print_section(SECTION_ID_ROOT, 0); |
||
2529 | return 0; |
||
2530 | } |
||
2531 | |||
2532 | static int opt_show_versions(const char *opt, const char *arg) |
||
2533 | { |
||
2534 | mark_section_show_entries(SECTION_ID_PROGRAM_VERSION, 1, NULL); |
||
2535 | mark_section_show_entries(SECTION_ID_LIBRARY_VERSION, 1, NULL); |
||
2536 | return 0; |
||
2537 | } |
||
2538 | |||
2539 | #define DEFINE_OPT_SHOW_SECTION(section, target_section_id) \ |
||
2540 | static int opt_show_##section(const char *opt, const char *arg) \ |
||
2541 | { \ |
||
2542 | mark_section_show_entries(SECTION_ID_##target_section_id, 1, NULL); \ |
||
2543 | return 0; \ |
||
2544 | } |
||
2545 | |||
2546 | DEFINE_OPT_SHOW_SECTION(chapters, CHAPTERS); |
||
2547 | DEFINE_OPT_SHOW_SECTION(error, ERROR); |
||
2548 | DEFINE_OPT_SHOW_SECTION(format, FORMAT); |
||
2549 | DEFINE_OPT_SHOW_SECTION(frames, FRAMES); |
||
2550 | DEFINE_OPT_SHOW_SECTION(library_versions, LIBRARY_VERSIONS); |
||
2551 | DEFINE_OPT_SHOW_SECTION(packets, PACKETS); |
||
2552 | DEFINE_OPT_SHOW_SECTION(program_version, PROGRAM_VERSION); |
||
2553 | DEFINE_OPT_SHOW_SECTION(streams, STREAMS); |
||
2554 | DEFINE_OPT_SHOW_SECTION(programs, PROGRAMS); |
||
2555 | |||
2556 | static const OptionDef real_options[] = { |
||
2557 | #include "cmdutils_common_opts.h" |
||
2558 | { "f", HAS_ARG, {.func_arg = opt_format}, "force format", "format" }, |
||
2559 | { "unit", OPT_BOOL, {&show_value_unit}, "show unit of the displayed values" }, |
||
2560 | { "prefix", OPT_BOOL, {&use_value_prefix}, "use SI prefixes for the displayed values" }, |
||
2561 | { "byte_binary_prefix", OPT_BOOL, {&use_byte_value_binary_prefix}, |
||
2562 | "use binary prefixes for byte units" }, |
||
2563 | { "sexagesimal", OPT_BOOL, {&use_value_sexagesimal_format}, |
||
2564 | "use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units" }, |
||
2565 | { "pretty", 0, {.func_arg = opt_pretty}, |
||
2566 | "prettify the format of displayed values, make it more human readable" }, |
||
2567 | { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format}, |
||
2568 | "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)", "format" }, |
||
2569 | { "of", OPT_STRING | HAS_ARG, {(void*)&print_format}, "alias for -print_format", "format" }, |
||
2570 | { "select_streams", OPT_STRING | HAS_ARG, {(void*)&stream_specifier}, "select the specified streams", "stream_specifier" }, |
||
2571 | { "sections", OPT_EXIT, {.func_arg = opt_sections}, "print sections structure and section information, and exit" }, |
||
2572 | { "show_data", OPT_BOOL, {(void*)&do_show_data}, "show packets data" }, |
||
2573 | { "show_error", 0, {(void*)&opt_show_error}, "show probing error" }, |
||
2574 | { "show_format", 0, {(void*)&opt_show_format}, "show format/container info" }, |
||
2575 | { "show_frames", 0, {(void*)&opt_show_frames}, "show frames info" }, |
||
2576 | { "show_format_entry", HAS_ARG, {.func_arg = opt_show_format_entry}, |
||
2577 | "show a particular entry from the format/container info", "entry" }, |
||
2578 | { "show_entries", HAS_ARG, {.func_arg = opt_show_entries}, |
||
2579 | "show a set of specified entries", "entry_list" }, |
||
2580 | { "show_packets", 0, {(void*)&opt_show_packets}, "show packets info" }, |
||
2581 | { "show_programs", 0, {(void*)&opt_show_programs}, "show programs info" }, |
||
2582 | { "show_streams", 0, {(void*)&opt_show_streams}, "show streams info" }, |
||
2583 | { "show_chapters", 0, {(void*)&opt_show_chapters}, "show chapters info" }, |
||
2584 | { "count_frames", OPT_BOOL, {(void*)&do_count_frames}, "count the number of frames per stream" }, |
||
2585 | { "count_packets", OPT_BOOL, {(void*)&do_count_packets}, "count the number of packets per stream" }, |
||
2586 | { "show_program_version", 0, {(void*)&opt_show_program_version}, "show ffprobe version" }, |
||
2587 | { "show_library_versions", 0, {(void*)&opt_show_library_versions}, "show library versions" }, |
||
2588 | { "show_versions", 0, {(void*)&opt_show_versions}, "show program and library versions" }, |
||
2589 | { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" }, |
||
2590 | { "private", OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" }, |
||
2591 | { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" }, |
||
2592 | { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" }, |
||
2593 | { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" }, |
||
2594 | { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"}, |
||
2595 | { NULL, }, |
||
2596 | }; |
||
2597 | |||
2598 | static inline int check_section_show_entries(int section_id) |
||
2599 | { |
||
2600 | int *id; |
||
2601 | struct section *section = §ions[section_id]; |
||
2602 | if (sections[section_id].show_all_entries || sections[section_id].entries_to_show) |
||
2603 | return 1; |
||
2604 | for (id = section->children_ids; *id != -1; id++) |
||
2605 | if (check_section_show_entries(*id)) |
||
2606 | return 1; |
||
2607 | return 0; |
||
2608 | } |
||
2609 | |||
2610 | #define SET_DO_SHOW(id, varname) do { \ |
||
2611 | if (check_section_show_entries(SECTION_ID_##id)) \ |
||
2612 | do_show_##varname = 1; \ |
||
2613 | } while (0) |
||
2614 | |||
2615 | int main(int argc, char **argv) |
||
2616 | { |
||
2617 | const Writer *w; |
||
2618 | WriterContext *wctx; |
||
2619 | char *buf; |
||
2620 | char *w_name = NULL, *w_args = NULL; |
||
2621 | int ret, i; |
||
2622 | |||
2623 | av_log_set_flags(AV_LOG_SKIP_REPEATED); |
||
2624 | register_exit(ffprobe_cleanup); |
||
2625 | |||
2626 | options = real_options; |
||
2627 | parse_loglevel(argc, argv, options); |
||
2628 | av_register_all(); |
||
2629 | avformat_network_init(); |
||
2630 | init_opts(); |
||
2631 | #if CONFIG_AVDEVICE |
||
2632 | avdevice_register_all(); |
||
2633 | #endif |
||
2634 | |||
2635 | show_banner(argc, argv, options); |
||
2636 | parse_options(NULL, argc, argv, options, opt_input_file); |
||
2637 | |||
2638 | /* mark things to show, based on -show_entries */ |
||
2639 | SET_DO_SHOW(CHAPTERS, chapters); |
||
2640 | SET_DO_SHOW(ERROR, error); |
||
2641 | SET_DO_SHOW(FORMAT, format); |
||
2642 | SET_DO_SHOW(FRAMES, frames); |
||
2643 | SET_DO_SHOW(LIBRARY_VERSIONS, library_versions); |
||
2644 | SET_DO_SHOW(PACKETS, packets); |
||
2645 | SET_DO_SHOW(PROGRAM_VERSION, program_version); |
||
2646 | SET_DO_SHOW(PROGRAMS, programs); |
||
2647 | SET_DO_SHOW(STREAMS, streams); |
||
2648 | SET_DO_SHOW(STREAM_DISPOSITION, stream_disposition); |
||
2649 | SET_DO_SHOW(PROGRAM_STREAM_DISPOSITION, stream_disposition); |
||
2650 | |||
2651 | if (do_bitexact && (do_show_program_version || do_show_library_versions)) { |
||
2652 | av_log(NULL, AV_LOG_ERROR, |
||
2653 | "-bitexact and -show_program_version or -show_library_versions " |
||
2654 | "options are incompatible\n"); |
||
2655 | ret = AVERROR(EINVAL); |
||
2656 | goto end; |
||
2657 | } |
||
2658 | |||
2659 | writer_register_all(); |
||
2660 | |||
2661 | if (!print_format) |
||
2662 | print_format = av_strdup("default"); |
||
2663 | if (!print_format) { |
||
2664 | ret = AVERROR(ENOMEM); |
||
2665 | goto end; |
||
2666 | } |
||
2667 | w_name = av_strtok(print_format, "=", &buf); |
||
2668 | w_args = buf; |
||
2669 | |||
2670 | w = writer_get_by_name(w_name); |
||
2671 | if (!w) { |
||
2672 | av_log(NULL, AV_LOG_ERROR, "Unknown output format with name '%s'\n", w_name); |
||
2673 | ret = AVERROR(EINVAL); |
||
2674 | goto end; |
||
2675 | } |
||
2676 | |||
2677 | if ((ret = writer_open(&wctx, w, w_args, |
||
2678 | sections, FF_ARRAY_ELEMS(sections))) >= 0) { |
||
2679 | writer_print_section_header(wctx, SECTION_ID_ROOT); |
||
2680 | |||
2681 | if (do_show_program_version) |
||
2682 | ffprobe_show_program_version(wctx); |
||
2683 | if (do_show_library_versions) |
||
2684 | ffprobe_show_library_versions(wctx); |
||
2685 | |||
2686 | if (!input_filename && |
||
2687 | ((do_show_format || do_show_programs || do_show_streams || do_show_chapters || do_show_packets || do_show_error) || |
||
2688 | (!do_show_program_version && !do_show_library_versions))) { |
||
2689 | show_usage(); |
||
2690 | av_log(NULL, AV_LOG_ERROR, "You have to specify one input file.\n"); |
||
2691 | av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name); |
||
2692 | ret = AVERROR(EINVAL); |
||
2693 | } else if (input_filename) { |
||
2694 | ret = probe_file(wctx, input_filename); |
||
2695 | if (ret < 0 && do_show_error) |
||
2696 | show_error(wctx, ret); |
||
2697 | } |
||
2698 | |||
2699 | writer_print_section_footer(wctx); |
||
2700 | writer_close(&wctx); |
||
2701 | } |
||
2702 | |||
2703 | end: |
||
2704 | av_freep(&print_format); |
||
2705 | av_freep(&read_intervals); |
||
2706 | |||
2707 | uninit_opts(); |
||
2708 | for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) |
||
2709 | av_dict_free(&(sections[i].entries_to_show)); |
||
2710 | |||
2711 | avformat_network_deinit(); |
||
2712 | |||
2713 | return ret < 0; |
||
2714 | }>>>>>>>>=>>>>>>>>>>>>>>>>>>>>>>%s>%s>%sffprobe%s>?xml>'>>>=>=>=>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |