fftools/textformat: Extract and generalize textformat api from ffprobe.c

Signed-off-by: softworkz <softworkz@hotmail.com>
This commit is contained in:
softworkz
2025-04-11 03:06:27 +02:00
parent d79e4f6d0b
commit 97d680d450
12 changed files with 2411 additions and 0 deletions

View File

@ -0,0 +1,672 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "libavutil/mem.h"
#include "libavutil/avassert.h"
#include "libavutil/bprint.h"
#include "libavutil/error.h"
#include "libavutil/hash.h"
#include "libavutil/intreadwrite.h"
#include "libavutil/macros.h"
#include "libavutil/opt.h"
#include "avtextformat.h"
#define SECTION_ID_NONE -1
#define SHOW_OPTIONAL_FIELDS_AUTO -1
#define SHOW_OPTIONAL_FIELDS_NEVER 0
#define SHOW_OPTIONAL_FIELDS_ALWAYS 1
static const struct {
double bin_val;
double dec_val;
const char *bin_str;
const char *dec_str;
} si_prefixes[] = {
{ 1.0, 1.0, "", "" },
{ 1.024e3, 1e3, "Ki", "K" },
{ 1.048576e6, 1e6, "Mi", "M" },
{ 1.073741824e9, 1e9, "Gi", "G" },
{ 1.099511627776e12, 1e12, "Ti", "T" },
{ 1.125899906842624e15, 1e15, "Pi", "P" },
};
static const char *textcontext_get_formatter_name(void *p)
{
AVTextFormatContext *tctx = p;
return tctx->formatter->name;
}
#define OFFSET(x) offsetof(AVTextFormatContext, x)
static const AVOption textcontext_options[] = {
{ "string_validation", "set string validation mode",
OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
{ "sv", "set string validation mode",
OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" },
{ "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_IGNORE}, .unit = "sv" },
{ "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, .unit = "sv" },
{ "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_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="\xEF\xBF\xBD"}},
{ NULL }
};
static void *textcontext_child_next(void *obj, void *prev)
{
AVTextFormatContext *ctx = obj;
if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
return ctx->priv;
return NULL;
}
static const AVClass textcontext_class = {
.class_name = "AVTextContext",
.item_name = textcontext_get_formatter_name,
.option = textcontext_options,
.version = LIBAVUTIL_VERSION_INT,
.child_next = textcontext_child_next,
};
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]);
}
int avtext_context_close(AVTextFormatContext **ptctx)
{
AVTextFormatContext *tctx = *ptctx;
int i;
int ret = 0;
if (!tctx)
return EINVAL;
av_hash_freep(&tctx->hash);
av_hash_freep(&tctx->hash);
if (tctx->formatter->uninit)
tctx->formatter->uninit(tctx);
for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
av_bprint_finalize(&tctx->section_pbuf[i], NULL);
if (tctx->formatter->priv_class)
av_opt_free(tctx->priv);
av_freep(&tctx->priv);
av_opt_free(tctx);
av_freep(ptctx);
return ret;
}
int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
const struct AVTextFormatSection *sections, int nb_sections,
int show_value_unit,
int use_value_prefix,
int use_byte_value_binary_prefix,
int use_value_sexagesimal_format,
int show_optional_fields,
char *show_data_hash)
{
AVTextFormatContext *tctx;
int i, ret = 0;
if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
tctx->show_value_unit = show_value_unit;
tctx->use_value_prefix = use_value_prefix;
tctx->use_byte_value_binary_prefix = use_byte_value_binary_prefix;
tctx->use_value_sexagesimal_format = use_value_sexagesimal_format;
tctx->show_optional_fields = show_optional_fields;
if (nb_sections > SECTION_MAX_NB_SECTIONS) {
av_log(tctx, AV_LOG_ERROR, "The number of section definitions (%d) is larger than the maximum allowed (%d)\n", nb_sections, SECTION_MAX_NB_SECTIONS);
ret = AVERROR(EINVAL);
goto fail;
}
tctx->class = &textcontext_class;
tctx->formatter = formatter;
tctx->level = -1;
tctx->sections = sections;
tctx->nb_sections = nb_sections;
tctx->writer = writer_context;
av_opt_set_defaults(tctx);
if (formatter->priv_class) {
void *priv_ctx = tctx->priv;
*(const AVClass **)priv_ctx = formatter->priv_class;
av_opt_set_defaults(priv_ctx);
}
/* convert options to dictionary */
if (args) {
AVDictionary *opts = NULL;
const AVDictionaryEntry *opt = NULL;
if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) {
av_log(tctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to textformat context\n", args);
av_dict_free(&opts);
goto fail;
}
while ((opt = av_dict_iterate(opts, opt))) {
if ((ret = av_opt_set(tctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) {
av_log(tctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to textformat context\n",
opt->key, opt->value);
av_dict_free(&opts);
goto fail;
}
}
av_dict_free(&opts);
}
if (show_data_hash) {
if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) {
if (ret == AVERROR(EINVAL)) {
const char *n;
av_log(NULL, AV_LOG_ERROR, "Unknown hash algorithm '%s'\nKnown algorithms:", show_data_hash);
for (i = 0; (n = av_hash_names(i)); i++)
av_log(NULL, AV_LOG_ERROR, " %s", n);
av_log(NULL, AV_LOG_ERROR, "\n");
}
return ret;
}
}
/* validate replace string */
{
const uint8_t *p = tctx->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, tctx->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(tctx, AV_LOG_ERROR,
"Invalid UTF8 sequence %s found in string validation replace '%s'\n",
bp.str, tctx->string_validation_replacement);
return ret;
}
}
}
for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
if (tctx->formatter->init)
ret = tctx->formatter->init(tctx);
if (ret < 0)
goto fail;
*ptctx = tctx;
return 0;
fail:
avtext_context_close(&tctx);
return ret;
}
/* Temporary definitions during refactoring */
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";
void avtext_print_section_header(AVTextFormatContext *tctx,
const void *data,
int section_id)
{
tctx->level++;
av_assert0(tctx->level < SECTION_MAX_NB_LEVELS);
tctx->nb_item[tctx->level] = 0;
memset(tctx->nb_item_type[tctx->level], 0, sizeof(tctx->nb_item_type[tctx->level]));
tctx->section[tctx->level] = &tctx->sections[section_id];
if (tctx->formatter->print_section_header)
tctx->formatter->print_section_header(tctx, data);
}
void avtext_print_section_footer(AVTextFormatContext *tctx)
{
int section_id = tctx->section[tctx->level]->id;
int parent_section_id = tctx->level ?
tctx->section[tctx->level-1]->id : SECTION_ID_NONE;
if (parent_section_id != SECTION_ID_NONE) {
tctx->nb_item[tctx->level - 1]++;
tctx->nb_item_type[tctx->level - 1][section_id]++;
}
if (tctx->formatter->print_section_footer)
tctx->formatter->print_section_footer(tctx);
tctx->level--;
}
void avtext_print_integer(AVTextFormatContext *tctx,
const char *key, int64_t val)
{
const struct AVTextFormatSection *section = tctx->section[tctx->level];
if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
tctx->formatter->print_integer(tctx, key, val);
tctx->nb_item[tctx->level]++;
}
}
static inline int validate_string(AVTextFormatContext *tctx, 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 = src; *p;) {
uint32_t code;
int invalid = 0;
const uint8_t *p0 = p;
if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) {
AVBPrint bp;
av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
bprint_bytes(&bp, p0, p-p0);
av_log(tctx, AV_LOG_DEBUG,
"Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src);
invalid = 1;
}
if (invalid) {
invalid_chars_nb++;
switch (tctx->string_validation) {
case AV_TEXTFORMAT_STRING_VALIDATION_FAIL:
av_log(tctx, AV_LOG_ERROR,
"Invalid UTF-8 sequence found in string '%s'\n", src);
ret = AVERROR_INVALIDDATA;
goto end;
break;
case AV_TEXTFORMAT_STRING_VALIDATION_REPLACE:
av_bprintf(&dstbuf, "%s", tctx->string_validation_replacement);
break;
}
}
if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE)
av_bprint_append_data(&dstbuf, p0, p-p0);
}
if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) {
av_log(tctx, AV_LOG_WARNING,
"%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n",
invalid_chars_nb, src, tctx->string_validation_replacement);
}
end:
av_bprint_finalize(&dstbuf, dstp);
return ret;
}
struct unit_value {
union { double d; int64_t i; } val;
const char *unit;
};
static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
{
double vald;
int64_t vali;
int show_float = 0;
if (uv.unit == unit_second_str) {
vald = uv.val.d;
show_float = 1;
} else {
vald = vali = uv.val.i;
}
if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) {
double secs;
int hours, mins;
secs = vald;
mins = (int)secs / 60;
secs = secs - mins * 60;
hours = mins / 60;
mins %= 60;
snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs);
} else {
const char *prefix_string = "";
if (tctx->use_value_prefix && vald > 1) {
int64_t index;
if (uv.unit == unit_byte_str && tctx->use_byte_value_binary_prefix) {
index = (int64_t) (log2(vald)) / 10;
index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
vald /= si_prefixes[index].bin_val;
prefix_string = si_prefixes[index].bin_str;
} else {
index = (int64_t) (log10(vald)) / 3;
index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
vald /= si_prefixes[index].dec_val;
prefix_string = si_prefixes[index].dec_str;
}
vali = vald;
}
if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald))
snprintf(buf, buf_size, "%f", vald);
else
snprintf(buf, buf_size, "%"PRId64, vali);
av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || tctx->show_value_unit ? " " : "",
prefix_string, tctx->show_value_unit ? uv.unit : "");
}
return buf;
}
void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit)
{
char val_str[128];
struct unit_value uv;
uv.val.i = value;
uv.unit = unit;
avtext_print_string(tctx, key, value_string(tctx, val_str, sizeof(val_str), uv), 0);
}
int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags)
{
const struct AVTextFormatSection *section = tctx->section[tctx->level];
int ret = 0;
if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER ||
(tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
&& (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
&& !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS)))
return 0;
if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) {
if (flags & AV_TEXTFORMAT_PRINT_STRING_VALIDATE) {
char *key1 = NULL, *val1 = NULL;
ret = validate_string(tctx, &key1, key);
if (ret < 0) goto end;
ret = validate_string(tctx, &val1, val);
if (ret < 0) goto end;
tctx->formatter->print_string(tctx, key1, val1);
end:
if (ret < 0) {
av_log(tctx, 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 {
tctx->formatter->print_string(tctx, key, val);
}
tctx->nb_item[tctx->level]++;
}
return ret;
}
void avtext_print_rational(AVTextFormatContext *tctx,
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);
avtext_print_string(tctx, key, buf.str, 0);
}
void avtext_print_time(AVTextFormatContext *tctx, const char *key,
int64_t ts, const AVRational *time_base, int is_duration)
{
char buf[128];
if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
} else {
double d = ts * av_q2d(*time_base);
struct unit_value uv;
uv.val.d = d;
uv.unit = unit_second_str;
value_string(tctx, buf, sizeof(buf), uv);
avtext_print_string(tctx, key, buf, 0);
}
}
void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration)
{
if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) {
avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL);
} else {
avtext_print_integer(tctx, key, ts);
}
}
void avtext_print_data(AVTextFormatContext *tctx, const char *name,
const 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;
}
avtext_print_string(tctx, name, bp.str, 0);
av_bprint_finalize(&bp, NULL);
}
void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name,
const uint8_t *data, int size)
{
char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
if (!tctx->hash)
return;
av_hash_init(tctx->hash);
av_hash_update(tctx->hash, data, size);
snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
p = buf + strlen(buf);
av_hash_final_hex(tctx->hash, p, buf + sizeof(buf) - p);
avtext_print_string(tctx, name, buf, 0);
}
void avtext_print_integers(AVTextFormatContext *tctx, const char *name,
uint8_t *data, int size, const char *format,
int columns, int bytes, int offset_add)
{
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, columns);
for (i = 0; i < l; i++) {
if (bytes == 1) av_bprintf(&bp, format, *data);
else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data));
else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data));
data += bytes;
size --;
}
av_bprintf(&bp, "\n");
offset += offset_add;
}
avtext_print_string(tctx, name, bp.str, 0);
av_bprint_finalize(&bp, NULL);
}
static const char *writercontext_get_writer_name(void *p)
{
AVTextWriterContext *wctx = p;
return wctx->writer->name;
}
static void *writercontext_child_next(void *obj, void *prev)
{
AVTextFormatContext *ctx = obj;
if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv)
return ctx->priv;
return NULL;
}
static const AVClass textwriter_class = {
.class_name = "AVTextWriterContext",
.item_name = writercontext_get_writer_name,
.version = LIBAVUTIL_VERSION_INT,
.child_next = writercontext_child_next,
};
int avtextwriter_context_close(AVTextWriterContext **pwctx)
{
AVTextWriterContext *wctx = *pwctx;
int ret = 0;
if (!wctx)
return EINVAL;
if (wctx->writer->uninit)
wctx->writer->uninit(wctx);
if (wctx->writer->priv_class)
av_opt_free(wctx->priv);
av_freep(&wctx->priv);
av_freep(pwctx);
return ret;
}
int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer)
{
AVTextWriterContext *wctx;
int ret = 0;
if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (!(wctx->priv = av_mallocz(writer->priv_size))) {
ret = AVERROR(ENOMEM);
goto fail;
}
if (writer->priv_class) {
void *priv_ctx = wctx->priv;
*(const AVClass **)priv_ctx = writer->priv_class;
av_opt_set_defaults(priv_ctx);
}
wctx->class = &textwriter_class;
wctx->writer = writer;
av_opt_set_defaults(wctx);
if (wctx->writer->init)
ret = wctx->writer->init(wctx);
if (ret < 0)
goto fail;
*pwctx = wctx;
return 0;
fail:
avtextwriter_context_close(&wctx);
return ret;
}
static const AVTextFormatter *registered_formatters[7+1];
static void formatters_register_all(void)
{
static int initialized;
if (initialized)
return;
initialized = 1;
registered_formatters[0] = &avtextformatter_default;
registered_formatters[1] = &avtextformatter_compact;
registered_formatters[2] = &avtextformatter_csv;
registered_formatters[3] = &avtextformatter_flat;
registered_formatters[4] = &avtextformatter_ini;
registered_formatters[5] = &avtextformatter_json;
registered_formatters[6] = &avtextformatter_xml;
}
const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
{
formatters_register_all();
for (int i = 0; registered_formatters[i]; i++)
if (!strcmp(registered_formatters[i]->name, name))
return registered_formatters[i];
return NULL;
}

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#ifndef FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H
#define FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H
#include <stddef.h>
#include <stdint.h>
#include "libavutil/attributes.h"
#include "libavutil/dict.h"
#include "libavformat/avio.h"
#include "libavutil/bprint.h"
#include "libavutil/rational.h"
#include "libavutil/hash.h"
#include "avtextwriters.h"
#define SECTION_MAX_NB_CHILDREN 11
typedef struct AVTextFormatSection {
int id; ///< unique id identifying a section
const char *name;
#define AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level
#define AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type
#define AV_TEXTFORMAT_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.
#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements
#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type
int flags;
const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1
const char *element_name; ///< name of the contained element, if provided
const char *unique_name; ///< unique section name, in case the name is ambiguous
AVDictionary *entries_to_show;
const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined
int show_all_entries;
} AVTextFormatSection;
typedef struct AVTextFormatContext AVTextFormatContext;
#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
typedef enum {
AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
AV_TEXTFORMAT_STRING_VALIDATION_REPLACE,
AV_TEXTFORMAT_STRING_VALIDATION_IGNORE,
AV_TEXTFORMAT_STRING_VALIDATION_NB
} StringValidation;
typedef struct AVTextFormatter {
const AVClass *priv_class; ///< private class of the formatter, if any
int priv_size; ///< private size for the formatter context
const char *name;
int (*init) (AVTextFormatContext *tctx);
void (*uninit)(AVTextFormatContext *tctx);
void (*print_section_header)(AVTextFormatContext *tctx, const void *data);
void (*print_section_footer)(AVTextFormatContext *tctx);
void (*print_integer) (AVTextFormatContext *tctx, const char *, int64_t);
void (*print_rational) (AVTextFormatContext *tctx, AVRational *q, char *sep);
void (*print_string) (AVTextFormatContext *tctx, const char *, const char *);
int flags; ///< a combination or AV_TEXTFORMAT__FLAG_*
} AVTextFormatter;
#define SECTION_MAX_NB_LEVELS 12
#define SECTION_MAX_NB_SECTIONS 100
struct AVTextFormatContext {
const AVClass *class; ///< class of the formatter
const AVTextFormatter *formatter; ///< the AVTextFormatter of which this is an instance
AVTextWriterContext *writer; ///< the AVTextWriterContext
char *name; ///< name of this formatter instance
void *priv; ///< private data for use by the filter
const struct AVTextFormatSection *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];
unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS];
/** section per each level */
const struct AVTextFormatSection *section[SECTION_MAX_NB_LEVELS];
AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
/// used by various formatters
int show_optional_fields;
int show_value_unit;
int use_value_prefix;
int use_byte_value_binary_prefix;
int use_value_sexagesimal_format;
struct AVHashContext *hash;
int string_validation;
char *string_validation_replacement;
unsigned int string_validation_utf8_flags;
};
#define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1
#define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2
int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
const struct AVTextFormatSection *sections, int nb_sections,
int show_value_unit,
int use_value_prefix,
int use_byte_value_binary_prefix,
int use_value_sexagesimal_format,
int show_optional_fields,
char *show_data_hash);
int avtext_context_close(AVTextFormatContext **tctx);
void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id);
void avtext_print_section_footer(AVTextFormatContext *tctx);
void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val);
int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags);
void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit);
void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep);
void avtext_print_time(AVTextFormatContext *tctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration);
void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration);
void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size);
void avtext_print_integers(AVTextFormatContext *tctx, const char *name, uint8_t *data, int size,
const char *format, int columns, int bytes, int offset_add);
const AVTextFormatter *avtext_get_formatter_by_name(const char *name);
extern const AVTextFormatter avtextformatter_default;
extern const AVTextFormatter avtextformatter_compact;
extern const AVTextFormatter avtextformatter_csv;
extern const AVTextFormatter avtextformatter_flat;
extern const AVTextFormatter avtextformatter_ini;
extern const AVTextFormatter avtextformatter_json;
extern const AVTextFormatter avtextformatter_xml;
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#ifndef FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H
#define FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H
#include <stddef.h>
#include <stdint.h>
#include "libavutil/attributes.h"
#include "libavutil/dict.h"
#include "libavformat/avio.h"
#include "libavutil/bprint.h"
#include "libavutil/rational.h"
#include "libavutil/hash.h"
typedef struct AVTextWriterContext AVTextWriterContext;
typedef struct AVTextWriter {
const AVClass *priv_class; ///< private class of the writer, if any
int priv_size; ///< private size for the writer private class
const char *name;
int (* init)(AVTextWriterContext *wctx);
void (* uninit)(AVTextWriterContext *wctx);
void (* writer_w8)(AVTextWriterContext *wctx, int b);
void (* writer_put_str)(AVTextWriterContext *wctx, const char *str);
void (* writer_printf)(AVTextWriterContext *wctx, const char *fmt, ...);
} AVTextWriter;
typedef struct AVTextWriterContext {
const AVClass *class; ///< class of the writer
const AVTextWriter *writer;
const char *name;
void *priv; ///< private data for use by the writer
} AVTextWriterContext;
int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer);
int avtextwriter_context_close(AVTextWriterContext **pwctx);
int avtextwriter_create_stdout(AVTextWriterContext **pwctx);
int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit);
int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit);
int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer);
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */

View File

@ -0,0 +1,282 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/error.h>
#include <libavutil/macros.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* Compact output */
/**
* Apply C-language-like string escaping.
*/
static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
{
const char *p;
for (p = src; *p; p++) {
switch (*p) {
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 '\\': av_bprintf(dst, "%s", "\\\\"); break;
default:
if (*p == sep)
av_bprint_chars(dst, '\\', 1);
av_bprint_chars(dst, *p, 1);
}
}
return dst->str;
}
/**
* Quote fields containing special characters, check RFC4180.
*/
static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
{
char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
int needs_quoting = !!src[strcspn(src, meta_chars)];
if (needs_quoting)
av_bprint_chars(dst, '"', 1);
for (; *src; src++) {
if (*src == '"')
av_bprint_chars(dst, '"', 1);
av_bprint_chars(dst, *src, 1);
}
if (needs_quoting)
av_bprint_chars(dst, '"', 1);
return dst->str;
}
static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
{
return src;
}
typedef struct CompactContext {
const AVClass *class;
char *item_sep_str;
char item_sep;
int nokey;
int print_section;
char *escape_mode_str;
const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
int nested_section[SECTION_MAX_NB_LEVELS];
int has_nested_elems[SECTION_MAX_NB_LEVELS];
int terminate_line[SECTION_MAX_NB_LEVELS];
} CompactContext;
#undef OFFSET
#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="|"}, 0, 0 },
{"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
{"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
{"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
{"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(compact);
static av_cold int compact_init(AVTextFormatContext *wctx)
{
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;
}
static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
{
CompactContext *compact = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
compact->terminate_line[wctx->level] = 1;
compact->has_nested_elems[wctx->level] = 0;
av_bprint_clear(&wctx->section_pbuf[wctx->level]);
if (parent_section &&
(section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
(!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
!(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
/* define a prefix for elements not contained in an array or
in a wrapper, or for array elements with a type */
const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
compact->nested_section[wctx->level] = 1;
compact->has_nested_elems[wctx->level-1] = 1;
av_bprintf(section_pbuf, "%s%s",
wctx->section_pbuf[wctx->level-1].str, element_name);
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
// add /TYPE to prefix
av_bprint_chars(section_pbuf, '/', 1);
// normalize section type, replace special characters and lower case
for (const char *p = section->get_type(data); *p; p++) {
char c =
(*p >= '0' && *p <= '9') ||
(*p >= 'a' && *p <= 'z') ||
(*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
av_bprint_chars(section_pbuf, c, 1);
}
}
av_bprint_chars(section_pbuf, ':', 1);
wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
} else {
if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) &&
wctx->level && wctx->nb_item[wctx->level-1])
writer_w8(wctx, compact->item_sep);
if (compact->print_section &&
!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
writer_printf(wctx, "%s%c", section->name, compact->item_sep);
}
}
static void compact_print_section_footer(AVTextFormatContext *wctx)
{
CompactContext *compact = wctx->priv;
if (!compact->nested_section[wctx->level] &&
compact->terminate_line[wctx->level] &&
!(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
writer_w8(wctx, '\n');
}
static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
{
CompactContext *compact = wctx->priv;
AVBPrint buf;
if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
if (!compact->nokey)
writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
av_bprint_finalize(&buf, NULL);
}
static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
CompactContext *compact = wctx->priv;
if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
if (!compact->nokey)
writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
writer_printf(wctx, "%"PRId64, value);
}
const AVTextFormatter avtextformatter_compact = {
.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 = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
.priv_class = &compact_class,
};
/* CSV output */
#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=","}, 0, 0 },
{"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
{"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
{"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
{"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(csv);
const AVTextFormatter avtextformatter_csv = {
.name = "csv",
.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 = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
.priv_class = &csv_class,
};

View File

@ -0,0 +1,145 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* Default output */
typedef struct DefaultContext {
const AVClass *class;
int nokey;
int noprint_wrappers;
int nested_section[SECTION_MAX_NB_LEVELS];
} DefaultContext;
#undef OFFSET
#define OFFSET(x) offsetof(DefaultContext, x)
static const AVOption default_options[] = {
{ "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{ "nw", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{ "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{ "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(default);
/* 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++)
dst[i] = av_toupper(src[i]);
dst[i] = 0;
return dst;
}
static void default_print_section_header(AVTextFormatContext *wctx, const void *data)
{
DefaultContext *def = wctx->priv;
char buf[32];
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
av_bprint_clear(&wctx->section_pbuf[wctx->level]);
if (parent_section &&
!(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) {
def->nested_section[wctx->level] = 1;
av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:",
wctx->section_pbuf[wctx->level-1].str,
upcase_string(buf, sizeof(buf),
av_x_if_null(section->element_name, section->name)));
}
if (def->noprint_wrappers || def->nested_section[wctx->level])
return;
if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name));
}
static void default_print_section_footer(AVTextFormatContext *wctx)
{
DefaultContext *def = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
char buf[32];
if (def->noprint_wrappers || def->nested_section[wctx->level])
return;
if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name));
}
static void default_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
{
DefaultContext *def = wctx->priv;
if (!def->nokey)
writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
writer_printf(wctx, "%s\n", value);
}
static void default_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
DefaultContext *def = wctx->priv;
if (!def->nokey)
writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
writer_printf(wctx, "%"PRId64"\n", value);
}
const AVTextFormatter avtextformatter_default = {
.name = "default",
.priv_size = sizeof(DefaultContext),
.print_section_header = default_print_section_header,
.print_section_footer = default_print_section_footer,
.print_integer = default_print_int,
.print_string = default_print_str,
.flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
.priv_class = &default_class,
};

View File

@ -0,0 +1,174 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/error.h>
#include <libavutil/macros.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* 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="."}, 0, 0 },
{"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 },
{"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(flat);
static av_cold int flat_init(AVTextFormatContext *wctx)
{
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];
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;
case '`': av_bprintf(dst, "%s", "\\`"); break;
case '$': av_bprintf(dst, "%s", "\\$"); break;
default: av_bprint_chars(dst, *p, 1); break;
}
}
return dst->str;
}
static void flat_print_section_header(AVTextFormatContext *wctx, const void *data)
{
FlatContext *flat = wctx->priv;
AVBPrint *buf = &wctx->section_pbuf[wctx->level];
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
/* build section header */
av_bprint_clear(buf);
if (!parent_section)
return;
av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
if (flat->hierarchical ||
!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str);
if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
wctx->nb_item_type[wctx->level-1][section->id] :
wctx->nb_item[wctx->level-1];
av_bprintf(buf, "%d%s", n, flat->sep_str);
}
}
}
static void flat_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value);
}
static void flat_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
{
FlatContext *flat = wctx->priv;
AVBPrint buf;
writer_put_str(wctx, wctx->section_pbuf[wctx->level].str);
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep));
av_bprint_clear(&buf);
writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value));
av_bprint_finalize(&buf, NULL);
}
const AVTextFormatter avtextformatter_flat = {
.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 = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
.priv_class = &flat_class,
};

160
fftools/textformat/tf_ini.c Normal file
View File

@ -0,0 +1,160 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* Default output */
typedef struct DefaultContext {
const AVClass *class;
int nokey;
int noprint_wrappers;
int nested_section[SECTION_MAX_NB_LEVELS];
} DefaultContext;
/* INI format output */
typedef struct INIContext {
const AVClass *class;
int hierarchical;
} INIContext;
#undef OFFSET
#define OFFSET(x) offsetof(INIContext, x)
static const AVOption ini_options[] = {
{"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(ini);
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;
}
static void ini_print_section_header(AVTextFormatContext *wctx, const void *data)
{
INIContext *ini = wctx->priv;
AVBPrint *buf = &wctx->section_pbuf[wctx->level];
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
av_bprint_clear(buf);
if (!parent_section) {
writer_put_str(wctx, "# ffprobe output\n\n");
return;
}
if (wctx->nb_item[wctx->level-1])
writer_w8(wctx, '\n');
av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str);
if (ini->hierarchical ||
!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) {
av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name);
if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ?
wctx->nb_item_type[wctx->level-1][section->id] :
wctx->nb_item[wctx->level-1];
av_bprintf(buf, ".%d", n);
}
}
if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER)))
writer_printf(wctx, "[%s]\n", buf->str);
}
static void ini_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
{
AVBPrint buf;
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
writer_printf(wctx, "%s=", ini_escape_str(&buf, key));
av_bprint_clear(&buf);
writer_printf(wctx, "%s\n", ini_escape_str(&buf, value));
av_bprint_finalize(&buf, NULL);
}
static void ini_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
writer_printf(wctx, "%s=%"PRId64"\n", key, value);
}
const AVTextFormatter avtextformatter_ini = {
.name = "ini",
.priv_size = sizeof(INIContext),
.print_section_header = ini_print_section_header,
.print_integer = ini_print_int,
.print_string = ini_print_str,
.flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
.priv_class = &ini_class,
};

View File

@ -0,0 +1,215 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* JSON output */
typedef struct JSONContext {
const AVClass *class;
int indent_level;
int compact;
const char *item_sep, *item_start_end;
} JSONContext;
#undef OFFSET
#define OFFSET(x) offsetof(JSONContext, x)
static const AVOption json_options[]= {
{ "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{ "c", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{ NULL }
};
DEFINE_FORMATTER_CLASS(json);
static av_cold int json_init(AVTextFormatContext *wctx)
{
JSONContext *json = wctx->priv;
json->item_sep = json->compact ? ", " : ",\n";
json->item_start_end = json->compact ? " " : "\n";
return 0;
}
static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx)
{
static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0};
static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0};
const char *p;
for (p = src; *p; p++) {
char *s = strchr(json_escape, *p);
if (s) {
av_bprint_chars(dst, '\\', 1);
av_bprint_chars(dst, json_subst[s - json_escape], 1);
} else if ((unsigned char)*p < 32) {
av_bprintf(dst, "\\u00%02x", *p & 0xff);
} else {
av_bprint_chars(dst, *p, 1);
}
}
return dst->str;
}
#define JSON_INDENT() writer_printf(wctx, "%*c", json->indent_level * 4, ' ')
static void json_print_section_header(AVTextFormatContext *wctx, const void *data)
{
JSONContext *json = wctx->priv;
AVBPrint buf;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
if (wctx->level && wctx->nb_item[wctx->level-1])
writer_put_str(wctx, ",\n");
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) {
writer_put_str(wctx, "{\n");
json->indent_level++;
} else {
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
json_escape_str(&buf, section->name, wctx);
JSON_INDENT();
json->indent_level++;
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
writer_printf(wctx, "\"%s\": [\n", buf.str);
} else if (parent_section && !(parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) {
writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end);
} else {
writer_printf(wctx, "{%s", json->item_start_end);
/* this is required so the parser can distinguish between packets and frames */
if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE) {
if (!json->compact)
JSON_INDENT();
writer_printf(wctx, "\"type\": \"%s\"", section->name);
wctx->nb_item[wctx->level]++;
}
}
av_bprint_finalize(&buf, NULL);
}
}
static void json_print_section_footer(AVTextFormatContext *wctx)
{
JSONContext *json = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
if (wctx->level == 0) {
json->indent_level--;
writer_put_str(wctx, "\n}\n");
} else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) {
writer_w8(wctx, '\n');
json->indent_level--;
JSON_INDENT();
writer_w8(wctx, ']');
} else {
writer_put_str(wctx, json->item_start_end);
json->indent_level--;
if (!json->compact)
JSON_INDENT();
writer_w8(wctx, '}');
}
}
static inline void json_print_item_str(AVTextFormatContext *wctx,
const char *key, const char *value)
{
AVBPrint buf;
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx));
av_bprint_clear(&buf);
writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx));
av_bprint_finalize(&buf, NULL);
}
static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
{
JSONContext *json = wctx->priv;
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
writer_put_str(wctx, json->item_sep);
if (!json->compact)
JSON_INDENT();
json_print_item_str(wctx, key, value);
}
static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
JSONContext *json = wctx->priv;
const struct AVTextFormatSection *parent_section = wctx->level ?
wctx->section[wctx->level-1] : NULL;
AVBPrint buf;
if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE))
writer_put_str(wctx, json->item_sep);
if (!json->compact)
JSON_INDENT();
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
writer_printf(wctx, "\"%s\": %"PRId64, json_escape_str(&buf, key, wctx), value);
av_bprint_finalize(&buf, NULL);
}
const AVTextFormatter avtextformatter_json = {
.name = "json",
.priv_size = sizeof(JSONContext),
.init = json_init,
.print_section_header = json_print_section_header,
.print_section_footer = json_print_section_footer,
.print_integer = json_print_int,
.print_string = json_print_str,
.flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
.priv_class = &json_class,
};

221
fftools/textformat/tf_xml.c Normal file
View File

@ -0,0 +1,221 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "avtextformat.h"
#include <libavutil/mem.h>
#include <libavutil/avassert.h>
#include <libavutil/bprint.h>
#include <libavutil/error.h>
#include <libavutil/macros.h>
#include <libavutil/opt.h>
#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
#define DEFINE_FORMATTER_CLASS(name) \
static const char *name##_get_name(void *ctx) \
{ \
return #name ; \
} \
static const AVClass name##_class = { \
.class_name = #name, \
.item_name = name##_get_name, \
.option = name##_options \
}
/* XML output */
typedef struct XMLContext {
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[] = {
{"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
{NULL},
};
DEFINE_FORMATTER_CLASS(xml);
static av_cold int xml_init(AVTextFormatContext *wctx)
{
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); \
return AVERROR(EINVAL); \
}
////CHECK_COMPLIANCE(show_private_data, "private");
CHECK_COMPLIANCE(wctx->show_value_unit, "unit");
CHECK_COMPLIANCE(wctx->use_value_prefix, "prefix");
}
return 0;
}
#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ')
static void xml_print_section_header(AVTextFormatContext *wctx, const void *data)
{
XMLContext *xml = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
const struct AVTextFormatSection *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\"";
writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
writer_printf(wctx, "<%sffprobe%s>\n",
xml->fully_qualified ? "ffprobe:" : "",
xml->fully_qualified ? qual : "");
return;
}
if (xml->within_tag) {
xml->within_tag = 0;
writer_put_str(wctx, ">\n");
}
if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) &&
wctx->level && wctx->nb_item[wctx->level-1])
writer_w8(wctx, '\n');
xml->indent_level++;
if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) {
XML_INDENT(); writer_printf(wctx, "<%s", section->name);
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
AVBPrint buf;
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
av_bprint_escape(&buf, section->get_type(data), NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
writer_printf(wctx, " type=\"%s\"", buf.str);
}
writer_printf(wctx, ">\n", section->name);
} else {
XML_INDENT(); writer_printf(wctx, "<%s ", section->name);
xml->within_tag = 1;
}
}
static void xml_print_section_footer(AVTextFormatContext *wctx)
{
XMLContext *xml = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
if (wctx->level == 0) {
writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : "");
} else if (xml->within_tag) {
xml->within_tag = 0;
writer_put_str(wctx, "/>\n");
xml->indent_level--;
} else {
XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name);
xml->indent_level--;
}
}
static void xml_print_value(AVTextFormatContext *wctx, const char *key,
const char *str, int64_t num, const int is_int)
{
AVBPrint buf;
XMLContext *xml = wctx->priv;
const struct AVTextFormatSection *section = wctx->section[wctx->level];
av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) {
xml->indent_level++;
XML_INDENT();
av_bprint_escape(&buf, key, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
writer_printf(wctx, "<%s key=\"%s\"",
section->element_name, buf.str);
av_bprint_clear(&buf);
if (is_int) {
writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num);
} else {
av_bprint_escape(&buf, str, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
writer_printf(wctx, " value=\"%s\"/>\n", buf.str);
}
xml->indent_level--;
} else {
if (wctx->nb_item[wctx->level])
writer_w8(wctx, ' ');
if (is_int) {
writer_printf(wctx, "%s=\"%"PRId64"\"", key, num);
} else {
av_bprint_escape(&buf, str, NULL,
AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES);
writer_printf(wctx, "%s=\"%s\"", key, buf.str);
}
}
av_bprint_finalize(&buf, NULL);
}
static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) {
xml_print_value(wctx, key, value, 0, 0);
}
static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
{
xml_print_value(wctx, key, NULL, value, 1);
}
const AVTextFormatter avtextformatter_xml = {
.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,
.flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT,
.priv_class = &xml_class,
};

View File

@ -0,0 +1,129 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include "avtextwriters.h"
#include "libavutil/opt.h"
/* AVIO Writer */
# define WRITER_NAME "aviowriter"
typedef struct IOWriterContext {
const AVClass *class;
AVIOContext *avio_context;
int close_on_uninit;
} IOWriterContext;
static const char *iowriter_get_name(void *ctx)
{
return WRITER_NAME;
}
static const AVClass iowriter_class = {
.class_name = WRITER_NAME,
.item_name = iowriter_get_name,
};
static av_cold void iowriter_uninit(AVTextWriterContext *wctx)
{
IOWriterContext *ctx = wctx->priv;
if (ctx->close_on_uninit && ctx->avio_context) {
avio_flush(ctx->avio_context);
avio_close(ctx->avio_context);
}
}
static void io_w8(AVTextWriterContext *wctx, int b)
{
IOWriterContext *ctx = wctx->priv;
avio_w8(ctx->avio_context, b);
}
static void io_put_str(AVTextWriterContext *wctx, const char *str)
{
IOWriterContext *ctx = wctx->priv;
avio_write(ctx->avio_context, str, strlen(str));
}
static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...)
{
IOWriterContext *ctx = wctx->priv;
va_list ap;
va_start(ap, fmt);
avio_vprintf(ctx->avio_context, fmt, ap);
va_end(ap);
}
const AVTextWriter avtextwriter_avio = {
.name = WRITER_NAME,
.priv_size = sizeof(IOWriterContext),
.uninit = iowriter_uninit,
.priv_class = &iowriter_class,
.writer_put_str = io_put_str,
.writer_printf = io_printf,
.writer_w8 = io_w8
};
int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit)
{
IOWriterContext *ctx;
int ret;
ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
if (ret < 0)
return ret;
ctx = (*pwctx)->priv;
if ((ret = avio_open(&ctx->avio_context, output_filename, AVIO_FLAG_WRITE)) < 0) {
av_log(ctx, AV_LOG_ERROR,
"Failed to open output '%s' with error: %s\n", output_filename, av_err2str(ret));
avtextwriter_context_close(pwctx);
return ret;
}
ctx->close_on_uninit = close_on_uninit;
return ret;
}
int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
{
IOWriterContext *ctx;
int ret;
ret = avtextwriter_context_open(pwctx, &avtextwriter_avio);
if (ret < 0)
return ret;
ctx = (*pwctx)->priv;
ctx->avio_context = avio_ctx;
ctx->close_on_uninit = close_on_uninit;
return ret;
}

View File

@ -0,0 +1,92 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <limits.h>
#include <stdarg.h>
#include "avtextwriters.h"
#include "libavutil/opt.h"
#include "libavutil/bprint.h"
/* Buffer Writer */
# define WRITER_NAME "bufferwriter"
typedef struct BufferWriterContext {
const AVClass *class;
AVBPrint *buffer;
} BufferWriterContext;
static const char *bufferwriter_get_name(void *ctx)
{
return WRITER_NAME;
}
static const AVClass bufferwriter_class = {
.class_name = WRITER_NAME,
.item_name = bufferwriter_get_name,
};
static void buffer_w8(AVTextWriterContext *wctx, int b)
{
BufferWriterContext *ctx = wctx->priv;
av_bprintf(ctx->buffer, "%c", b);
}
static void buffer_put_str(AVTextWriterContext *wctx, const char *str)
{
BufferWriterContext *ctx = wctx->priv;
av_bprintf(ctx->buffer, "%s", str);
}
static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...)
{
BufferWriterContext *ctx = wctx->priv;
va_list vargs;
va_start(vargs, fmt);
av_vbprintf(ctx->buffer, fmt, vargs);
va_end(vargs);
}
const AVTextWriter avtextwriter_buffer = {
.name = WRITER_NAME,
.priv_size = sizeof(BufferWriterContext),
.priv_class = &bufferwriter_class,
.writer_put_str = buffer_put_str,
.writer_printf = buffer_printf,
.writer_w8 = buffer_w8
};
int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer)
{
BufferWriterContext *ctx;
int ret;
ret = avtextwriter_context_open(pwctx, &avtextwriter_buffer);
if (ret < 0)
return ret;
ctx = (*pwctx)->priv;
ctx->buffer = buffer;
return ret;
}

View File

@ -0,0 +1,82 @@
/*
* Copyright (c) The FFmpeg developers
*
* 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
*/
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include "avtextwriters.h"
#include "libavutil/opt.h"
/* STDOUT Writer */
# define WRITER_NAME "stdoutwriter"
typedef struct StdOutWriterContext {
const AVClass *class;
} StdOutWriterContext;
static const char *stdoutwriter_get_name(void *ctx)
{
return WRITER_NAME;
}
static const AVClass stdoutwriter_class = {
.class_name = WRITER_NAME,
.item_name = stdoutwriter_get_name,
};
static inline void stdout_w8(AVTextWriterContext *wctx, int b)
{
printf("%c", b);
}
static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str)
{
printf("%s", str);
}
static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
}
static const AVTextWriter avtextwriter_stdout = {
.name = WRITER_NAME,
.priv_size = sizeof(StdOutWriterContext),
.priv_class = &stdoutwriter_class,
.writer_put_str = stdout_put_str,
.writer_printf = stdout_printf,
.writer_w8 = stdout_w8
};
int avtextwriter_create_stdout(AVTextWriterContext **pwctx)
{
int ret;
ret = avtextwriter_context_open(pwctx, &avtextwriter_stdout);
return ret;
}