2025-04-11 03:06:27 +02:00
|
|
|
/*
|
|
|
|
|
* 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]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-15 02:31:28 +02:00
|
|
|
void avtext_context_close(AVTextFormatContext **ptctx)
|
2025-04-11 03:06:27 +02:00
|
|
|
{
|
|
|
|
|
AVTextFormatContext *tctx = *ptctx;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
if (!tctx)
|
2025-04-15 02:31:28 +02:00
|
|
|
return;
|
2025-04-11 03:06:27 +02:00
|
|
|
|
|
|
|
|
av_hash_freep(&tctx->hash);
|
|
|
|
|
|
|
|
|
|
av_hash_freep(&tctx->hash);
|
|
|
|
|
|
2025-04-15 02:11:57 +02:00
|
|
|
if (tctx->formatter) {
|
|
|
|
|
if (tctx->formatter->uninit)
|
|
|
|
|
tctx->formatter->uninit(tctx);
|
|
|
|
|
if (tctx->formatter->priv_class)
|
|
|
|
|
av_opt_free(tctx->priv);
|
|
|
|
|
}
|
2025-04-11 03:06:27 +02:00
|
|
|
for (i = 0; i < SECTION_MAX_NB_LEVELS; i++)
|
|
|
|
|
av_bprint_finalize(&tctx->section_pbuf[i], NULL);
|
|
|
|
|
av_freep(&tctx->priv);
|
|
|
|
|
av_opt_free(tctx);
|
|
|
|
|
av_freep(ptctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-15 02:15:20 +02:00
|
|
|
for (int i = 0; i < SECTION_MAX_NB_LEVELS; i++)
|
|
|
|
|
av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED);
|
|
|
|
|
|
|
|
|
|
tctx->class = &textcontext_class;
|
|
|
|
|
av_opt_set_defaults(tctx);
|
|
|
|
|
|
2025-04-11 03:06:27 +02:00
|
|
|
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->formatter = formatter;
|
|
|
|
|
tctx->level = -1;
|
|
|
|
|
tctx->sections = sections;
|
|
|
|
|
tctx->nb_sections = nb_sections;
|
|
|
|
|
tctx->writer = writer_context;
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
{
|
2025-04-14 22:13:19 +02:00
|
|
|
char buf[44];
|
|
|
|
|
snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
|
|
|
|
|
avtext_print_string(tctx, key, buf, 0);
|
2025-04-11 03:06:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
2025-04-15 02:31:28 +02:00
|
|
|
void avtextwriter_context_close(AVTextWriterContext **pwctx)
|
2025-04-11 03:06:27 +02:00
|
|
|
{
|
|
|
|
|
AVTextWriterContext *wctx = *pwctx;
|
|
|
|
|
|
|
|
|
|
if (!wctx)
|
2025-04-15 02:31:28 +02:00
|
|
|
return;
|
2025-04-11 03:06:27 +02:00
|
|
|
|
2025-04-15 01:45:54 +02:00
|
|
|
if (wctx->writer) {
|
|
|
|
|
if (wctx->writer->uninit)
|
|
|
|
|
wctx->writer->uninit(wctx);
|
|
|
|
|
if (wctx->writer->priv_class)
|
|
|
|
|
av_opt_free(wctx->priv);
|
|
|
|
|
}
|
2025-04-11 03:06:27 +02:00
|
|
|
av_freep(&wctx->priv);
|
|
|
|
|
av_freep(pwctx);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|