ffprobe.c 105 KB
Newer Older
Stefano Sabatini's avatar
Stefano Sabatini committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
 * Copyright (c) 2007-2010 Stefano Sabatini
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

21 22 23 24 25
/**
 * @file
 * simple media prober based on the FFmpeg libraries
 */

26
#include "config.h"
27
#include "libavutil/ffversion.h"
28

29 30
#include <string.h>

Stefano Sabatini's avatar
Stefano Sabatini committed
31 32
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
33
#include "libavutil/avassert.h"
34
#include "libavutil/avstring.h"
35
#include "libavutil/bprint.h"
36
#include "libavutil/opt.h"
Stefano Sabatini's avatar
Stefano Sabatini committed
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 45 46
#include "libswscale/swscale.h"
#include "libswresample/swresample.h"
#include "libpostproc/postprocess.h"
Stefano Sabatini's avatar
Stefano Sabatini committed
47 48
#include "cmdutils.h"

49
const char program_name[] = "ffprobe";
Stefano Sabatini's avatar
Stefano Sabatini committed
50 51
const int program_birth_year = 2007;

52
static int do_bitexact = 0;
53 54 55 56
static int do_count_frames = 0;
static int do_count_packets = 0;
static int do_read_frames  = 0;
static int do_read_packets = 0;
57
static int do_show_chapters = 0;
58
static int do_show_error   = 0;
Stefano Sabatini's avatar
Stefano Sabatini committed
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;
Stefano Sabatini's avatar
Stefano Sabatini committed
63
static int do_show_streams = 0;
64
static int do_show_stream_disposition = 0;
65
static int do_show_data    = 0;
66 67
static int do_show_program_version  = 0;
static int do_show_library_versions = 0;
Stefano Sabatini's avatar
Stefano Sabatini committed
68

69 70 71 72 73 74
static int do_show_chapter_tags = 0;
static int do_show_format_tags = 0;
static int do_show_frame_tags = 0;
static int do_show_program_tags = 0;
static int do_show_stream_tags = 0;

Stefano Sabatini's avatar
Stefano Sabatini committed
75 76 77 78
static int show_value_unit              = 0;
static int use_value_prefix             = 0;
static int use_byte_value_binary_prefix = 0;
static int use_value_sexagesimal_format = 0;
79
static int show_private_data            = 1;
Stefano Sabatini's avatar
Stefano Sabatini committed
80

81
static char *print_format;
82
static char *stream_specifier;
83

84 85 86 87 88 89 90 91 92 93 94
typedef struct {
    int id;             ///< identifier
    int64_t start, end; ///< start, end in second/AV_TIME_BASE units
    int has_start, has_end;
    int start_is_offset, end_is_offset;
    int duration_frames;
} ReadInterval;

static ReadInterval *read_intervals;
static int read_intervals_nb = 0;

95 96
/* section structure definition */

97 98
#define SECTION_MAX_NB_CHILDREN 10

99
struct section {
100
    int id;             ///< unique id identifying a section
101 102 103 104
    const char *name;

#define SECTION_FLAG_IS_WRAPPER      1 ///< the section only contains other sections, but has no data at its own level
#define SECTION_FLAG_IS_ARRAY        2 ///< the section contains an array of elements of the same type
105 106
#define SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys.
                                           ///  For these sections the element_name field is mandatory.
107
    int flags;
108
    int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
109
    const char *element_name; ///< name of the contained element, if provided
110 111 112
    const char *unique_name;  ///< unique section name, in case the name is ambiguous
    AVDictionary *entries_to_show;
    int show_all_entries;
113 114 115 116
};

typedef enum {
    SECTION_ID_NONE = -1,
117 118 119
    SECTION_ID_CHAPTER,
    SECTION_ID_CHAPTER_TAGS,
    SECTION_ID_CHAPTERS,
120 121 122 123 124 125 126 127 128 129 130
    SECTION_ID_ERROR,
    SECTION_ID_FORMAT,
    SECTION_ID_FORMAT_TAGS,
    SECTION_ID_FRAME,
    SECTION_ID_FRAMES,
    SECTION_ID_FRAME_TAGS,
    SECTION_ID_LIBRARY_VERSION,
    SECTION_ID_LIBRARY_VERSIONS,
    SECTION_ID_PACKET,
    SECTION_ID_PACKETS,
    SECTION_ID_PACKETS_AND_FRAMES,
131 132 133 134 135 136
    SECTION_ID_PROGRAM_STREAM_DISPOSITION,
    SECTION_ID_PROGRAM_STREAM_TAGS,
    SECTION_ID_PROGRAM,
    SECTION_ID_PROGRAM_STREAMS,
    SECTION_ID_PROGRAM_STREAM,
    SECTION_ID_PROGRAM_TAGS,
137
    SECTION_ID_PROGRAM_VERSION,
138
    SECTION_ID_PROGRAMS,
139 140
    SECTION_ID_ROOT,
    SECTION_ID_STREAM,
141
    SECTION_ID_STREAM_DISPOSITION,
142
    SECTION_ID_STREAMS,
143
    SECTION_ID_STREAM_TAGS,
144
    SECTION_ID_SUBTITLE,
145 146
} SectionID;

147
static struct section sections[] = {
148 149 150
    [SECTION_ID_CHAPTERS] =           { SECTION_ID_CHAPTERS, "chapters", SECTION_FLAG_IS_ARRAY, { SECTION_ID_CHAPTER, -1 } },
    [SECTION_ID_CHAPTER] =            { SECTION_ID_CHAPTER, "chapter", 0, { SECTION_ID_CHAPTER_TAGS, -1 } },
    [SECTION_ID_CHAPTER_TAGS] =       { SECTION_ID_CHAPTER_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "chapter_tags" },
151 152 153
    [SECTION_ID_ERROR] =              { SECTION_ID_ERROR, "error", 0, { -1 } },
    [SECTION_ID_FORMAT] =             { SECTION_ID_FORMAT, "format", 0, { SECTION_ID_FORMAT_TAGS, -1 } },
    [SECTION_ID_FORMAT_TAGS] =        { SECTION_ID_FORMAT_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "format_tags" },
154
    [SECTION_ID_FRAMES] =             { SECTION_ID_FRAMES, "frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_FRAME, SECTION_ID_SUBTITLE, -1 } },
155 156 157 158 159 160 161
    [SECTION_ID_FRAME] =              { SECTION_ID_FRAME, "frame", 0, { SECTION_ID_FRAME_TAGS, -1 } },
    [SECTION_ID_FRAME_TAGS] =         { SECTION_ID_FRAME_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "frame_tags" },
    [SECTION_ID_LIBRARY_VERSIONS] =   { SECTION_ID_LIBRARY_VERSIONS, "library_versions", SECTION_FLAG_IS_ARRAY, { SECTION_ID_LIBRARY_VERSION, -1 } },
    [SECTION_ID_LIBRARY_VERSION] =    { SECTION_ID_LIBRARY_VERSION, "library_version", 0, { -1 } },
    [SECTION_ID_PACKETS] =            { SECTION_ID_PACKETS, "packets", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} },
    [SECTION_ID_PACKETS_AND_FRAMES] = { SECTION_ID_PACKETS_AND_FRAMES, "packets_and_frames", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PACKET, -1} },
    [SECTION_ID_PACKET] =             { SECTION_ID_PACKET, "packet", 0, { -1 } },
162 163 164 165 166 167
    [SECTION_ID_PROGRAM_STREAM_DISPOSITION] = { SECTION_ID_PROGRAM_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "program_stream_disposition" },
    [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" },
    [SECTION_ID_PROGRAM] =                    { SECTION_ID_PROGRAM, "program", 0, { SECTION_ID_PROGRAM_TAGS, SECTION_ID_PROGRAM_STREAMS, -1 } },
    [SECTION_ID_PROGRAM_STREAMS] =            { SECTION_ID_PROGRAM_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM_STREAM, -1 }, .unique_name = "program_streams" },
    [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" },
    [SECTION_ID_PROGRAM_TAGS] =               { SECTION_ID_PROGRAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "program_tags" },
168
    [SECTION_ID_PROGRAM_VERSION] =    { SECTION_ID_PROGRAM_VERSION, "program_version", 0, { -1 } },
169
    [SECTION_ID_PROGRAMS] =                   { SECTION_ID_PROGRAMS, "programs", SECTION_FLAG_IS_ARRAY, { SECTION_ID_PROGRAM, -1 } },
170
    [SECTION_ID_ROOT] =               { SECTION_ID_ROOT, "root", SECTION_FLAG_IS_WRAPPER,
171 172
                                        { SECTION_ID_CHAPTERS, SECTION_ID_FORMAT, SECTION_ID_FRAMES, SECTION_ID_PROGRAMS, SECTION_ID_STREAMS,
                                          SECTION_ID_PACKETS, SECTION_ID_ERROR, SECTION_ID_PROGRAM_VERSION, SECTION_ID_LIBRARY_VERSIONS, -1} },
173 174 175 176
    [SECTION_ID_STREAMS] =            { SECTION_ID_STREAMS, "streams", SECTION_FLAG_IS_ARRAY, { SECTION_ID_STREAM, -1 } },
    [SECTION_ID_STREAM] =             { SECTION_ID_STREAM, "stream", 0, { SECTION_ID_STREAM_DISPOSITION, SECTION_ID_STREAM_TAGS, -1 } },
    [SECTION_ID_STREAM_DISPOSITION] = { SECTION_ID_STREAM_DISPOSITION, "disposition", 0, { -1 }, .unique_name = "stream_disposition" },
    [SECTION_ID_STREAM_TAGS] =        { SECTION_ID_STREAM_TAGS, "tags", SECTION_FLAG_HAS_VARIABLE_FIELDS, { -1 }, .element_name = "tag", .unique_name = "stream_tags" },
177
    [SECTION_ID_SUBTITLE] =           { SECTION_ID_SUBTITLE, "subtitle", 0, { -1 } },
178 179
};

180
static const OptionDef *options;
Stefano Sabatini's avatar
Stefano Sabatini committed
181 182 183

/* FFprobe context */
static const char *input_filename;
184
static AVInputFormat *iformat = NULL;
Stefano Sabatini's avatar
Stefano Sabatini committed
185

186 187
static const char *const binary_unit_prefixes [] = { "", "Ki", "Mi", "Gi", "Ti", "Pi" };
static const char *const decimal_unit_prefixes[] = { "", "K" , "M" , "G" , "T" , "P"  };
Stefano Sabatini's avatar
Stefano Sabatini committed
188

189 190 191 192
static const char unit_second_str[]         = "s"    ;
static const char unit_hertz_str[]          = "Hz"   ;
static const char unit_byte_str[]           = "byte" ;
static const char unit_bit_per_second_str[] = "bit/s";
193

194 195
static uint64_t *nb_streams_packets;
static uint64_t *nb_streams_frames;
196
static int *selected_streams;
Stefano Sabatini's avatar
Stefano Sabatini committed
197

198
static void ffprobe_cleanup(int ret)
199
{
200 201 202
    int i;
    for (i = 0; i < FF_ARRAY_ELEMS(sections); i++)
        av_dict_free(&(sections[i].entries_to_show));
203 204
}

205
struct unit_value {
206
    union { double d; long long int i; } val;
207 208 209 210
    const char *unit;
};

static char *value_string(char *buf, int buf_size, struct unit_value uv)
Stefano Sabatini's avatar
Stefano Sabatini committed
211
{
212
    double vald;
213
    long long int vali;
214 215 216 217 218 219
    int show_float = 0;

    if (uv.unit == unit_second_str) {
        vald = uv.val.d;
        show_float = 1;
    } else {
220
        vald = vali = uv.val.i;
221 222 223
    }

    if (uv.unit == unit_second_str && use_value_sexagesimal_format) {
Stefano Sabatini's avatar
Stefano Sabatini committed
224 225
        double secs;
        int hours, mins;
226
        secs  = vald;
Stefano Sabatini's avatar
Stefano Sabatini committed
227 228 229 230 231
        mins  = (int)secs / 60;
        secs  = secs - mins * 60;
        hours = mins / 60;
        mins %= 60;
        snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs);
232 233
    } else {
        const char *prefix_string = "";
Stefano Sabatini's avatar
Stefano Sabatini committed
234

235
        if (use_value_prefix && vald > 1) {
236 237 238
            long long int index;

            if (uv.unit == unit_byte_str && use_byte_value_binary_prefix) {
239
                index = (long long int) (log2(vald)) / 10;
240
                index = av_clip(index, 0, FF_ARRAY_ELEMS(binary_unit_prefixes) - 1);
241
                vald /= exp2(index * 10);
242 243 244 245 246 247 248
                prefix_string = binary_unit_prefixes[index];
            } else {
                index = (long long int) (log10(vald)) / 3;
                index = av_clip(index, 0, FF_ARRAY_ELEMS(decimal_unit_prefixes) - 1);
                vald /= pow(10, index * 3);
                prefix_string = decimal_unit_prefixes[index];
            }
249
        }
Stefano Sabatini's avatar
Stefano Sabatini committed
250

251
        if (show_float || (use_value_prefix && vald != (long long int)vald))
252
            snprintf(buf, buf_size, "%f", vald);
253
        else
254
            snprintf(buf, buf_size, "%lld", vali);
255
        av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || show_value_unit ? " " : "",
256
                 prefix_string, show_value_unit ? uv.unit : "");
Stefano Sabatini's avatar
Stefano Sabatini committed
257 258 259 260 261
    }

    return buf;
}

262
/* WRITERS API */
263

264
typedef struct WriterContext WriterContext;
265

266
#define WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS 1
267
#define WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER 2
268

269 270 271 272
typedef enum {
    WRITER_STRING_VALIDATION_FAIL,
    WRITER_STRING_VALIDATION_REPLACE,
    WRITER_STRING_VALIDATION_IGNORE,
273
    WRITER_STRING_VALIDATION_NB
274 275
} StringValidation;

276
typedef struct Writer {
277
    const AVClass *priv_class;      ///< private class of the writer, if any
278 279
    int priv_size;                  ///< private size for the writer context
    const char *name;
280

281
    int  (*init)  (WriterContext *wctx);
282 283
    void (*uninit)(WriterContext *wctx);

284 285
    void (*print_section_header)(WriterContext *wctx);
    void (*print_section_footer)(WriterContext *wctx);
286
    void (*print_integer)       (WriterContext *wctx, const char *, long long int);
287
    void (*print_rational)      (WriterContext *wctx, AVRational *q, char *sep);
288
    void (*print_string)        (WriterContext *wctx, const char *, const char *);
289
    int flags;                  ///< a combination or WRITER_FLAG_*
290 291
} Writer;

292 293
#define SECTION_MAX_NB_LEVELS 10

294 295 296 297 298
struct WriterContext {
    const AVClass *class;           ///< class of the writer
    const Writer *writer;           ///< the Writer of which this is an instance
    char *name;                     ///< name of this writer instance
    void *priv;                     ///< private data for use by the filter
299 300 301 302 303 304 305 306 307 308 309

    const struct section *sections; ///< array containing all sections
    int nb_sections;                ///< number of sections

    int level;                      ///< current level, starting from 0

    /** number of the item printed in the given section, starting from 0 */
    unsigned int nb_item[SECTION_MAX_NB_LEVELS];

    /** section per each level */
    const struct section *section[SECTION_MAX_NB_LEVELS];
310 311
    AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
                                                  ///  used by various writers
312

313 314 315
    unsigned int nb_section_packet; ///< number of the packet section in case we are in "packets_and_frames" section
    unsigned int nb_section_frame;  ///< number of the frame  section in case we are in "packets_and_frames" section
    unsigned int nb_section_packet_frame; ///< nb_section_packet or nb_section_frame according if is_packets_and_frames
316 317 318 319

    StringValidation string_validation;
    char *string_validation_replacement;
    unsigned int string_validation_utf8_flags;
320
};
321

322 323 324 325 326 327
static const char *writer_get_name(void *p)
{
    WriterContext *wctx = p;
    return wctx->writer->name;
}

328 329
#define OFFSET(x) offsetof(WriterContext, x)

330 331 332 333 334 335 336 337 338 339
static const AVOption writer_options[] = {
    { "string_validation", "set string validation mode",
      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
    { "sv", "set string validation mode",
      OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=WRITER_STRING_VALIDATION_REPLACE}, 0, WRITER_STRING_VALIDATION_NB-1, .unit = "sv" },
    { "ignore",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_IGNORE},  .unit = "sv" },
    { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_REPLACE}, .unit = "sv" },
    { "fail",    NULL, 0, AV_OPT_TYPE_CONST, {.i64 = WRITER_STRING_VALIDATION_FAIL},    .unit = "sv" },
    { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}},
    { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}},
340
    { NULL }
341 342
};

343 344 345 346 347 348 349 350
static void *writer_child_next(void *obj, void *prev)
{
    WriterContext *ctx = obj;
    if (!prev && ctx->writer && ctx->writer->priv_class && ctx->priv)
        return ctx->priv;
    return NULL;
}

351
static const AVClass writer_class = {
352 353 354 355
    .class_name = "Writer",
    .item_name  = writer_get_name,
    .option     = writer_options,
    .version    = LIBAVUTIL_VERSION_INT,
356
    .child_next = writer_child_next,
357 358
};

359
static void writer_close(WriterContext **wctx)
360
{
361 362
    int i;

363 364
    if (!*wctx)
        return;
365

366 367
    if ((*wctx)->writer->uninit)
        (*wctx)->writer->uninit(*wctx);
368 369
    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
        av_bprint_finalize(&(*wctx)->section_pbuf[i], NULL);
370 371
    if ((*wctx)->writer->priv_class)
        av_opt_free((*wctx)->priv);
372
    av_freep(&((*wctx)->priv));
373
    av_opt_free(*wctx);
374
    av_freep(wctx);
375 376
}

377 378 379 380 381 382 383 384 385
static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size)
{
    int i;
    av_bprintf(bp, "0X");
    for (i = 0; i < ubuf_size; i++)
        av_bprintf(bp, "%02X", ubuf[i]);
}


386 387
static int writer_open(WriterContext **wctx, const Writer *writer, const char *args,
                       const struct section *sections, int nb_sections)
388
{
389
    int i, ret = 0;
390

391
    if (!(*wctx = av_mallocz(sizeof(WriterContext)))) {
392 393
        ret = AVERROR(ENOMEM);
        goto fail;
394 395
    }

396 397 398
    if (!((*wctx)->priv = av_mallocz(writer->priv_size))) {
        ret = AVERROR(ENOMEM);
        goto fail;
399
    }
400

401
    (*wctx)->class = &writer_class;
402
    (*wctx)->writer = writer;
403 404 405
    (*wctx)->level = -1;
    (*wctx)->sections = sections;
    (*wctx)->nb_sections = nb_sections;
406

407 408
    av_opt_set_defaults(*wctx);

409 410 411 412
    if (writer->priv_class) {
        void *priv_ctx = (*wctx)->priv;
        *((const AVClass **)priv_ctx) = writer->priv_class;
        av_opt_set_defaults(priv_ctx);
413
    }
414

415 416 417 418 419 420 421 422
    /* convert options to dictionary */
    if (args) {
        AVDictionary *opts = NULL;
        AVDictionaryEntry *opt = NULL;

        if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
            av_log(*wctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to writer context\n", args);
            av_dict_free(&opts);
423
            goto fail;
424 425 426 427 428 429 430 431 432 433 434 435
        }

        while ((opt = av_dict_get(opts, "", opt, AV_DICT_IGNORE_SUFFIX))) {
            if ((ret = av_opt_set(*wctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
                av_log(*wctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to writer context\n",
                       opt->key, opt->value);
                av_dict_free(&opts);
                goto fail;
            }
        }

        av_dict_free(&opts);
436
    }
437

438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457
    /* validate replace string */
    {
        const uint8_t *p = (*wctx)->string_validation_replacement;
        const uint8_t *endp = p + strlen(p);
        while (*p) {
            const uint8_t *p0 = p;
            int32_t code;
            ret = av_utf8_decode(&code, &p, endp, (*wctx)->string_validation_utf8_flags);
            if (ret < 0) {
                AVBPrint bp;
                av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
                bprint_bytes(&bp, p0, p-p0),
                    av_log(wctx, AV_LOG_ERROR,
                           "Invalid UTF8 sequence %s found in string validation replace '%s'\n",
                           bp.str, (*wctx)->string_validation_replacement);
                return ret;
            }
        }
    }

458 459 460
    for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
        av_bprint_init(&(*wctx)->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);

461
    if ((*wctx)->writer->init)
462
        ret = (*wctx)->writer->init(*wctx);
463 464 465 466 467 468 469
    if (ret < 0)
        goto fail;

    return 0;

fail:
    writer_close(wctx);
470 471 472
    return ret;
}

473
static inline void writer_print_section_header(WriterContext *wctx,
474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492
                                               int section_id)
{
    int parent_section_id;
    wctx->level++;
    av_assert0(wctx->level < SECTION_MAX_NB_LEVELS);
    parent_section_id = wctx->level ?
        (wctx->section[wctx->level-1])->id : SECTION_ID_NONE;

    wctx->nb_item[wctx->level] = 0;
    wctx->section[wctx->level] = &wctx->sections[section_id];

    if (section_id == SECTION_ID_PACKETS_AND_FRAMES) {
        wctx->nb_section_packet = wctx->nb_section_frame =
        wctx->nb_section_packet_frame = 0;
    } else if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) {
        wctx->nb_section_packet_frame = section_id == SECTION_ID_PACKET ?
            wctx->nb_section_packet : wctx->nb_section_frame;
    }

493
    if (wctx->writer->print_section_header)
494
        wctx->writer->print_section_header(wctx);
495 496
}

497
static inline void writer_print_section_footer(WriterContext *wctx)
498
{
499 500 501 502 503 504 505 506 507
    int section_id = wctx->section[wctx->level]->id;
    int parent_section_id = wctx->level ?
        wctx->section[wctx->level-1]->id : SECTION_ID_NONE;

    if (parent_section_id != SECTION_ID_NONE)
        wctx->nb_item[wctx->level-1]++;
    if (parent_section_id == SECTION_ID_PACKETS_AND_FRAMES) {
        if (section_id == SECTION_ID_PACKET) wctx->nb_section_packet++;
        else                                     wctx->nb_section_frame++;
508
    }
509 510 511
    if (wctx->writer->print_section_footer)
        wctx->writer->print_section_footer(wctx);
    wctx->level--;
512
}
513

514
static inline void writer_print_integer(WriterContext *wctx,
515
                                        const char *key, long long int val)
516
{
517 518 519
    const struct section *section = wctx->section[wctx->level];

    if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
520
        wctx->writer->print_integer(wctx, key, val);
521
        wctx->nb_item[wctx->level]++;
522
    }
523 524
}

525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582
static inline int validate_string(WriterContext *wctx, char **dstp, const char *src)
{
    const uint8_t *p, *endp;
    AVBPrint dstbuf;
    int invalid_chars_nb = 0, ret = 0;

    av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);

    endp = src + strlen(src);
    for (p = (uint8_t *)src; *p;) {
        uint32_t code;
        int invalid = 0;
        const uint8_t *p0 = p;

        if (av_utf8_decode(&code, &p, endp, wctx->string_validation_utf8_flags) < 0) {
            AVBPrint bp;
            av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
            bprint_bytes(&bp, p0, p-p0);
            av_log(wctx, AV_LOG_DEBUG,
                   "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
            invalid = 1;
        }

        if (invalid) {
            invalid_chars_nb++;

            switch (wctx->string_validation) {
            case WRITER_STRING_VALIDATION_FAIL:
                av_log(wctx, AV_LOG_ERROR,
                       "Invalid UTF-8 sequence found in string '%s'\n", src);
                ret = AVERROR_INVALIDDATA;
                goto end;
                break;

            case WRITER_STRING_VALIDATION_REPLACE:
                av_bprintf(&dstbuf, "%s", wctx->string_validation_replacement);
                break;
            }
        }

        if (!invalid || wctx->string_validation == WRITER_STRING_VALIDATION_IGNORE)
            av_bprint_append_data(&dstbuf, p0, p-p0);
    }

    if (invalid_chars_nb && wctx->string_validation == WRITER_STRING_VALIDATION_REPLACE) {
        av_log(wctx, AV_LOG_WARNING,
               "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
               invalid_chars_nb, src, wctx->string_validation_replacement);
    }

end:
    av_bprint_finalize(&dstbuf, dstp);
    return ret;
}

#define PRINT_STRING_OPT      1
#define PRINT_STRING_VALIDATE 2

583
static inline int writer_print_string(WriterContext *wctx,
584
                                      const char *key, const char *val, int flags)
585
{
586
    const struct section *section = wctx->section[wctx->level];
587
    int ret = 0;
588

589 590
    if ((flags & PRINT_STRING_OPT)
        && !(wctx->writer->flags & WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS))
591
        return 0;
592 593

    if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612
        if (flags & PRINT_STRING_VALIDATE) {
            char *key1 = NULL, *val1 = NULL;
            ret = validate_string(wctx, &key1, key);
            if (ret < 0) goto end;
            ret = validate_string(wctx, &val1, val);
            if (ret < 0) goto end;
            wctx->writer->print_string(wctx, key1, val1);
        end:
            if (ret < 0) {
                av_log(wctx, AV_LOG_ERROR,
                       "Invalid key=value string combination %s=%s in section %s\n",
                       key, val, section->unique_name);
            }
            av_free(key1);
            av_free(val1);
        } else {
            wctx->writer->print_string(wctx, key, val);
        }

613
        wctx->nb_item[wctx->level]++;
614
    }
615 616

    return ret;
617 618
}

619 620 621 622 623 624 625 626 627
static inline void writer_print_rational(WriterContext *wctx,
                                         const char *key, AVRational q, char sep)
{
    AVBPrint buf;
    av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC);
    av_bprintf(&buf, "%d%c%d", q.num, sep, q.den);
    writer_print_string(wctx, key, buf.str, 0);
}

628
static void writer_print_time(WriterContext *wctx, const char *key,
629
                              int64_t ts, const AVRational *time_base, int is_duration)
630 631 632
{
    char buf[128];

633
    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
634
        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
635 636 637 638 639 640 641 642
    } else {
        double d = ts * av_q2d(*time_base);
        struct unit_value uv;
        uv.val.d = d;
        uv.unit = unit_second_str;
        value_string(buf, sizeof(buf), uv);
        writer_print_string(wctx, key, buf, 0);
    }
643 644
}

645
static void writer_print_ts(WriterContext *wctx, const char *key, int64_t ts, int is_duration)
646
{
647
    if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
648
        writer_print_string(wctx, key, "N/A", PRINT_STRING_OPT);
649
    } else {
650
        writer_print_integer(wctx, key, ts);
651 652 653
    }
}

654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681
static void writer_print_data(WriterContext *wctx, const char *name,
                              uint8_t *data, int size)
{
    AVBPrint bp;
    int offset = 0, l, i;

    av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
    av_bprintf(&bp, "\n");
    while (size) {
        av_bprintf(&bp, "%08x: ", offset);
        l = FFMIN(size, 16);
        for (i = 0; i < l; i++) {
            av_bprintf(&bp, "%02x", data[i]);
            if (i & 1)
                av_bprintf(&bp, " ");
        }
        av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2);
        for (i = 0; i < l; i++)
            av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1);
        av_bprintf(&bp, "\n");
        offset += l;
        data   += l;
        size   -= l;
    }
    writer_print_string(wctx, name, bp.str, 0);
    av_bprint_finalize(&bp, NULL);
}

682 683
#define MAX_REGISTERED_WRITERS_NB 64

684
static const Writer *registered_writers[MAX_REGISTERED_WRITERS_NB + 1];
685

686
static int writer_register(const Writer *writer)
687
{
688 689 690 691 692 693 694
    static int next_registered_writer_idx = 0;

    if (next_registered_writer_idx == MAX_REGISTERED_WRITERS_NB)
        return AVERROR(ENOMEM);

    registered_writers[next_registered_writer_idx++] = writer;
    return 0;
695 696
}

697
static const Writer *writer_get_by_name(const char *name)
698 699 700 701 702 703 704 705 706
{
    int i;

    for (i = 0; registered_writers[i]; i++)
        if (!strcmp(registered_writers[i]->name, name))
            return registered_writers[i];

    return NULL;
}
707

708

709
/* WRITERS */
710

711 712 713 714 715 716
#define DEFINE_WRITER_CLASS(name)                   \
static const char *name##_get_name(void *ctx)       \
{                                                   \
    return #name ;                                  \
}                                                   \
static const AVClass name##_class = {               \
717 718 719
    .class_name = #name,                            \
    .item_name  = name##_get_name,                  \
    .option     = name##_options                    \
720 721
}

722 723
/* Default output */

724 725
typedef struct DefaultContext {
    const AVClass *class;
726
    int nokey;
727
    int noprint_wrappers;
728
    int nested_section[SECTION_MAX_NB_LEVELS];
729 730
} DefaultContext;

731
#undef OFFSET
732 733 734
#define OFFSET(x) offsetof(DefaultContext, x)

static const AVOption default_options[] = {
735 736 737 738
    { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
    { "nw",               "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
    { "nokey",          "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
    { "nk",             "force no key printing",     OFFSET(nokey),          AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
739 740 741
    {NULL},
};

742
DEFINE_WRITER_CLASS(default);
743

744 745 746 747 748
/* lame uppercasing routine, assumes the string is lower case ASCII */
static inline char *upcase_string(char *dst, size_t dst_size, const char *src)
{
    int i;
    for (i = 0; src[i] && i < dst_size-1; i++)
749
        dst[i] = av_toupper(src[i]);
750 751 752 753
    dst[i] = 0;
    return dst;
}

754
static void default_print_section_header(WriterContext *wctx)
755
{
756
    DefaultContext *def = wctx->priv;
757
    char buf[32];
758 759 760 761
    const struct section *section = wctx->section[wctx->level];
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;

762
    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
763 764 765
    if (parent_section &&
        !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) {
        def->nested_section[wctx->level] = 1;
766 767
        av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
                   wctx->section_pbuf[wctx->level-1].str,
768 769 770 771 772
                   upcase_string(buf, sizeof(buf),
                                 av_x_if_null(section->element_name, section->name)));
    }

    if (def->noprint_wrappers || def->nested_section[wctx->level])
773
        return;
774

775 776
    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
        printf("[%s]\n", upcase_string(buf, sizeof(buf), section->name));
777 778
}

779
static void default_print_section_footer(WriterContext *wctx)
780
{
781
    DefaultContext *def = wctx->priv;
782
    const struct section *section = wctx->section[wctx->level];
783 784
    char buf[32];

785
    if (def->noprint_wrappers || def->nested_section[wctx->level])
786 787 788 789
        return;

    if (!(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
        printf("[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
790 791 792 793
}

static void default_print_str(WriterContext *wctx, const char *key, const char *value)
{
794
    DefaultContext *def = wctx->priv;
795

796
    if (!def->nokey)
797
        printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
798
    printf("%s\n", value);
799 800
}

801
static void default_print_int(WriterContext *wctx, const char *key, long long int value)
802
{
803 804 805
    DefaultContext *def = wctx->priv;

    if (!def->nokey)
806
        printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
807
    printf("%lld\n", value);
808 809
}

810
static const Writer default_writer = {
811
    .name                  = "default",
812
    .priv_size             = sizeof(DefaultContext),
813 814 815 816
    .print_section_header  = default_print_section_header,
    .print_section_footer  = default_print_section_footer,
    .print_integer         = default_print_int,
    .print_string          = default_print_str,
817
    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
818
    .priv_class            = &default_class,
819 820
};

821 822 823
/* Compact output */

/**
824
 * Apply C-language-like string escaping.
825
 */
826
static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
827 828 829 830
{
    const char *p;

    for (p = src; *p; p++) {
831
        switch (*p) {
832 833
        case '\b': av_bprintf(dst, "%s", "\\b");  break;
        case '\f': av_bprintf(dst, "%s", "\\f");  break;
834 835 836
        case '\n': av_bprintf(dst, "%s", "\\n");  break;
        case '\r': av_bprintf(dst, "%s", "\\r");  break;
        case '\\': av_bprintf(dst, "%s", "\\\\"); break;
837 838
        default:
            if (*p == sep)
839 840
                av_bprint_chars(dst, '\\', 1);
            av_bprint_chars(dst, *p, 1);
841 842
        }
    }
843
    return dst->str;
844 845 846 847 848
}

/**
 * Quote fields containing special characters, check RFC4180.
 */
849
static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
850
{
851 852
    char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
    int needs_quoting = !!src[strcspn(src, meta_chars)];
853

854
    if (needs_quoting)
855
        av_bprint_chars(dst, '"', 1);
856

857 858
    for (; *src; src++) {
        if (*src == '"')
859
            av_bprint_chars(dst, '"', 1);
860
        av_bprint_chars(dst, *src, 1);
861
    }
862
    if (needs_quoting)
863
        av_bprint_chars(dst, '"', 1);
864
    return dst->str;
865 866
}

867
static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
868 869 870 871 872 873 874 875 876
{
    return src;
}

typedef struct CompactContext {
    const AVClass *class;
    char *item_sep_str;
    char item_sep;
    int nokey;
877
    int print_section;
878
    char *escape_mode_str;
879
    const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
880
    int nested_section[SECTION_MAX_NB_LEVELS];
881 882
    int has_nested_elems[SECTION_MAX_NB_LEVELS];
    int terminate_line[SECTION_MAX_NB_LEVELS];
883 884
} CompactContext;

885
#undef OFFSET
886 887 888 889 890
#define OFFSET(x) offsetof(CompactContext, x)

static const AVOption compact_options[]= {
    {"item_sep", "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str="|"},  CHAR_MIN, CHAR_MAX },
    {"s",        "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str="|"},  CHAR_MIN, CHAR_MAX },
891 892
    {"nokey",    "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.i64=0},    0,        1        },
    {"nk",       "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.i64=0},    0,        1        },
893 894
    {"escape",   "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  CHAR_MIN, CHAR_MAX },
    {"e",        "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"},  CHAR_MIN, CHAR_MAX },
895 896
    {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
    {"p",             "print section name", OFFSET(print_section), AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
897 898 899
    {NULL},
};

900
DEFINE_WRITER_CLASS(compact);
901

902
static av_cold int compact_init(WriterContext *wctx)
903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923
{
    CompactContext *compact = wctx->priv;

    if (strlen(compact->item_sep_str) != 1) {
        av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
               compact->item_sep_str);
        return AVERROR(EINVAL);
    }
    compact->item_sep = compact->item_sep_str[0];

    if      (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str;
    else if (!strcmp(compact->escape_mode_str, "c"   )) compact->escape_str = c_escape_str;
    else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str;
    else {
        av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
        return AVERROR(EINVAL);
    }

    return 0;
}

924
static void compact_print_section_header(WriterContext *wctx)
925 926
{
    CompactContext *compact = wctx->priv;
927
    const struct section *section = wctx->section[wctx->level];
928 929
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;
930 931
    compact->terminate_line[wctx->level] = 1;
    compact->has_nested_elems[wctx->level] = 0;
932

933
    av_bprint_clear(&wctx->section_pbuf[wctx->level]);
934
    if (!(section->flags & SECTION_FLAG_IS_ARRAY) && parent_section &&
935 936
        !(parent_section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY))) {
        compact->nested_section[wctx->level] = 1;
937
        compact->has_nested_elems[wctx->level-1] = 1;
938 939
        av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
                   wctx->section_pbuf[wctx->level-1].str,
940
                   (char *)av_x_if_null(section->element_name, section->name));
941
        wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
942 943 944 945 946 947 948 949 950 951
    } else {
        if (parent_section && compact->has_nested_elems[wctx->level-1] &&
            (section->flags & SECTION_FLAG_IS_ARRAY)) {
            compact->terminate_line[wctx->level-1] = 0;
            printf("\n");
        }
        if (compact->print_section &&
            !(section->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
            printf("%s%c", section->name, compact->item_sep);
    }
952 953
}

954
static void compact_print_section_footer(WriterContext *wctx)
955
{
956
    CompactContext *compact = wctx->priv;
957

958
    if (!compact->nested_section[wctx->level] &&
959
        compact->terminate_line[wctx->level] &&
960
        !(wctx->section[wctx->level]->flags & (SECTION_FLAG_IS_WRAPPER|SECTION_FLAG_IS_ARRAY)))
961
        printf("\n");
962 963 964 965 966
}

static void compact_print_str(WriterContext *wctx, const char *key, const char *value)
{
    CompactContext *compact = wctx->priv;
967
    AVBPrint buf;
968

969
    if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep);
970
    if (!compact->nokey)
971
        printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
972 973 974
    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
    printf("%s", compact->escape_str(&buf, value, compact->item_sep, wctx));
    av_bprint_finalize(&buf, NULL);
975 976
}

977
static void compact_print_int(WriterContext *wctx, const char *key, long long int value)
978 979 980
{
    CompactContext *compact = wctx->priv;

981
    if (wctx->nb_item[wctx->level]) printf("%c", compact->item_sep);
982
    if (!compact->nokey)
983
        printf("%s%s=", wctx->section_pbuf[wctx->level].str, key);
984
    printf("%lld", value);
985 986
}

987
static const Writer compact_writer = {
988 989 990 991 992 993 994 995
    .name                 = "compact",
    .priv_size            = sizeof(CompactContext),
    .init                 = compact_init,
    .print_section_header = compact_print_section_header,
    .print_section_footer = compact_print_section_footer,
    .print_integer        = compact_print_int,
    .print_string         = compact_print_str,
    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
996
    .priv_class           = &compact_class,
997 998
};

999 1000
/* CSV output */

1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016
#undef OFFSET
#define OFFSET(x) offsetof(CompactContext, x)

static const AVOption csv_options[] = {
    {"item_sep", "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str=","},  CHAR_MIN, CHAR_MAX },
    {"s",        "set item separator",    OFFSET(item_sep_str),    AV_OPT_TYPE_STRING, {.str=","},  CHAR_MIN, CHAR_MAX },
    {"nokey",    "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
    {"nk",       "force no key printing", OFFSET(nokey),           AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
    {"escape",   "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX },
    {"e",        "set escape mode",       OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, CHAR_MIN, CHAR_MAX },
    {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
    {"p",             "print section name", OFFSET(print_section), AV_OPT_TYPE_INT,    {.i64=1},    0,        1        },
    {NULL},
};

DEFINE_WRITER_CLASS(csv);
1017

1018
static const Writer csv_writer = {
1019 1020
    .name                 = "csv",
    .priv_size            = sizeof(CompactContext),
1021
    .init                 = compact_init,
1022 1023 1024 1025 1026
    .print_section_header = compact_print_section_header,
    .print_section_footer = compact_print_section_footer,
    .print_integer        = compact_print_int,
    .print_string         = compact_print_str,
    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS,
1027
    .priv_class           = &csv_class,
1028 1029
};

1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044
/* Flat output */

typedef struct FlatContext {
    const AVClass *class;
    const char *sep_str;
    char sep;
    int hierarchical;
} FlatContext;

#undef OFFSET
#define OFFSET(x) offsetof(FlatContext, x)

static const AVOption flat_options[]= {
    {"sep_char", "set separator",    OFFSET(sep_str),    AV_OPT_TYPE_STRING, {.str="."},  CHAR_MIN, CHAR_MAX },
    {"s",        "set separator",    OFFSET(sep_str),    AV_OPT_TYPE_STRING, {.str="."},  CHAR_MIN, CHAR_MAX },
1045 1046
    {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 },
    {"h",           "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 },
1047 1048 1049
    {NULL},
};

1050
DEFINE_WRITER_CLASS(flat);
1051

1052
static av_cold int flat_init(WriterContext *wctx)
1053 1054 1055 1056 1057 1058 1059 1060 1061
{
    FlatContext *flat = wctx->priv;

    if (strlen(flat->sep_str) != 1) {
        av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
               flat->sep_str);
        return AVERROR(EINVAL);
    }
    flat->sep = flat->sep_str[0];
1062

1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
    return 0;
}

static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep)
{
    const char *p;

    for (p = src; *p; p++) {
        if (!((*p >= '0' && *p <= '9') ||
              (*p >= 'a' && *p <= 'z') ||
              (*p >= 'A' && *p <= 'Z')))
            av_bprint_chars(dst, '_', 1);
        else
            av_bprint_chars(dst, *p, 1);
    }
    return dst->str;
}

static const char *flat_escape_value_str(AVBPrint *dst, const char *src)
{
    const char *p;

    for (p = src; *p; p++) {
        switch (*p) {
        case '\n': av_bprintf(dst, "%s", "\\n");  break;
        case '\r': av_bprintf(dst, "%s", "\\r");  break;
        case '\\': av_bprintf(dst, "%s", "\\\\"); break;
        case '"':  av_bprintf(dst, "%s", "\\\""); break;
1091 1092
        case '`':  av_bprintf(dst, "%s", "\\`");  break;
        case '$':  av_bprintf(dst, "%s", "\\$");  break;
1093 1094 1095 1096 1097 1098
        default:   av_bprint_chars(dst, *p, 1);   break;
        }
    }
    return dst->str;
}

1099
static void flat_print_section_header(WriterContext *wctx)
1100 1101
{
    FlatContext *flat = wctx->priv;
1102
    AVBPrint *buf = &wctx->section_pbuf[wctx->level];
1103 1104 1105
    const struct section *section = wctx->section[wctx->level];
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;
1106

1107 1108
    /* build section header */
    av_bprint_clear(buf);
1109 1110
    if (!parent_section)
        return;
1111
    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
1112

1113 1114 1115
    if (flat->hierarchical ||
        !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) {
        av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str);
1116

1117 1118 1119 1120 1121
        if (parent_section->flags & SECTION_FLAG_IS_ARRAY) {
            int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ?
                wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1];
            av_bprintf(buf, "%d%s", n, flat->sep_str);
        }
1122
    }
1123 1124 1125 1126
}

static void flat_print_int(WriterContext *wctx, const char *key, long long int value)
{
1127
    printf("%s%s=%lld\n", wctx->section_pbuf[wctx->level].str, key, value);
1128 1129 1130 1131 1132 1133 1134
}

static void flat_print_str(WriterContext *wctx, const char *key, const char *value)
{
    FlatContext *flat = wctx->priv;
    AVBPrint buf;

1135
    printf("%s", wctx->section_pbuf[wctx->level].str);
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150
    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
    printf("%s=", flat_escape_key_str(&buf, key, flat->sep));
    av_bprint_clear(&buf);
    printf("\"%s\"\n", flat_escape_value_str(&buf, value));
    av_bprint_finalize(&buf, NULL);
}

static const Writer flat_writer = {
    .name                  = "flat",
    .priv_size             = sizeof(FlatContext),
    .init                  = flat_init,
    .print_section_header  = flat_print_section_header,
    .print_integer         = flat_print_int,
    .print_string          = flat_print_str,
    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
1151
    .priv_class            = &flat_class,
1152 1153
};

1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164
/* INI format output */

typedef struct {
    const AVClass *class;
    int hierarchical;
} INIContext;

#undef OFFSET
#define OFFSET(x) offsetof(INIContext, x)

static const AVOption ini_options[] = {
1165 1166
    {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 },
    {"h",           "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_INT, {.i64=1}, 0, 1 },
1167 1168 1169
    {NULL},
};

1170
DEFINE_WRITER_CLASS(ini);
1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198

static char *ini_escape_str(AVBPrint *dst, const char *src)
{
    int i = 0;
    char c = 0;

    while (c = src[i++]) {
        switch (c) {
        case '\b': av_bprintf(dst, "%s", "\\b"); break;
        case '\f': av_bprintf(dst, "%s", "\\f"); break;
        case '\n': av_bprintf(dst, "%s", "\\n"); break;
        case '\r': av_bprintf(dst, "%s", "\\r"); break;
        case '\t': av_bprintf(dst, "%s", "\\t"); break;
        case '\\':
        case '#' :
        case '=' :
        case ':' : av_bprint_chars(dst, '\\', 1);
        default:
            if ((unsigned char)c < 32)
                av_bprintf(dst, "\\x00%02x", c & 0xff);
            else
                av_bprint_chars(dst, c, 1);
            break;
        }
    }
    return dst->str;
}

1199
static void ini_print_section_header(WriterContext *wctx)
1200 1201
{
    INIContext *ini = wctx->priv;
1202
    AVBPrint *buf = &wctx->section_pbuf[wctx->level];
1203 1204 1205
    const struct section *section = wctx->section[wctx->level];
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;
1206

1207 1208
    av_bprint_clear(buf);
    if (!parent_section) {
1209 1210 1211
        printf("# ffprobe output\n\n");
        return;
    }
1212

1213
    if (wctx->nb_item[wctx->level-1])
1214 1215
        printf("\n");

1216
    av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
1217 1218 1219
    if (ini->hierarchical ||
        !(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER))) {
        av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name);
1220

1221 1222 1223 1224 1225
        if (parent_section->flags & SECTION_FLAG_IS_ARRAY) {
            int n = parent_section->id == SECTION_ID_PACKETS_AND_FRAMES ?
                wctx->nb_section_packet_frame : wctx->nb_item[wctx->level-1];
            av_bprintf(buf, ".%d", n);
        }
1226
    }
1227

1228
    if (!(section->flags & (SECTION_FLAG_IS_ARRAY|SECTION_FLAG_IS_WRAPPER)))
1229
        printf("[%s]\n", buf->str);
1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254
}

static void ini_print_str(WriterContext *wctx, const char *key, const char *value)
{
    AVBPrint buf;

    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
    printf("%s=", ini_escape_str(&buf, key));
    av_bprint_clear(&buf);
    printf("%s\n", ini_escape_str(&buf, value));
    av_bprint_finalize(&buf, NULL);
}

static void ini_print_int(WriterContext *wctx, const char *key, long long int value)
{
    printf("%s=%lld\n", key, value);
}

static const Writer ini_writer = {
    .name                  = "ini",
    .priv_size             = sizeof(INIContext),
    .print_section_header  = ini_print_section_header,
    .print_integer         = ini_print_int,
    .print_string          = ini_print_str,
    .flags = WRITER_FLAG_DISPLAY_OPTIONAL_FIELDS|WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
1255
    .priv_class            = &ini_class,
1256 1257
};

1258 1259 1260
/* JSON output */

typedef struct {
1261
    const AVClass *class;
1262
    int indent_level;
1263 1264
    int compact;
    const char *item_sep, *item_start_end;
1265 1266
} JSONContext;

1267 1268 1269 1270
#undef OFFSET
#define OFFSET(x) offsetof(JSONContext, x)

static const AVOption json_options[]= {
1271 1272
    { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
    { "c",       "enable compact output", OFFSET(compact), AV_OPT_TYPE_INT, {.i64=0}, 0, 1 },
1273 1274 1275
    { NULL }
};

1276
DEFINE_WRITER_CLASS(json);
1277

1278
static av_cold int json_init(WriterContext *wctx)
1279 1280
{
    JSONContext *json = wctx->priv;
1281 1282 1283

    json->item_sep       = json->compact ? ", " : ",\n";
    json->item_start_end = json->compact ? " "  : "\n";
1284 1285 1286 1287

    return 0;
}

1288
static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
1289 1290 1291
{
    static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0};
    static const char json_subst[]  = {'"', '\\',  'b',  'f',  'n',  'r',  't', 0};
1292 1293 1294 1295 1296
    const char *p;

    for (p = src; *p; p++) {
        char *s = strchr(json_escape, *p);
        if (s) {
1297 1298
            av_bprint_chars(dst, '\\', 1);
            av_bprint_chars(dst, json_subst[s - json_escape], 1);
1299
        } else if ((unsigned char)*p < 32) {
1300
            av_bprintf(dst, "\\u00%02x", *p & 0xff);
1301
        } else {
1302
            av_bprint_chars(dst, *p, 1);
1303 1304
        }
    }
1305
    return dst->str;
1306 1307
}

1308 1309
#define JSON_INDENT() printf("%*c", json->indent_level * 4, ' ')

1310
static void json_print_section_header(WriterContext *wctx)
1311 1312
{
    JSONContext *json = wctx->priv;
1313
    AVBPrint buf;
1314 1315 1316
    const struct section *section = wctx->section[wctx->level];
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;
1317

1318 1319 1320 1321 1322 1323 1324
    if (wctx->level && wctx->nb_item[wctx->level-1])
        printf(",\n");

    if (section->flags & SECTION_FLAG_IS_WRAPPER) {
        printf("{\n");
        json->indent_level++;
    } else {
1325
        av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
1326 1327 1328
        json_escape_str(&buf, section->name, wctx);
        JSON_INDENT();

1329
        json->indent_level++;
1330 1331
        if (section->flags & SECTION_FLAG_IS_ARRAY) {
            printf("\"%s\": [\n", buf.str);
1332
        } else if (parent_section && !(parent_section->flags & SECTION_FLAG_IS_ARRAY)) {
1333 1334 1335 1336 1337
            printf("\"%s\": {%s", buf.str, json->item_start_end);
        } else {
            printf("{%s", json->item_start_end);

            /* this is required so the parser can distinguish between packets and frames */
1338
            if (parent_section && parent_section->id == SECTION_ID_PACKETS_AND_FRAMES) {
1339 1340 1341 1342 1343 1344
                if (!json->compact)
                    JSON_INDENT();
                printf("\"type\": \"%s\"%s", section->name, json->item_sep);
            }
        }
        av_bprint_finalize(&buf, NULL);
1345
    }
1346 1347
}

1348
static void json_print_section_footer(WriterContext *wctx)
1349 1350
{
    JSONContext *json = wctx->priv;
1351
    const struct section *section = wctx->section[wctx->level];
1352

1353 1354 1355 1356
    if (wctx->level == 0) {
        json->indent_level--;
        printf("\n}\n");
    } else if (section->flags & SECTION_FLAG_IS_ARRAY) {
1357
        printf("\n");
1358
        json->indent_level--;
1359 1360
        JSON_INDENT();
        printf("]");
1361 1362 1363
    } else {
        printf("%s", json->item_start_end);
        json->indent_level--;
1364 1365
        if (!json->compact)
            JSON_INDENT();
1366
        printf("}");
1367
    }
1368 1369 1370
}

static inline void json_print_item_str(WriterContext *wctx,
1371
                                       const char *key, const char *value)
1372
{
1373
    AVBPrint buf;
1374

1375 1376
    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
    printf("\"%s\":", json_escape_str(&buf, key,   wctx));
1377
    av_bprint_clear(&buf);
1378 1379
    printf(" \"%s\"", json_escape_str(&buf, value, wctx));
    av_bprint_finalize(&buf, NULL);
1380 1381 1382 1383
}

static void json_print_str(WriterContext *wctx, const char *key, const char *value)
{
1384 1385
    JSONContext *json = wctx->priv;

1386 1387
    if (wctx->nb_item[wctx->level])
        printf("%s", json->item_sep);
1388 1389
    if (!json->compact)
        JSON_INDENT();
1390
    json_print_item_str(wctx, key, value);
1391 1392
}

1393
static void json_print_int(WriterContext *wctx, const char *key, long long int value)
1394
{
1395
    JSONContext *json = wctx->priv;
1396
    AVBPrint buf;
1397

1398 1399
    if (wctx->nb_item[wctx->level])
        printf("%s", json->item_sep);
1400 1401
    if (!json->compact)
        JSON_INDENT();
1402 1403 1404 1405

    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
    printf("\"%s\": %lld", json_escape_str(&buf, key, wctx), value);
    av_bprint_finalize(&buf, NULL);
1406
}
1407

1408
static const Writer json_writer = {
1409 1410
    .name                 = "json",
    .priv_size            = sizeof(JSONContext),
1411
    .init                 = json_init,
1412 1413 1414 1415
    .print_section_header = json_print_section_header,
    .print_section_footer = json_print_section_footer,
    .print_integer        = json_print_int,
    .print_string         = json_print_str,
1416
    .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
1417
    .priv_class           = &json_class,
1418 1419
};

1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433
/* XML output */

typedef struct {
    const AVClass *class;
    int within_tag;
    int indent_level;
    int fully_qualified;
    int xsd_strict;
} XMLContext;

#undef OFFSET
#define OFFSET(x) offsetof(XMLContext, x)

static const AVOption xml_options[] = {
1434 1435 1436 1437
    {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.i64=0},  0, 1 },
    {"q",               "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_INT, {.i64=0},  0, 1 },
    {"xsd_strict",      "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_INT, {.i64=0},  0, 1 },
    {"x",               "ensure that the output is XSD compliant",         OFFSET(xsd_strict),      AV_OPT_TYPE_INT, {.i64=0},  0, 1 },
1438 1439 1440
    {NULL},
};

1441
DEFINE_WRITER_CLASS(xml);
1442

1443
static av_cold int xml_init(WriterContext *wctx)
1444 1445 1446 1447 1448 1449 1450 1451 1452 1453
{
    XMLContext *xml = wctx->priv;

    if (xml->xsd_strict) {
        xml->fully_qualified = 1;
#define CHECK_COMPLIANCE(opt, opt_name)                                 \
        if (opt) {                                                      \
            av_log(wctx, AV_LOG_ERROR,                                  \
                   "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \
                   "You need to disable such option with '-no%s'\n", opt_name, opt_name); \
1454
            return AVERROR(EINVAL);                                     \
1455 1456 1457 1458
        }
        CHECK_COMPLIANCE(show_private_data, "private");
        CHECK_COMPLIANCE(show_value_unit,   "unit");
        CHECK_COMPLIANCE(use_value_prefix,  "prefix");
1459 1460 1461 1462 1463 1464 1465

        if (do_show_frames && do_show_packets) {
            av_log(wctx, AV_LOG_ERROR,
                   "Interleaved frames and packets are not allowed in XSD. "
                   "Select only one between the -show_frames and the -show_packets options.\n");
            return AVERROR(EINVAL);
        }
1466 1467 1468 1469 1470
    }

    return 0;
}

1471
static const char *xml_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
1472 1473 1474
{
    const char *p;

1475
    for (p = src; *p; p++) {
1476
        switch (*p) {
1477 1478 1479
        case '&' : av_bprintf(dst, "%s", "&amp;");  break;
        case '<' : av_bprintf(dst, "%s", "&lt;");   break;
        case '>' : av_bprintf(dst, "%s", "&gt;");   break;
1480
        case '"' : av_bprintf(dst, "%s", "&quot;"); break;
1481 1482
        case '\'': av_bprintf(dst, "%s", "&apos;"); break;
        default: av_bprint_chars(dst, *p, 1);
1483 1484 1485
        }
    }

1486
    return dst->str;
1487 1488
}

1489
#define XML_INDENT() printf("%*c", xml->indent_level * 4, ' ')
1490

1491
static void xml_print_section_header(WriterContext *wctx)
1492 1493
{
    XMLContext *xml = wctx->priv;
1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507
    const struct section *section = wctx->section[wctx->level];
    const struct section *parent_section = wctx->level ?
        wctx->section[wctx->level-1] : NULL;

    if (wctx->level == 0) {
        const char *qual = " xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' "
            "xmlns:ffprobe='http://www.ffmpeg.org/schema/ffprobe' "
            "xsi:schemaLocation='http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd'";

        printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        printf("<%sffprobe%s>\n",
               xml->fully_qualified ? "ffprobe:" : "",
               xml->fully_qualified ? qual : "");
        return;
1508 1509
    }

1510 1511 1512
    if (xml->within_tag) {
        xml->within_tag = 0;
        printf(">\n");
1513
    }
1514
    if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
1515 1516
        xml->indent_level++;
    } else {
1517 1518 1519
        if (parent_section && (parent_section->flags & SECTION_FLAG_IS_WRAPPER) &&
            wctx->level && wctx->nb_item[wctx->level-1])
            printf("\n");
1520
        xml->indent_level++;
1521

1522 1523 1524 1525 1526 1527 1528
        if (section->flags & SECTION_FLAG_IS_ARRAY) {
            XML_INDENT(); printf("<%s>\n", section->name);
        } else {
            XML_INDENT(); printf("<%s ", section->name);
            xml->within_tag = 1;
        }
    }
1529 1530
}

1531
static void xml_print_section_footer(WriterContext *wctx)
1532 1533
{
    XMLContext *xml = wctx->priv;
1534
    const struct section *section = wctx->section[wctx->level];
1535

1536 1537 1538 1539
    if (wctx->level == 0) {
        printf("</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
    } else if (xml->within_tag) {
        xml->within_tag = 0;
1540
        printf("/>\n");
1541
        xml->indent_level--;
1542
    } else if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
1543 1544 1545 1546
        xml->indent_level--;
    } else {
        XML_INDENT(); printf("</%s>\n", section->name);
        xml->indent_level--;
1547 1548 1549 1550 1551
    }
}

static void xml_print_str(WriterContext *wctx, const char *key, const char *value)
{
1552
    AVBPrint buf;
1553 1554
    XMLContext *xml = wctx->priv;
    const struct section *section = wctx->section[wctx->level];
1555

1556
    av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
1557

1558
    if (section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS) {
1559
        XML_INDENT();
1560 1561
        printf("<%s key=\"%s\"",
               section->element_name, xml_escape_str(&buf, key, wctx));
1562 1563 1564 1565 1566 1567 1568 1569
        av_bprint_clear(&buf);
        printf(" value=\"%s\"/>\n", xml_escape_str(&buf, value, wctx));
    } else {
        if (wctx->nb_item[wctx->level])
            printf(" ");
        printf("%s=\"%s\"", key, xml_escape_str(&buf, value, wctx));
    }

1570
    av_bprint_finalize(&buf, NULL);
1571 1572 1573 1574
}

static void xml_print_int(WriterContext *wctx, const char *key, long long int value)
{
1575
    if (wctx->nb_item[wctx->level])
1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587
        printf(" ");
    printf("%s=\"%lld\"", key, value);
}

static Writer xml_writer = {
    .name                 = "xml",
    .priv_size            = sizeof(XMLContext),
    .init                 = xml_init,
    .print_section_header = xml_print_section_header,
    .print_section_footer = xml_print_section_footer,
    .print_integer        = xml_print_int,
    .print_string         = xml_print_str,
1588
    .flags = WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER,
1589
    .priv_class           = &xml_class,
1590 1591
};

1592 1593 1594 1595 1596 1597 1598 1599 1600
static void writer_register_all(void)
{
    static int initialized;

    if (initialized)
        return;
    initialized = 1;

    writer_register(&default_writer);
1601
    writer_register(&compact_writer);
1602
    writer_register(&csv_writer);
1603
    writer_register(&flat_writer);
1604
    writer_register(&ini_writer);
1605
    writer_register(&json_writer);
1606
    writer_register(&xml_writer);
1607 1608 1609
}

#define print_fmt(k, f, ...) do {              \
1610 1611 1612
    av_bprint_clear(&pbuf);                    \
    av_bprintf(&pbuf, f, __VA_ARGS__);         \
    writer_print_string(w, k, pbuf.str, 0);    \
1613 1614 1615
} while (0)

#define print_int(k, v)         writer_print_integer(w, k, v)
1616
#define print_q(k, v, s)        writer_print_rational(w, k, v, s)
1617
#define print_str(k, v)         writer_print_string(w, k, v, 0)
1618 1619
#define print_str_opt(k, v)     writer_print_string(w, k, v, PRINT_STRING_OPT)
#define print_str_validate(k, v) writer_print_string(w, k, v, PRINT_STRING_VALIDATE)
1620 1621 1622 1623
#define print_time(k, v, tb)    writer_print_time(w, k, v, tb, 0)
#define print_ts(k, v)          writer_print_ts(w, k, v, 0)
#define print_duration_time(k, v, tb) writer_print_time(w, k, v, tb, 1)
#define print_duration_ts(k, v)       writer_print_ts(w, k, v, 1)
1624 1625 1626 1627 1628 1629 1630
#define print_val(k, v, u) do {                                     \
    struct unit_value uv;                                           \
    uv.val.i = v;                                                   \
    uv.unit = u;                                                    \
    writer_print_string(w, k, value_string(val_str, sizeof(val_str), uv), 0); \
} while (0)

1631 1632
#define print_section_header(s) writer_print_section_header(w, s)
#define print_section_footer(s) writer_print_section_footer(w, s)
1633

1634
static inline int show_tags(WriterContext *w, AVDictionary *tags, int section_id)
1635 1636
{
    AVDictionaryEntry *tag = NULL;
1637
    int ret = 0;
1638 1639

    if (!tags)
1640
        return 0;
1641
    writer_print_section_header(w, section_id);
1642 1643

    while ((tag = av_dict_get(tags, "", tag, AV_DICT_IGNORE_SUFFIX))) {
1644
        if ((ret = print_str_validate(tag->key, tag->value)) < 0)
1645 1646
            break;
    }
1647
    writer_print_section_footer(w);
1648 1649

    return ret;
1650
}
1651 1652

static void show_packet(WriterContext *w, AVFormatContext *fmt_ctx, AVPacket *pkt, int packet_idx)
1653 1654 1655
{
    char val_str[128];
    AVStream *st = fmt_ctx->streams[pkt->stream_index];
1656
    AVBPrint pbuf;
1657
    const char *s;
1658

1659 1660
    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);

1661 1662
    writer_print_section_header(w, SECTION_ID_PACKET);

1663 1664 1665
    s = av_get_media_type_string(st->codec->codec_type);
    if (s) print_str    ("codec_type", s);
    else   print_str_opt("codec_type", "unknown");
1666
    print_int("stream_index",     pkt->stream_index);
1667 1668 1669 1670
    print_ts  ("pts",             pkt->pts);
    print_time("pts_time",        pkt->pts, &st->time_base);
    print_ts  ("dts",             pkt->dts);
    print_time("dts_time",        pkt->dts, &st->time_base);
1671 1672
    print_duration_ts("duration",        pkt->duration);
    print_duration_time("duration_time", pkt->duration, &st->time_base);
1673 1674
    print_duration_ts("convergence_duration", pkt->convergence_duration);
    print_duration_time("convergence_duration_time", pkt->convergence_duration, &st->time_base);
1675
    print_val("size",             pkt->size, unit_byte_str);
1676 1677
    if (pkt->pos != -1) print_fmt    ("pos", "%"PRId64, pkt->pos);
    else                print_str_opt("pos", "N/A");
1678
    print_fmt("flags", "%c",      pkt->flags & AV_PKT_FLAG_KEY ? 'K' : '_');
1679 1680
    if (do_show_data)
        writer_print_data(w, "data", pkt->data, pkt->size);
1681
    writer_print_section_footer(w);
1682

1683
    av_bprint_finalize(&pbuf, NULL);
1684
    fflush(stdout);
1685 1686
}

1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709
static void show_subtitle(WriterContext *w, AVSubtitle *sub, AVStream *stream,
                          AVFormatContext *fmt_ctx)
{
    AVBPrint pbuf;

    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);

    writer_print_section_header(w, SECTION_ID_SUBTITLE);

    print_str ("media_type",         "subtitle");
    print_ts  ("pts",                 sub->pts);
    print_time("pts_time",            sub->pts, &AV_TIME_BASE_Q);
    print_int ("format",              sub->format);
    print_int ("start_display_time",  sub->start_display_time);
    print_int ("end_display_time",    sub->end_display_time);
    print_int ("num_rects",           sub->num_rects);

    writer_print_section_footer(w);

    av_bprint_finalize(&pbuf, NULL);
    fflush(stdout);
}

1710 1711
static void show_frame(WriterContext *w, AVFrame *frame, AVStream *stream,
                       AVFormatContext *fmt_ctx)
1712
{
1713
    AVBPrint pbuf;
1714 1715
    const char *s;

1716 1717
    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);

1718
    writer_print_section_header(w, SECTION_ID_FRAME);
1719 1720 1721 1722 1723 1724 1725 1726 1727

    s = av_get_media_type_string(stream->codec->codec_type);
    if (s) print_str    ("media_type", s);
    else   print_str_opt("media_type", "unknown");
    print_int("key_frame",              frame->key_frame);
    print_ts  ("pkt_pts",               frame->pkt_pts);
    print_time("pkt_pts_time",          frame->pkt_pts, &stream->time_base);
    print_ts  ("pkt_dts",               frame->pkt_dts);
    print_time("pkt_dts_time",          frame->pkt_dts, &stream->time_base);
1728 1729
    print_ts  ("best_effort_timestamp", av_frame_get_best_effort_timestamp(frame));
    print_time("best_effort_timestamp_time", av_frame_get_best_effort_timestamp(frame), &stream->time_base);
1730 1731 1732
    print_duration_ts  ("pkt_duration",      av_frame_get_pkt_duration(frame));
    print_duration_time("pkt_duration_time", av_frame_get_pkt_duration(frame), &stream->time_base);
    if (av_frame_get_pkt_pos (frame) != -1) print_fmt    ("pkt_pos", "%"PRId64, av_frame_get_pkt_pos(frame));
1733
    else                      print_str_opt("pkt_pos", "N/A");
1734
    if (av_frame_get_pkt_size(frame) != -1) print_fmt    ("pkt_size", "%d", av_frame_get_pkt_size(frame));
1735
    else                       print_str_opt("pkt_size", "N/A");
1736 1737

    switch (stream->codec->codec_type) {
1738 1739
        AVRational sar;

1740
    case AVMEDIA_TYPE_VIDEO:
1741 1742 1743 1744 1745
        print_int("width",                  frame->width);
        print_int("height",                 frame->height);
        s = av_get_pix_fmt_name(frame->format);
        if (s) print_str    ("pix_fmt", s);
        else   print_str_opt("pix_fmt", "unknown");
1746 1747 1748
        sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, frame);
        if (sar.num) {
            print_q("sample_aspect_ratio", sar, ':');
1749 1750 1751 1752 1753 1754 1755 1756 1757 1758
        } else {
            print_str_opt("sample_aspect_ratio", "N/A");
        }
        print_fmt("pict_type",              "%c", av_get_picture_type_char(frame->pict_type));
        print_int("coded_picture_number",   frame->coded_picture_number);
        print_int("display_picture_number", frame->display_picture_number);
        print_int("interlaced_frame",       frame->interlaced_frame);
        print_int("top_field_first",        frame->top_field_first);
        print_int("repeat_pict",            frame->repeat_pict);
        break;
1759 1760 1761 1762 1763 1764

    case AVMEDIA_TYPE_AUDIO:
        s = av_get_sample_fmt_name(frame->format);
        if (s) print_str    ("sample_fmt", s);
        else   print_str_opt("sample_fmt", "unknown");
        print_int("nb_samples",         frame->nb_samples);
1765 1766 1767 1768 1769 1770 1771 1772
        print_int("channels", av_frame_get_channels(frame));
        if (av_frame_get_channel_layout(frame)) {
            av_bprint_clear(&pbuf);
            av_bprint_channel_layout(&pbuf, av_frame_get_channels(frame),
                                     av_frame_get_channel_layout(frame));
            print_str    ("channel_layout", pbuf.str);
        } else
            print_str_opt("channel_layout", "unknown");
1773 1774
        break;
    }
1775 1776
    if (do_show_frame_tags)
        show_tags(w, av_frame_get_metadata(frame), SECTION_ID_FRAME_TAGS);
1777

1778
    writer_print_section_footer(w);
1779

1780
    av_bprint_finalize(&pbuf, NULL);
1781 1782 1783
    fflush(stdout);
}

1784 1785 1786
static av_always_inline int process_frame(WriterContext *w,
                                          AVFormatContext *fmt_ctx,
                                          AVFrame *frame, AVPacket *pkt)
1787 1788
{
    AVCodecContext *dec_ctx = fmt_ctx->streams[pkt->stream_index]->codec;
1789
    AVSubtitle sub;
1790
    int ret = 0, got_frame = 0;
1791

1792
    if (dec_ctx->codec) {
1793 1794
        switch (dec_ctx->codec_type) {
        case AVMEDIA_TYPE_VIDEO:
1795
            ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, pkt);
1796
            break;
1797

1798
        case AVMEDIA_TYPE_AUDIO:
1799
            ret = avcodec_decode_audio4(dec_ctx, frame, &got_frame, pkt);
1800
            break;
1801 1802 1803 1804

        case AVMEDIA_TYPE_SUBTITLE:
            ret = avcodec_decode_subtitle2(dec_ctx, &sub, &got_frame, pkt);
            break;
1805
        }
1806
    }
1807

1808 1809 1810 1811 1812 1813
    if (ret < 0)
        return ret;
    ret = FFMIN(ret, pkt->size); /* guard against bogus return values */
    pkt->data += ret;
    pkt->size -= ret;
    if (got_frame) {
1814
        int is_sub = (dec_ctx->codec_type == AVMEDIA_TYPE_SUBTITLE);
1815 1816
        nb_streams_frames[pkt->stream_index]++;
        if (do_show_frames)
1817 1818 1819 1820 1821 1822
            if (is_sub)
                show_subtitle(w, &sub, fmt_ctx->streams[pkt->stream_index], fmt_ctx);
            else
                show_frame(w, frame, fmt_ctx->streams[pkt->stream_index], fmt_ctx);
        if (is_sub)
            avsubtitle_free(&sub);
1823 1824
    }
    return got_frame;
1825 1826
}

1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852
static void log_read_interval(const ReadInterval *interval, void *log_ctx, int log_level)
{
    av_log(log_ctx, log_level, "id:%d", interval->id);

    if (interval->has_start) {
        av_log(log_ctx, log_level, " start:%s%s", interval->start_is_offset ? "+" : "",
               av_ts2timestr(interval->start, &AV_TIME_BASE_Q));
    } else {
        av_log(log_ctx, log_level, " start:N/A");
    }

    if (interval->has_end) {
        av_log(log_ctx, log_level, " end:%s", interval->end_is_offset ? "+" : "");
        if (interval->duration_frames)
            av_log(log_ctx, log_level, "#%"PRId64, interval->end);
        else
            av_log(log_ctx, log_level, "%s", av_ts2timestr(interval->end, &AV_TIME_BASE_Q));
    } else {
        av_log(log_ctx, log_level, " end:N/A");
    }

    av_log(log_ctx, log_level, "\n");
}

static int read_interval_packets(WriterContext *w, AVFormatContext *fmt_ctx,
                                 const ReadInterval *interval, int64_t *cur_ts)
1853
{
1854
    AVPacket pkt, pkt1;
1855
    AVFrame *frame = NULL;
1856
    int ret = 0, i = 0, frame_count = 0;
1857
    int64_t start = -INT64_MAX, end = interval->end;
1858
    int has_start = 0, has_end = interval->has_end && !interval->end_is_offset;
1859 1860 1861

    av_init_packet(&pkt);

1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888
    av_log(NULL, AV_LOG_VERBOSE, "Processing read interval ");
    log_read_interval(interval, NULL, AV_LOG_VERBOSE);

    if (interval->has_start) {
        int64_t target;
        if (interval->start_is_offset) {
            if (*cur_ts == AV_NOPTS_VALUE) {
                av_log(NULL, AV_LOG_ERROR,
                       "Could not seek to relative position since current "
                       "timestamp is not defined\n");
                ret = AVERROR(EINVAL);
                goto end;
            }
            target = *cur_ts + interval->start;
        } else {
            target = interval->start;
        }

        av_log(NULL, AV_LOG_VERBOSE, "Seeking to read interval start point %s\n",
               av_ts2timestr(target, &AV_TIME_BASE_Q));
        if ((ret = avformat_seek_file(fmt_ctx, -1, -INT64_MAX, target, INT64_MAX, 0)) < 0) {
            av_log(NULL, AV_LOG_ERROR, "Could not seek to position %"PRId64": %s\n",
                   interval->start, av_err2str(ret));
            goto end;
        }
    }

1889
    frame = av_frame_alloc();
1890 1891 1892 1893
    if (!frame) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
1894
    while (!av_read_frame(fmt_ctx, &pkt)) {
1895
        if (selected_streams[pkt.stream_index]) {
1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918
            AVRational tb = fmt_ctx->streams[pkt.stream_index]->time_base;

            if (pkt.pts != AV_NOPTS_VALUE)
                *cur_ts = av_rescale_q(pkt.pts, tb, AV_TIME_BASE_Q);

            if (!has_start && *cur_ts != AV_NOPTS_VALUE) {
                start = *cur_ts;
                has_start = 1;
            }

            if (has_start && !has_end && interval->end_is_offset) {
                end = start + interval->end;
                has_end = 1;
            }

            if (interval->end_is_offset && interval->duration_frames) {
                if (frame_count >= interval->end)
                    break;
            } else if (has_end && *cur_ts != AV_NOPTS_VALUE && *cur_ts >= end) {
                break;
            }

            frame_count++;
1919 1920 1921 1922 1923 1924 1925
            if (do_read_packets) {
                if (do_show_packets)
                    show_packet(w, fmt_ctx, &pkt, i++);
                nb_streams_packets[pkt.stream_index]++;
            }
            if (do_read_frames) {
                pkt1 = pkt;
1926
                while (pkt1.size && process_frame(w, fmt_ctx, frame, &pkt1) > 0);
1927
            }
1928
        }
1929
        av_free_packet(&pkt);
1930 1931 1932 1933 1934 1935 1936
    }
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    //Flush remaining frames that are cached in the decoder
    for (i = 0; i < fmt_ctx->nb_streams; i++) {
        pkt.stream_index = i;
1937
        if (do_read_frames)
1938
            while (process_frame(w, fmt_ctx, frame, &pkt) > 0);
1939
    }
1940 1941

end:
1942
    av_frame_free(&frame);
1943 1944 1945 1946 1947 1948 1949
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "Could not read packets in interval ");
        log_read_interval(interval, NULL, AV_LOG_ERROR);
    }
    return ret;
}

1950
static int read_packets(WriterContext *w, AVFormatContext *fmt_ctx)
1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964
{
    int i, ret = 0;
    int64_t cur_ts = fmt_ctx->start_time;

    if (read_intervals_nb == 0) {
        ReadInterval interval = (ReadInterval) { .has_start = 0, .has_end = 0 };
        ret = read_interval_packets(w, fmt_ctx, &interval, &cur_ts);
    } else {
        for (i = 0; i < read_intervals_nb; i++) {
            ret = read_interval_packets(w, fmt_ctx, &read_intervals[i], &cur_ts);
            if (ret < 0)
                break;
        }
    }
1965 1966

    return ret;
1967 1968
}

1969
static int show_stream(WriterContext *w, AVFormatContext *fmt_ctx, int stream_idx, int in_program)
Stefano Sabatini's avatar
Stefano Sabatini committed
1970 1971 1972
{
    AVStream *stream = fmt_ctx->streams[stream_idx];
    AVCodecContext *dec_ctx;
1973
    const AVCodec *dec;
Stefano Sabatini's avatar
Stefano Sabatini committed
1974
    char val_str[128];
1975
    const char *s;
1976
    AVRational sar, dar;
1977
    AVBPrint pbuf;
1978
    int ret = 0;
1979 1980

    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
Stefano Sabatini's avatar
Stefano Sabatini committed
1981

1982
    writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM : SECTION_ID_STREAM);
Stefano Sabatini's avatar
Stefano Sabatini committed
1983

1984
    print_int("index", stream->index);
Stefano Sabatini's avatar
Stefano Sabatini committed
1985 1986

    if ((dec_ctx = stream->codec)) {
1987
        const char *profile = NULL;
1988 1989 1990 1991
        dec = dec_ctx->codec;
        if (dec) {
            print_str("codec_name", dec->name);
            if (!do_bitexact) {
1992 1993
                if (dec->long_name) print_str    ("codec_long_name", dec->long_name);
                else                print_str_opt("codec_long_name", "unknown");
1994
            }
Stefano Sabatini's avatar
Stefano Sabatini committed
1995
        } else {
1996
            print_str_opt("codec_name", "unknown");
1997
            if (!do_bitexact) {
1998
                print_str_opt("codec_long_name", "unknown");
1999
            }
Stefano Sabatini's avatar
Stefano Sabatini committed
2000 2001
        }

2002 2003 2004 2005 2006
        if (dec && (profile = av_get_profile_name(dec, dec_ctx->profile)))
            print_str("profile", profile);
        else
            print_str_opt("profile", "unknown");

2007 2008 2009
        s = av_get_media_type_string(dec_ctx->codec_type);
        if (s) print_str    ("codec_type", s);
        else   print_str_opt("codec_type", "unknown");
2010
        print_q("codec_time_base", dec_ctx->time_base, '/');
Stefano Sabatini's avatar
Stefano Sabatini committed
2011 2012

        /* print AVI/FourCC tag */
2013
        av_get_codec_tag_string(val_str, sizeof(val_str), dec_ctx->codec_tag);
2014 2015
        print_str("codec_tag_string",    val_str);
        print_fmt("codec_tag", "0x%04x", dec_ctx->codec_tag);
Stefano Sabatini's avatar
Stefano Sabatini committed
2016 2017

        switch (dec_ctx->codec_type) {
2018
        case AVMEDIA_TYPE_VIDEO:
2019 2020 2021
            print_int("width",        dec_ctx->width);
            print_int("height",       dec_ctx->height);
            print_int("has_b_frames", dec_ctx->has_b_frames);
2022 2023 2024 2025 2026 2027
            sar = av_guess_sample_aspect_ratio(fmt_ctx, stream, NULL);
            if (sar.den) {
                print_q("sample_aspect_ratio", sar, ':');
                av_reduce(&dar.num, &dar.den,
                          dec_ctx->width  * sar.num,
                          dec_ctx->height * sar.den,
2028
                          1024*1024);
2029
                print_q("display_aspect_ratio", dar, ':');
2030 2031 2032
            } else {
                print_str_opt("sample_aspect_ratio", "N/A");
                print_str_opt("display_aspect_ratio", "N/A");
2033
            }
2034 2035 2036
            s = av_get_pix_fmt_name(dec_ctx->pix_fmt);
            if (s) print_str    ("pix_fmt", s);
            else   print_str_opt("pix_fmt", "unknown");
2037
            print_int("level",   dec_ctx->level);
2038
            if (dec_ctx->timecode_frame_start >= 0) {
2039 2040 2041
                char tcbuf[AV_TIMECODE_STR_SIZE];
                av_timecode_make_mpeg_tc_string(tcbuf, dec_ctx->timecode_frame_start);
                print_str("timecode", tcbuf);
2042 2043 2044
            } else {
                print_str_opt("timecode", "N/A");
            }
Stefano Sabatini's avatar
Stefano Sabatini committed
2045 2046
            break;

2047
        case AVMEDIA_TYPE_AUDIO:
2048 2049 2050
            s = av_get_sample_fmt_name(dec_ctx->sample_fmt);
            if (s) print_str    ("sample_fmt", s);
            else   print_str_opt("sample_fmt", "unknown");
2051
            print_val("sample_rate",     dec_ctx->sample_rate, unit_hertz_str);
2052
            print_int("channels",        dec_ctx->channels);
2053 2054 2055 2056 2057 2058 2059 2060 2061

            if (dec_ctx->channel_layout) {
                av_bprint_clear(&pbuf);
                av_bprint_channel_layout(&pbuf, dec_ctx->channels, dec_ctx->channel_layout);
                print_str    ("channel_layout", pbuf.str);
            } else {
                print_str_opt("channel_layout", "unknown");
            }

2062
            print_int("bits_per_sample", av_get_bits_per_sample(dec_ctx->codec_id));
Stefano Sabatini's avatar
Stefano Sabatini committed
2063
            break;
2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074

        case AVMEDIA_TYPE_SUBTITLE:
            if (dec_ctx->width)
                print_int("width",       dec_ctx->width);
            else
                print_str_opt("width",   "N/A");
            if (dec_ctx->height)
                print_int("height",      dec_ctx->height);
            else
                print_str_opt("height",  "N/A");
            break;
Stefano Sabatini's avatar
Stefano Sabatini committed
2075 2076
        }
    } else {
2077
        print_str_opt("codec_type", "unknown");
Stefano Sabatini's avatar
Stefano Sabatini committed
2078
    }
2079
    if (dec_ctx->codec && dec_ctx->codec->priv_class && show_private_data) {
2080
        const AVOption *opt = NULL;
2081 2082 2083 2084 2085 2086 2087 2088 2089
        while (opt = av_opt_next(dec_ctx->priv_data,opt)) {
            uint8_t *str;
            if (opt->flags) continue;
            if (av_opt_get(dec_ctx->priv_data, opt->name, 0, &str) >= 0) {
                print_str(opt->name, str);
                av_free(str);
            }
        }
    }
Stefano Sabatini's avatar
Stefano Sabatini committed
2090

2091 2092
    if (fmt_ctx->iformat->flags & AVFMT_SHOW_IDS) print_fmt    ("id", "0x%x", stream->id);
    else                                          print_str_opt("id", "N/A");
2093 2094 2095
    print_q("r_frame_rate",   stream->r_frame_rate,   '/');
    print_q("avg_frame_rate", stream->avg_frame_rate, '/');
    print_q("time_base",      stream->time_base,      '/');
2096 2097 2098 2099
    print_ts  ("start_pts",   stream->start_time);
    print_time("start_time",  stream->start_time, &stream->time_base);
    print_ts  ("duration_ts", stream->duration);
    print_time("duration",    stream->duration, &stream->time_base);
2100 2101
    if (dec_ctx->bit_rate > 0) print_val    ("bit_rate", dec_ctx->bit_rate, unit_bit_per_second_str);
    else                       print_str_opt("bit_rate", "N/A");
2102 2103
    if (stream->nb_frames) print_fmt    ("nb_frames", "%"PRId64, stream->nb_frames);
    else                   print_str_opt("nb_frames", "N/A");
2104 2105 2106 2107
    if (nb_streams_frames[stream_idx])  print_fmt    ("nb_read_frames", "%"PRIu64, nb_streams_frames[stream_idx]);
    else                                print_str_opt("nb_read_frames", "N/A");
    if (nb_streams_packets[stream_idx]) print_fmt    ("nb_read_packets", "%"PRIu64, nb_streams_packets[stream_idx]);
    else                                print_str_opt("nb_read_packets", "N/A");
2108 2109 2110
    if (do_show_data)
        writer_print_data(w, "extradata", dec_ctx->extradata,
                                          dec_ctx->extradata_size);
2111 2112 2113 2114 2115 2116

    /* Print disposition information */
#define PRINT_DISPOSITION(flagname, name) do {                                \
        print_int(name, !!(stream->disposition & AV_DISPOSITION_##flagname)); \
    } while (0)

2117
    if (do_show_stream_disposition) {
2118
    writer_print_section_header(w, in_program ? SECTION_ID_PROGRAM_STREAM_DISPOSITION : SECTION_ID_STREAM_DISPOSITION);
2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130
    PRINT_DISPOSITION(DEFAULT,          "default");
    PRINT_DISPOSITION(DUB,              "dub");
    PRINT_DISPOSITION(ORIGINAL,         "original");
    PRINT_DISPOSITION(COMMENT,          "comment");
    PRINT_DISPOSITION(LYRICS,           "lyrics");
    PRINT_DISPOSITION(KARAOKE,          "karaoke");
    PRINT_DISPOSITION(FORCED,           "forced");
    PRINT_DISPOSITION(HEARING_IMPAIRED, "hearing_impaired");
    PRINT_DISPOSITION(VISUAL_IMPAIRED,  "visual_impaired");
    PRINT_DISPOSITION(CLEAN_EFFECTS,    "clean_effects");
    PRINT_DISPOSITION(ATTACHED_PIC,     "attached_pic");
    writer_print_section_footer(w);
2131
    }
2132

2133 2134
    if (do_show_stream_tags)
        ret = show_tags(w, stream->metadata, in_program ? SECTION_ID_PROGRAM_STREAM_TAGS : SECTION_ID_STREAM_TAGS);
Stefano Sabatini's avatar
Stefano Sabatini committed
2135

2136
    writer_print_section_footer(w);
2137
    av_bprint_finalize(&pbuf, NULL);
2138
    fflush(stdout);
2139 2140

    return ret;
Stefano Sabatini's avatar
Stefano Sabatini committed
2141 2142
}

2143
static int show_streams(WriterContext *w, AVFormatContext *fmt_ctx)
2144
{
2145 2146
    int i, ret = 0;

2147
    writer_print_section_header(w, SECTION_ID_STREAMS);
2148
    for (i = 0; i < fmt_ctx->nb_streams; i++)
2149 2150 2151 2152 2153
        if (selected_streams[i]) {
            ret = show_stream(w, fmt_ctx, i, 0);
            if (ret < 0)
                break;
        }
2154
    writer_print_section_footer(w);
2155 2156

    return ret;
2157 2158
}

2159
static int show_program(WriterContext *w, AVFormatContext *fmt_ctx, AVProgram *program)
2160
{
2161
    int i, ret = 0;
2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172

    writer_print_section_header(w, SECTION_ID_PROGRAM);
    print_int("program_id", program->id);
    print_int("program_num", program->program_num);
    print_int("nb_streams", program->nb_stream_indexes);
    print_int("pmt_pid", program->pmt_pid);
    print_int("pcr_pid", program->pcr_pid);
    print_ts("start_pts", program->start_time);
    print_time("start_time", program->start_time, &AV_TIME_BASE_Q);
    print_ts("end_pts", program->end_time);
    print_time("end_time", program->end_time, &AV_TIME_BASE_Q);
2173 2174
    if (do_show_program_tags)
        ret = show_tags(w, program->metadata, SECTION_ID_PROGRAM_TAGS);
2175 2176
    if (ret < 0)
        goto end;
2177 2178 2179

    writer_print_section_header(w, SECTION_ID_PROGRAM_STREAMS);
    for (i = 0; i < program->nb_stream_indexes; i++) {
2180 2181 2182 2183 2184
        if (selected_streams[program->stream_index[i]]) {
            ret = show_stream(w, fmt_ctx, program->stream_index[i], 1);
            if (ret < 0)
                break;
        }
2185 2186 2187
    }
    writer_print_section_footer(w);

2188
end:
2189
    writer_print_section_footer(w);
2190
    return ret;
2191 2192
}

2193
static int show_programs(WriterContext *w, AVFormatContext *fmt_ctx)
2194
{
2195
    int i, ret = 0;
2196 2197 2198 2199 2200 2201

    writer_print_section_header(w, SECTION_ID_PROGRAMS);
    for (i = 0; i < fmt_ctx->nb_programs; i++) {
        AVProgram *program = fmt_ctx->programs[i];
        if (!program)
            continue;
2202 2203 2204
        ret = show_program(w, fmt_ctx, program);
        if (ret < 0)
            break;
2205
    }
2206
    writer_print_section_footer(w);
2207
    return ret;
2208 2209
}

2210
static int show_chapters(WriterContext *w, AVFormatContext *fmt_ctx)
2211
{
2212
    int i, ret = 0;
2213 2214 2215 2216 2217 2218 2219 2220 2221 2222 2223 2224

    writer_print_section_header(w, SECTION_ID_CHAPTERS);
    for (i = 0; i < fmt_ctx->nb_chapters; i++) {
        AVChapter *chapter = fmt_ctx->chapters[i];

        writer_print_section_header(w, SECTION_ID_CHAPTER);
        print_int("id", chapter->id);
        print_q  ("time_base", chapter->time_base, '/');
        print_int("start", chapter->start);
        print_time("start_time", chapter->start, &chapter->time_base);
        print_int("end", chapter->end);
        print_time("end_time", chapter->end, &chapter->time_base);
2225 2226
        if (do_show_chapter_tags)
            ret = show_tags(w, chapter->metadata, SECTION_ID_CHAPTER_TAGS);
2227 2228 2229
        writer_print_section_footer(w);
    }
    writer_print_section_footer(w);
2230 2231

    return ret;
2232 2233
}

2234
static int show_format(WriterContext *w, AVFormatContext *fmt_ctx)
Stefano Sabatini's avatar
Stefano Sabatini committed
2235 2236
{
    char val_str[128];
2237
    int64_t size = fmt_ctx->pb ? avio_size(fmt_ctx->pb) : -1;
2238
    int ret = 0;
Stefano Sabatini's avatar
Stefano Sabatini committed
2239

2240
    writer_print_section_header(w, SECTION_ID_FORMAT);
2241
    print_str_validate("filename", fmt_ctx->filename);
2242
    print_int("nb_streams",       fmt_ctx->nb_streams);
2243
    print_int("nb_programs",      fmt_ctx->nb_programs);
2244
    print_str("format_name",      fmt_ctx->iformat->name);
2245
    if (!do_bitexact) {
2246 2247
        if (fmt_ctx->iformat->long_name) print_str    ("format_long_name", fmt_ctx->iformat->long_name);
        else                             print_str_opt("format_long_name", "unknown");
2248
    }
2249 2250
    print_time("start_time",      fmt_ctx->start_time, &AV_TIME_BASE_Q);
    print_time("duration",        fmt_ctx->duration,   &AV_TIME_BASE_Q);
2251 2252 2253 2254
    if (size >= 0) print_val    ("size", size, unit_byte_str);
    else           print_str_opt("size", "N/A");
    if (fmt_ctx->bit_rate > 0) print_val    ("bit_rate", fmt_ctx->bit_rate, unit_bit_per_second_str);
    else                       print_str_opt("bit_rate", "N/A");
2255
    print_int("probe_score", av_format_get_probe_score(fmt_ctx));
2256 2257
    if (do_show_format_tags)
        ret = show_tags(w, fmt_ctx->metadata, SECTION_ID_FORMAT_TAGS);
2258 2259

    writer_print_section_footer(w);
2260
    fflush(stdout);
2261
    return ret;
Stefano Sabatini's avatar
Stefano Sabatini committed
2262 2263
}

2264 2265 2266 2267 2268 2269 2270 2271
static void show_error(WriterContext *w, int err)
{
    char errbuf[128];
    const char *errbuf_ptr = errbuf;

    if (av_strerror(err, errbuf, sizeof(errbuf)) < 0)
        errbuf_ptr = strerror(AVUNERROR(err));

2272
    writer_print_section_header(w, SECTION_ID_ERROR);
2273 2274
    print_int("code", err);
    print_str("string", errbuf_ptr);
2275
    writer_print_section_footer(w);
2276 2277
}

Stefano Sabatini's avatar
Stefano Sabatini committed
2278 2279
static int open_input_file(AVFormatContext **fmt_ctx_ptr, const char *filename)
{
2280
    int err, i, orig_nb_streams;
2281 2282
    AVFormatContext *fmt_ctx = NULL;
    AVDictionaryEntry *t;
2283
    AVDictionary **opts;
Stefano Sabatini's avatar
Stefano Sabatini committed
2284

2285 2286
    if ((err = avformat_open_input(&fmt_ctx, filename,
                                   iformat, &format_opts)) < 0) {
Stefano Sabatini's avatar
Stefano Sabatini committed
2287 2288 2289
        print_error(filename, err);
        return err;
    }
2290 2291 2292 2293 2294
    if ((t = av_dict_get(format_opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
        av_log(NULL, AV_LOG_ERROR, "Option %s not found.\n", t->key);
        return AVERROR_OPTION_NOT_FOUND;
    }

Stefano Sabatini's avatar
Stefano Sabatini committed
2295
    /* fill the streams in the format context */
2296 2297 2298 2299
    opts = setup_find_stream_info_opts(fmt_ctx, codec_opts);
    orig_nb_streams = fmt_ctx->nb_streams;

    if ((err = avformat_find_stream_info(fmt_ctx, opts)) < 0) {
Stefano Sabatini's avatar
Stefano Sabatini committed
2300 2301 2302
        print_error(filename, err);
        return err;
    }
2303 2304 2305
    for (i = 0; i < orig_nb_streams; i++)
        av_dict_free(&opts[i]);
    av_freep(&opts);
Stefano Sabatini's avatar
Stefano Sabatini committed
2306

2307
    av_dump_format(fmt_ctx, 0, filename, 0);
Stefano Sabatini's avatar
Stefano Sabatini committed
2308 2309 2310 2311 2312 2313

    /* bind a decoder to each input stream */
    for (i = 0; i < fmt_ctx->nb_streams; i++) {
        AVStream *stream = fmt_ctx->streams[i];
        AVCodec *codec;

2314
        if (stream->codec->codec_id == AV_CODEC_ID_PROBE) {
2315
            av_log(NULL, AV_LOG_WARNING,
2316 2317 2318
                   "Failed to probe codec for input stream %d\n",
                    stream->index);
        } else if (!(codec = avcodec_find_decoder(stream->codec->codec_id))) {
2319
            av_log(NULL, AV_LOG_WARNING,
2320 2321
                    "Unsupported codec with id %d for input stream %d\n",
                    stream->codec->codec_id, stream->index);
2322 2323 2324 2325
        } else {
            AVDictionary *opts = filter_codec_opts(codec_opts, stream->codec->codec_id,
                                                   fmt_ctx, stream, codec);
            if (avcodec_open2(stream->codec, codec, &opts) < 0) {
2326
                av_log(NULL, AV_LOG_WARNING, "Could not open codec for input stream %d\n",
2327 2328 2329 2330 2331 2332 2333
                       stream->index);
            }
            if ((t = av_dict_get(opts, "", NULL, AV_DICT_IGNORE_SUFFIX))) {
                av_log(NULL, AV_LOG_ERROR, "Option %s for input stream %d not found\n",
                       t->key, stream->index);
                return AVERROR_OPTION_NOT_FOUND;
            }
Stefano Sabatini's avatar
Stefano Sabatini committed
2334 2335 2336 2337 2338 2339 2340
        }
    }

    *fmt_ctx_ptr = fmt_ctx;
    return 0;
}

2341 2342 2343 2344 2345 2346 2347
static void close_input_file(AVFormatContext **ctx_ptr)
{
    int i;
    AVFormatContext *fmt_ctx = *ctx_ptr;

    /* close decoder for each stream */
    for (i = 0; i < fmt_ctx->nb_streams; i++)
2348
        if (fmt_ctx->streams[i]->codec->codec_id != AV_CODEC_ID_NONE)
2349 2350 2351 2352 2353
            avcodec_close(fmt_ctx->streams[i]->codec);

    avformat_close_input(ctx_ptr);
}

2354
static int probe_file(WriterContext *wctx, const char *filename)
Stefano Sabatini's avatar
Stefano Sabatini committed
2355 2356
{
    AVFormatContext *fmt_ctx;
2357
    int ret, i;
2358
    int section_id;
Stefano Sabatini's avatar
Stefano Sabatini committed
2359

2360 2361 2362
    do_read_frames = do_show_frames || do_count_frames;
    do_read_packets = do_show_packets || do_count_packets;

2363
    ret = open_input_file(&fmt_ctx, filename);
2364 2365 2366
    if (ret < 0)
        return ret;

2367 2368
#define CHECK_END if (ret < 0) goto end

2369 2370 2371
    nb_streams_frames  = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_frames));
    nb_streams_packets = av_calloc(fmt_ctx->nb_streams, sizeof(*nb_streams_packets));
    selected_streams   = av_calloc(fmt_ctx->nb_streams, sizeof(*selected_streams));
2372

2373 2374 2375 2376 2377
    for (i = 0; i < fmt_ctx->nb_streams; i++) {
        if (stream_specifier) {
            ret = avformat_match_stream_specifier(fmt_ctx,
                                                  fmt_ctx->streams[i],
                                                  stream_specifier);
2378
            CHECK_END;
2379 2380 2381 2382 2383
            else
                selected_streams[i] = ret;
            ret = 0;
        } else {
            selected_streams[i] = 1;
2384
        }
2385 2386 2387 2388 2389 2390 2391 2392 2393 2394 2395 2396
    }

    if (do_read_frames || do_read_packets) {
        if (do_show_frames && do_show_packets &&
            wctx->writer->flags & WRITER_FLAG_PUT_PACKETS_AND_FRAMES_IN_SAME_CHAPTER)
            section_id = SECTION_ID_PACKETS_AND_FRAMES;
        else if (do_show_packets && !do_show_frames)
            section_id = SECTION_ID_PACKETS;
        else // (!do_show_packets && do_show_frames)
            section_id = SECTION_ID_FRAMES;
        if (do_show_frames || do_show_packets)
            writer_print_section_header(wctx, section_id);
2397
        ret = read_packets(wctx, fmt_ctx);
2398 2399
        if (do_show_frames || do_show_packets)
            writer_print_section_footer(wctx);
2400 2401
        CHECK_END;
    }
2402

2403 2404 2405 2406 2407 2408 2409 2410 2411 2412 2413 2414 2415 2416 2417 2418
    if (do_show_programs) {
        ret = show_programs(wctx, fmt_ctx);
        CHECK_END;
    }

    if (do_show_streams) {
        ret = show_streams(wctx, fmt_ctx);
        CHECK_END;
    }
    if (do_show_chapters) {
        ret = show_chapters(wctx, fmt_ctx);
        CHECK_END;
    }
    if (do_show_format) {
        ret = show_format(wctx, fmt_ctx);
        CHECK_END;
2419 2420 2421 2422 2423 2424 2425
    }

end:
    close_input_file(&fmt_ctx);
    av_freep(&nb_streams_frames);
    av_freep(&nb_streams_packets);
    av_freep(&selected_streams);
2426

2427
    return ret;
Stefano Sabatini's avatar
Stefano Sabatini committed
2428 2429 2430 2431
}

static void show_usage(void)
{
2432 2433 2434
    av_log(NULL, AV_LOG_INFO, "Simple multimedia streams analyzer\n");
    av_log(NULL, AV_LOG_INFO, "usage: %s [OPTIONS] [INPUT_FILE]\n", program_name);
    av_log(NULL, AV_LOG_INFO, "\n");
Stefano Sabatini's avatar
Stefano Sabatini committed
2435 2436
}

2437 2438
static void ffprobe_show_program_version(WriterContext *w)
{
2439 2440
    AVBPrint pbuf;
    av_bprint_init(&pbuf, 1, AV_BPRINT_SIZE_UNLIMITED);
2441

2442
    writer_print_section_header(w, SECTION_ID_PROGRAM_VERSION);
2443 2444
    print_str("version", FFMPEG_VERSION);
    print_fmt("copyright", "Copyright (c) %d-%d the FFmpeg developers",
2445
              program_birth_year, CONFIG_THIS_YEAR);
2446 2447
    print_str("build_date", __DATE__);
    print_str("build_time", __TIME__);
2448
    print_str("compiler_ident", CC_IDENT);
2449
    print_str("configuration", FFMPEG_CONFIGURATION);
2450
    writer_print_section_footer(w);
2451

2452
    av_bprint_finalize(&pbuf, NULL);
2453 2454 2455 2456 2457 2458
}

#define SHOW_LIB_VERSION(libname, LIBNAME)                              \
    do {                                                                \
        if (CONFIG_##LIBNAME) {                                         \
            unsigned int version = libname##_version();                 \
2459
            writer_print_section_header(w, SECTION_ID_LIBRARY_VERSION); \
2460 2461 2462 2463 2464
            print_str("name",    "lib" #libname);                       \
            print_int("major",   LIB##LIBNAME##_VERSION_MAJOR);         \
            print_int("minor",   LIB##LIBNAME##_VERSION_MINOR);         \
            print_int("micro",   LIB##LIBNAME##_VERSION_MICRO);         \
            print_int("version", version);                              \
2465
            print_str("ident",   LIB##LIBNAME##_IDENT);                 \
2466
            writer_print_section_footer(w);                             \
2467 2468 2469 2470 2471
        }                                                               \
    } while (0)

static void ffprobe_show_library_versions(WriterContext *w)
{
2472
    writer_print_section_header(w, SECTION_ID_LIBRARY_VERSIONS);
2473 2474 2475 2476 2477 2478 2479 2480
    SHOW_LIB_VERSION(avutil,     AVUTIL);
    SHOW_LIB_VERSION(avcodec,    AVCODEC);
    SHOW_LIB_VERSION(avformat,   AVFORMAT);
    SHOW_LIB_VERSION(avdevice,   AVDEVICE);
    SHOW_LIB_VERSION(avfilter,   AVFILTER);
    SHOW_LIB_VERSION(swscale,    SWSCALE);
    SHOW_LIB_VERSION(swresample, SWRESAMPLE);
    SHOW_LIB_VERSION(postproc,   POSTPROC);
2481
    writer_print_section_footer(w);
2482 2483
}

2484
static int opt_format(void *optctx, const char *opt, const char *arg)
2485 2486 2487
{
    iformat = av_find_input_format(arg);
    if (!iformat) {
2488
        av_log(NULL, AV_LOG_ERROR, "Unknown input format: %s\n", arg);
2489
        return AVERROR(EINVAL);
2490
    }
2491
    return 0;
2492 2493
}

2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 2532 2533 2534 2535 2536 2537 2538 2539 2540 2541 2542 2543 2544 2545 2546 2547 2548 2549 2550 2551 2552 2553 2554 2555 2556 2557 2558 2559 2560 2561 2562 2563 2564 2565
static inline void mark_section_show_entries(SectionID section_id,
                                             int show_all_entries, AVDictionary *entries)
{
    struct section *section = &sections[section_id];

    section->show_all_entries = show_all_entries;
    if (show_all_entries) {
        SectionID *id;
        for (id = section->children_ids; *id != -1; id++)
            mark_section_show_entries(*id, show_all_entries, entries);
    } else {
        av_dict_copy(&section->entries_to_show, entries, 0);
    }
}

static int match_section(const char *section_name,
                         int show_all_entries, AVDictionary *entries)
{
    int i, ret = 0;

    for (i = 0; i < FF_ARRAY_ELEMS(sections); i++) {
        const struct section *section = &sections[i];
        if (!strcmp(section_name, section->name) ||
            (section->unique_name && !strcmp(section_name, section->unique_name))) {
            av_log(NULL, AV_LOG_DEBUG,
                   "'%s' matches section with unique name '%s'\n", section_name,
                   (char *)av_x_if_null(section->unique_name, section->name));
            ret++;
            mark_section_show_entries(section->id, show_all_entries, entries);
        }
    }
    return ret;
}

static int opt_show_entries(void *optctx, const char *opt, const char *arg)
{
    const char *p = arg;
    int ret = 0;

    while (*p) {
        AVDictionary *entries = NULL;
        char *section_name = av_get_token(&p, "=:");
        int show_all_entries = 0;

        if (!section_name) {
            av_log(NULL, AV_LOG_ERROR,
                   "Missing section name for option '%s'\n", opt);
            return AVERROR(EINVAL);
        }

        if (*p == '=') {
            p++;
            while (*p && *p != ':') {
                char *entry = av_get_token(&p, ",:");
                if (!entry)
                    break;
                av_log(NULL, AV_LOG_VERBOSE,
                       "Adding '%s' to the entries to show in section '%s'\n",
                       entry, section_name);
                av_dict_set(&entries, entry, "", AV_DICT_DONT_STRDUP_KEY);
                if (*p == ',')
                    p++;
            }
        } else {
            show_all_entries = 1;
        }

        ret = match_section(section_name, show_all_entries, entries);
        if (ret == 0) {
            av_log(NULL, AV_LOG_ERROR, "No match for section '%s'\n", section_name);
            ret = AVERROR(EINVAL);
        }
2566
        av_dict_free(&entries);
2567 2568 2569 2570 2571 2572 2573 2574 2575 2576 2577
        av_free(section_name);

        if (ret <= 0)
            break;
        if (*p)
            p++;
    }

    return ret;
}

2578
static int opt_show_format_entry(void *optctx, const char *opt, const char *arg)
2579
{
2580 2581 2582 2583 2584 2585 2586 2587 2588
    char *buf = av_asprintf("format=%s", arg);
    int ret;

    av_log(NULL, AV_LOG_WARNING,
           "Option '%s' is deprecated, use '-show_entries format=%s' instead\n",
           opt, arg);
    ret = opt_show_entries(optctx, opt, buf);
    av_free(buf);
    return ret;
2589 2590
}

2591
static void opt_input_file(void *optctx, const char *arg)
Stefano Sabatini's avatar
Stefano Sabatini committed
2592
{
2593
    if (input_filename) {
2594 2595 2596
        av_log(NULL, AV_LOG_ERROR,
                "Argument '%s' provided as input filename, but '%s' was already specified.\n",
                arg, input_filename);
2597
        exit_program(1);
2598
    }
2599 2600 2601
    if (!strcmp(arg, "-"))
        arg = "pipe:";
    input_filename = arg;
Stefano Sabatini's avatar
Stefano Sabatini committed
2602 2603
}

2604 2605 2606 2607 2608 2609
static int opt_input_file_i(void *optctx, const char *opt, const char *arg)
{
    opt_input_file(optctx, arg);
    return 0;
}

2610
void show_help_default(const char *opt, const char *arg)
Stefano Sabatini's avatar
Stefano Sabatini committed
2611
{
2612
    av_log_set_callback(log_callback_help);
Stefano Sabatini's avatar
Stefano Sabatini committed
2613
    show_usage();
2614
    show_help_options(options, "Main options:", 0, 0, 0);
Stefano Sabatini's avatar
Stefano Sabatini committed
2615
    printf("\n");
2616 2617

    show_help_children(avformat_get_class(), AV_OPT_FLAG_DECODING_PARAM);
Stefano Sabatini's avatar
Stefano Sabatini committed
2618 2619
}

2620 2621 2622 2623 2624 2625 2626 2627 2628 2629 2630 2631 2632 2633 2634 2635 2636 2637 2638 2639 2640 2641 2642 2643 2644 2645 2646 2647 2648 2649 2650 2651 2652 2653 2654 2655 2656 2657 2658 2659 2660 2661 2662 2663 2664 2665 2666 2667 2668 2669 2670 2671 2672 2673 2674 2675 2676 2677 2678 2679 2680 2681 2682 2683 2684 2685 2686 2687 2688 2689 2690 2691 2692 2693 2694 2695 2696 2697 2698 2699 2700 2701 2702 2703 2704 2705 2706 2707 2708 2709 2710 2711 2712 2713 2714 2715 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727
/**
 * Parse interval specification, according to the format:
 * INTERVAL ::= [START|+START_OFFSET][%[END|+END_OFFSET]]
 * INTERVALS ::= INTERVAL[,INTERVALS]
*/
static int parse_read_interval(const char *interval_spec,
                               ReadInterval *interval)
{
    int ret = 0;
    char *next, *p, *spec = av_strdup(interval_spec);
    if (!spec)
        return AVERROR(ENOMEM);

    if (!*spec) {
        av_log(NULL, AV_LOG_ERROR, "Invalid empty interval specification\n");
        ret = AVERROR(EINVAL);
        goto end;
    }

    p = spec;
    next = strchr(spec, '%');
    if (next)
        *next++ = 0;

    /* parse first part */
    if (*p) {
        interval->has_start = 1;

        if (*p == '+') {
            interval->start_is_offset = 1;
            p++;
        } else {
            interval->start_is_offset = 0;
        }

        ret = av_parse_time(&interval->start, p, 1);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Invalid interval start specification '%s'\n", p);
            goto end;
        }
    } else {
        interval->has_start = 0;
    }

    /* parse second part */
    p = next;
    if (p && *p) {
        int64_t us;
        interval->has_end = 1;

        if (*p == '+') {
            interval->end_is_offset = 1;
            p++;
        } else {
            interval->end_is_offset = 0;
        }

        if (interval->end_is_offset && *p == '#') {
            long long int lli;
            char *tail;
            interval->duration_frames = 1;
            p++;
            lli = strtoll(p, &tail, 10);
            if (*tail || lli < 0) {
                av_log(NULL, AV_LOG_ERROR,
                       "Invalid or negative value '%s' for duration number of frames\n", p);
                goto end;
            }
            interval->end = lli;
        } else {
            ret = av_parse_time(&us, p, 1);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Invalid interval end/duration specification '%s'\n", p);
                goto end;
            }
            interval->end = us;
        }
    } else {
        interval->has_end = 0;
    }

end:
    av_free(spec);
    return ret;
}

static int parse_read_intervals(const char *intervals_spec)
{
    int ret, n, i;
    char *p, *spec = av_strdup(intervals_spec);
    if (!spec)
        return AVERROR(ENOMEM);

    /* preparse specification, get number of intervals */
    for (n = 0, p = spec; *p; p++)
        if (*p == ',')
            n++;
    n++;

    read_intervals = av_malloc(n * sizeof(*read_intervals));
    if (!read_intervals) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
    read_intervals_nb = n;

    /* parse intervals */
    p = spec;
2728 2729 2730 2731 2732
    for (i = 0; p; i++) {
        char *next;

        av_assert0(i < read_intervals_nb);
        next = strchr(p, ',');
2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758
        if (next)
            *next++ = 0;

        read_intervals[i].id = i;
        ret = parse_read_interval(p, &read_intervals[i]);
        if (ret < 0) {
            av_log(NULL, AV_LOG_ERROR, "Error parsing read interval #%d '%s'\n",
                   i, p);
            goto end;
        }
        av_log(NULL, AV_LOG_VERBOSE, "Parsed log interval ");
        log_read_interval(&read_intervals[i], NULL, AV_LOG_VERBOSE);
        p = next;
    }
    av_assert0(i == read_intervals_nb);

end:
    av_free(spec);
    return ret;
}

static int opt_read_intervals(void *optctx, const char *opt, const char *arg)
{
    return parse_read_intervals(arg);
}

2759
static int opt_pretty(void *optctx, const char *opt, const char *arg)
Stefano Sabatini's avatar
Stefano Sabatini committed
2760 2761 2762 2763 2764
{
    show_value_unit              = 1;
    use_value_prefix             = 1;
    use_byte_value_binary_prefix = 1;
    use_value_sexagesimal_format = 1;
2765
    return 0;
Stefano Sabatini's avatar
Stefano Sabatini committed
2766 2767
}

2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796
static void print_section(SectionID id, int level)
{
    const SectionID *pid;
    const struct section *section = &sections[id];
    printf("%c%c%c",
           section->flags & SECTION_FLAG_IS_WRAPPER           ? 'W' : '.',
           section->flags & SECTION_FLAG_IS_ARRAY             ? 'A' : '.',
           section->flags & SECTION_FLAG_HAS_VARIABLE_FIELDS  ? 'V' : '.');
    printf("%*c  %s", level * 4, ' ', section->name);
    if (section->unique_name)
        printf("/%s", section->unique_name);
    printf("\n");

    for (pid = section->children_ids; *pid != -1; pid++)
        print_section(*pid, level+1);
}

static int opt_sections(void *optctx, const char *opt, const char *arg)
{
    printf("Sections:\n"
           "W.. = Section is a wrapper (contains other sections, no local entries)\n"
           ".A. = Section contains an array of elements of the same type\n"
           "..V = Section may contain a variable number of fields with variable keys\n"
           "FLAGS NAME/UNIQUE_NAME\n"
           "---\n");
    print_section(SECTION_ID_ROOT, 0);
    return 0;
}

2797 2798
static int opt_show_versions(const char *opt, const char *arg)
{
2799 2800
    mark_section_show_entries(SECTION_ID_PROGRAM_VERSION, 1, NULL);
    mark_section_show_entries(SECTION_ID_LIBRARY_VERSION, 1, NULL);
2801 2802 2803
    return 0;
}

2804 2805 2806 2807 2808 2809 2810
#define DEFINE_OPT_SHOW_SECTION(section, target_section_id)             \
    static int opt_show_##section(const char *opt, const char *arg)     \
    {                                                                   \
        mark_section_show_entries(SECTION_ID_##target_section_id, 1, NULL); \
        return 0;                                                       \
    }

2811
DEFINE_OPT_SHOW_SECTION(chapters,         CHAPTERS);
2812 2813 2814 2815 2816 2817 2818
DEFINE_OPT_SHOW_SECTION(error,            ERROR);
DEFINE_OPT_SHOW_SECTION(format,           FORMAT);
DEFINE_OPT_SHOW_SECTION(frames,           FRAMES);
DEFINE_OPT_SHOW_SECTION(library_versions, LIBRARY_VERSIONS);
DEFINE_OPT_SHOW_SECTION(packets,          PACKETS);
DEFINE_OPT_SHOW_SECTION(program_version,  PROGRAM_VERSION);
DEFINE_OPT_SHOW_SECTION(streams,          STREAMS);
2819
DEFINE_OPT_SHOW_SECTION(programs,         PROGRAMS);
2820

2821
static const OptionDef real_options[] = {
Stefano Sabatini's avatar
Stefano Sabatini committed
2822
#include "cmdutils_common_opts.h"
2823 2824 2825 2826
    { "f", HAS_ARG, {.func_arg = opt_format}, "force format", "format" },
    { "unit", OPT_BOOL, {&show_value_unit}, "show unit of the displayed values" },
    { "prefix", OPT_BOOL, {&use_value_prefix}, "use SI prefixes for the displayed values" },
    { "byte_binary_prefix", OPT_BOOL, {&use_byte_value_binary_prefix},
Stefano Sabatini's avatar
Stefano Sabatini committed
2827
      "use binary prefixes for byte units" },
2828
    { "sexagesimal", OPT_BOOL,  {&use_value_sexagesimal_format},
Stefano Sabatini's avatar
Stefano Sabatini committed
2829
      "use sexagesimal format HOURS:MM:SS.MICROSECONDS for time units" },
2830
    { "pretty", 0, {.func_arg = opt_pretty},
Stefano Sabatini's avatar
Stefano Sabatini committed
2831
      "prettify the format of displayed values, make it more human readable" },
2832
    { "print_format", OPT_STRING | HAS_ARG, {(void*)&print_format},
2833
      "set the output printing format (available formats are: default, compact, csv, flat, ini, json, xml)", "format" },
2834
    { "of", OPT_STRING | HAS_ARG, {(void*)&print_format}, "alias for -print_format", "format" },
2835
    { "select_streams", OPT_STRING | HAS_ARG, {(void*)&stream_specifier}, "select the specified streams", "stream_specifier" },
2836
    { "sections", OPT_EXIT, {.func_arg = opt_sections}, "print sections structure and section information, and exit" },
2837
    { "show_data",    OPT_BOOL, {(void*)&do_show_data}, "show packets data" },
2838 2839 2840
    { "show_error",   0, {(void*)&opt_show_error},  "show probing error" },
    { "show_format",  0, {(void*)&opt_show_format}, "show format/container info" },
    { "show_frames",  0, {(void*)&opt_show_frames}, "show frames info" },
2841
    { "show_format_entry", HAS_ARG, {.func_arg = opt_show_format_entry},
2842
      "show a particular entry from the format/container info", "entry" },
2843 2844 2845
    { "show_entries", HAS_ARG, {.func_arg = opt_show_entries},
      "show a set of specified entries", "entry_list" },
    { "show_packets", 0, {(void*)&opt_show_packets}, "show packets info" },
2846
    { "show_programs", 0, {(void*)&opt_show_programs}, "show programs info" },
2847
    { "show_streams", 0, {(void*)&opt_show_streams}, "show streams info" },
2848
    { "show_chapters", 0, {(void*)&opt_show_chapters}, "show chapters info" },
2849 2850
    { "count_frames", OPT_BOOL, {(void*)&do_count_frames}, "count the number of frames per stream" },
    { "count_packets", OPT_BOOL, {(void*)&do_count_packets}, "count the number of packets per stream" },
2851 2852
    { "show_program_version",  0, {(void*)&opt_show_program_version},  "show ffprobe version" },
    { "show_library_versions", 0, {(void*)&opt_show_library_versions}, "show library versions" },
2853
    { "show_versions",         0, {(void*)&opt_show_versions}, "show program and library versions" },
2854 2855
    { "show_private_data", OPT_BOOL, {(void*)&show_private_data}, "show private data" },
    { "private",           OPT_BOOL, {(void*)&show_private_data}, "same as show_private_data" },
2856
    { "bitexact", OPT_BOOL, {&do_bitexact}, "force bitexact output" },
2857
    { "read_intervals", HAS_ARG, {.func_arg = opt_read_intervals}, "set read intervals", "read_intervals" },
2858
    { "default", HAS_ARG | OPT_AUDIO | OPT_VIDEO | OPT_EXPERT, {.func_arg = opt_default}, "generic catch all option", "" },
2859
    { "i", HAS_ARG, {.func_arg = opt_input_file_i}, "read specified file", "input_file"},
Stefano Sabatini's avatar
Stefano Sabatini committed
2860 2861 2862
    { NULL, },
};

2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879
static inline int check_section_show_entries(int section_id)
{
    int *id;
    struct section *section = &sections[section_id];
    if (sections[section_id].show_all_entries || sections[section_id].entries_to_show)
        return 1;
    for (id = section->children_ids; *id != -1; id++)
        if (check_section_show_entries(*id))
            return 1;
    return 0;
}

#define SET_DO_SHOW(id, varname) do {                                   \
        if (check_section_show_entries(SECTION_ID_##id))                \
            do_show_##varname = 1;                                      \
    } while (0)

Stefano Sabatini's avatar
Stefano Sabatini committed
2880 2881
int main(int argc, char **argv)
{
2882 2883 2884 2885
    const Writer *w;
    WriterContext *wctx;
    char *buf;
    char *w_name = NULL, *w_args = NULL;
2886
    int ret, i;
2887

2888
    av_log_set_flags(AV_LOG_SKIP_REPEATED);
2889
    register_exit(ffprobe_cleanup);
2890

2891
    options = real_options;
2892
    parse_loglevel(argc, argv, options);
Stefano Sabatini's avatar
Stefano Sabatini committed
2893
    av_register_all();
2894
    avformat_network_init();
2895
    init_opts();
2896 2897 2898
#if CONFIG_AVDEVICE
    avdevice_register_all();
#endif
Stefano Sabatini's avatar
Stefano Sabatini committed
2899

2900
    show_banner(argc, argv, options);
2901
    parse_options(NULL, argc, argv, options, opt_input_file);
Stefano Sabatini's avatar
Stefano Sabatini committed
2902

2903
    /* mark things to show, based on -show_entries */
2904
    SET_DO_SHOW(CHAPTERS, chapters);
2905 2906 2907 2908 2909 2910
    SET_DO_SHOW(ERROR, error);
    SET_DO_SHOW(FORMAT, format);
    SET_DO_SHOW(FRAMES, frames);
    SET_DO_SHOW(LIBRARY_VERSIONS, library_versions);
    SET_DO_SHOW(PACKETS, packets);
    SET_DO_SHOW(PROGRAM_VERSION, program_version);
2911
    SET_DO_SHOW(PROGRAMS, programs);
2912 2913
    SET_DO_SHOW(STREAMS, streams);
    SET_DO_SHOW(STREAM_DISPOSITION, stream_disposition);
2914
    SET_DO_SHOW(PROGRAM_STREAM_DISPOSITION, stream_disposition);
2915

2916 2917 2918 2919 2920 2921
    SET_DO_SHOW(CHAPTER_TAGS, chapter_tags);
    SET_DO_SHOW(FORMAT_TAGS, format_tags);
    SET_DO_SHOW(FRAME_TAGS, frame_tags);
    SET_DO_SHOW(PROGRAM_TAGS, program_tags);
    SET_DO_SHOW(STREAM_TAGS, stream_tags);

2922 2923 2924 2925 2926 2927 2928 2929
    if (do_bitexact && (do_show_program_version || do_show_library_versions)) {
        av_log(NULL, AV_LOG_ERROR,
               "-bitexact and -show_program_version or -show_library_versions "
               "options are incompatible\n");
        ret = AVERROR(EINVAL);
        goto end;
    }

2930 2931 2932 2933
    writer_register_all();

    if (!print_format)
        print_format = av_strdup("default");
2934 2935 2936 2937
    if (!print_format) {
        ret = AVERROR(ENOMEM);
        goto end;
    }
2938 2939 2940 2941 2942 2943 2944 2945
    w_name = av_strtok(print_format, "=", &buf);
    w_args = buf;

    w = writer_get_by_name(w_name);
    if (!w) {
        av_log(NULL, AV_LOG_ERROR, "Unknown output format with name '%s'\n", w_name);
        ret = AVERROR(EINVAL);
        goto end;
Stefano Sabatini's avatar
Stefano Sabatini committed
2946 2947
    }

2948 2949
    if ((ret = writer_open(&wctx, w, w_args,
                           sections, FF_ARRAY_ELEMS(sections))) >= 0) {
2950 2951 2952
        if (w == &xml_writer)
            wctx->string_validation_utf8_flags |= AV_UTF8_FLAG_EXCLUDE_XML_INVALID_CONTROL_CODES;

2953
        writer_print_section_header(wctx, SECTION_ID_ROOT);
2954

2955 2956 2957 2958 2959 2960
        if (do_show_program_version)
            ffprobe_show_program_version(wctx);
        if (do_show_library_versions)
            ffprobe_show_library_versions(wctx);

        if (!input_filename &&
2961
            ((do_show_format || do_show_programs || do_show_streams || do_show_chapters || do_show_packets || do_show_error) ||
2962
             (!do_show_program_version && !do_show_library_versions))) {
2963 2964 2965 2966
            show_usage();
            av_log(NULL, AV_LOG_ERROR, "You have to specify one input file.\n");
            av_log(NULL, AV_LOG_ERROR, "Use -h to get full help or, even better, run 'man %s'.\n", program_name);
            ret = AVERROR(EINVAL);
2967
        } else if (input_filename) {
2968
            ret = probe_file(wctx, input_filename);
2969 2970 2971
            if (ret < 0 && do_show_error)
                show_error(wctx, ret);
        }
2972

2973
        writer_print_section_footer(wctx);
2974 2975
        writer_close(&wctx);
    }
2976

2977 2978
end:
    av_freep(&print_format);
2979
    av_freep(&read_intervals);
2980 2981

    uninit_opts();
2982 2983
    for (i = 0; i < FF_ARRAY_ELEMS(sections); i++)
        av_dict_free(&(sections[i].entries_to_show));
2984

2985 2986
    avformat_network_deinit();

2987
    return ret < 0;
Stefano Sabatini's avatar
Stefano Sabatini committed
2988
}