...
This commit is contained in:
@@ -0,0 +1,702 @@
|
||||
/*
|
||||
* 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;
|
||||
char bin_str[4];
|
||||
char dec_str[4];
|
||||
} 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)
|
||||
{
|
||||
av_bprintf(bp, "0X");
|
||||
for (unsigned i = 0; i < ubuf_size; i++)
|
||||
av_bprintf(bp, "%02X", ubuf[i]);
|
||||
}
|
||||
|
||||
int avtext_context_close(AVTextFormatContext **ptctx)
|
||||
{
|
||||
AVTextFormatContext *tctx = *ptctx;
|
||||
int ret = 0;
|
||||
|
||||
if (!tctx)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
av_hash_freep(&tctx->hash);
|
||||
|
||||
if (tctx->formatter) {
|
||||
if (tctx->formatter->uninit)
|
||||
ret = tctx->formatter->uninit(tctx);
|
||||
if (tctx->formatter->priv_class)
|
||||
av_opt_free(tctx->priv);
|
||||
}
|
||||
for (int 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);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args,
|
||||
const AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, char *show_data_hash)
|
||||
{
|
||||
AVTextFormatContext *tctx;
|
||||
int ret = 0;
|
||||
|
||||
av_assert0(ptctx && formatter);
|
||||
|
||||
if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (!(tctx->priv = av_mallocz(formatter->priv_size))) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
tctx->show_value_unit = options.show_value_unit;
|
||||
tctx->use_value_prefix = options.use_value_prefix;
|
||||
tctx->use_byte_value_binary_prefix = options.use_byte_value_binary_prefix;
|
||||
tctx->use_value_sexagesimal_format = options.use_value_sexagesimal_format;
|
||||
tctx->show_optional_fields = options.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 (unsigned i = 0; (n = av_hash_names(i)); i++)
|
||||
av_log(NULL, AV_LOG_ERROR, " %s", n);
|
||||
av_log(NULL, AV_LOG_ERROR, "\n");
|
||||
}
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* validate replace string */
|
||||
{
|
||||
const uint8_t *p = (uint8_t *)tctx->string_validation_replacement;
|
||||
const uint8_t *endp = p + strlen((const char *)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);
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (section_id < 0 || section_id >= tctx->nb_sections) {
|
||||
av_log(tctx, AV_LOG_ERROR, "Invalid section_id for section_header: %d\n", section_id);
|
||||
return;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (tctx->level < 0 || tctx->level >= SECTION_MAX_NB_LEVELS) {
|
||||
av_log(tctx, AV_LOG_ERROR, "Invalid level for section_footer: %d\n", tctx->level);
|
||||
return;
|
||||
}
|
||||
|
||||
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, int flags)
|
||||
{
|
||||
const AVTextFormatSection *section;
|
||||
|
||||
av_assert0(tctx);
|
||||
|
||||
if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
|
||||
return;
|
||||
|
||||
if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO
|
||||
&& (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL)
|
||||
&& !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))
|
||||
return;
|
||||
|
||||
av_assert0(key && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
|
||||
|
||||
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, *srcp = (const uint8_t *)src;
|
||||
AVBPrint dstbuf;
|
||||
AVBPrint invalid_seq;
|
||||
int invalid_chars_nb = 0, ret = 0;
|
||||
|
||||
*dstp = NULL;
|
||||
av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
av_bprint_init(&invalid_seq, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
|
||||
endp = srcp + strlen(src);
|
||||
for (p = srcp; *p;) {
|
||||
int32_t code;
|
||||
int invalid = 0;
|
||||
const uint8_t *p0 = p;
|
||||
|
||||
if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) {
|
||||
|
||||
av_bprint_clear(&invalid_seq);
|
||||
|
||||
bprint_bytes(&invalid_seq, p0, p - p0);
|
||||
|
||||
av_log(tctx, AV_LOG_DEBUG, "Invalid UTF-8 sequence '%s' found in string '%s'\n", invalid_seq.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;
|
||||
|
||||
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);
|
||||
av_bprint_finalize(&invalid_seq, NULL);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct unit_value {
|
||||
union {
|
||||
double d;
|
||||
int64_t i;
|
||||
} val;
|
||||
|
||||
const char *unit;
|
||||
};
|
||||
|
||||
static char *value_string(const AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv)
|
||||
{
|
||||
double vald;
|
||||
int64_t vali = 0;
|
||||
int show_float = 0;
|
||||
|
||||
if (uv.unit == unit_second_str) {
|
||||
vald = uv.val.d;
|
||||
show_float = 1;
|
||||
} else {
|
||||
vald = (double)uv.val.i;
|
||||
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_clip64(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_clip64(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1);
|
||||
vald /= si_prefixes[index].dec_val;
|
||||
prefix_string = si_prefixes[index].dec_str;
|
||||
}
|
||||
vali = (int64_t)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_integer(AVTextFormatContext *tctx, const char *key, int64_t val, const char *unit)
|
||||
{
|
||||
char val_str[128];
|
||||
struct unit_value uv;
|
||||
uv.val.i = val;
|
||||
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 AVTextFormatSection *section;
|
||||
int ret = 0;
|
||||
|
||||
av_assert0(key && val && tctx->level >= 0 && tctx->level < SECTION_MAX_NB_LEVELS);
|
||||
|
||||
section = tctx->section[tctx->level];
|
||||
|
||||
if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER)
|
||||
return 0;
|
||||
|
||||
if (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)
|
||||
{
|
||||
char buf[44];
|
||||
snprintf(buf, sizeof(buf), "%d%c%d", q.num, sep, q.den);
|
||||
avtext_print_string(tctx, key, buf, 0);
|
||||
}
|
||||
|
||||
void avtext_print_time(AVTextFormatContext *tctx, const char *key,
|
||||
int64_t ts, const AVRational *time_base, 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 {
|
||||
char buf[128];
|
||||
double d = av_q2d(*time_base) * ts;
|
||||
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, 0);
|
||||
}
|
||||
|
||||
void avtext_print_data(AVTextFormatContext *tctx, const char *key,
|
||||
const uint8_t *data, int size)
|
||||
{
|
||||
AVBPrint bp;
|
||||
unsigned offset = 0;
|
||||
int i;
|
||||
|
||||
av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
av_bprintf(&bp, "\n");
|
||||
while (size) {
|
||||
av_bprintf(&bp, "%08x: ", offset);
|
||||
int 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, key, bp.str, 0);
|
||||
av_bprint_finalize(&bp, NULL);
|
||||
}
|
||||
|
||||
void avtext_print_data_hash(AVTextFormatContext *tctx, const char *key,
|
||||
const uint8_t *data, int size)
|
||||
{
|
||||
char buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 };
|
||||
int len;
|
||||
|
||||
if (!tctx->hash)
|
||||
return;
|
||||
|
||||
av_hash_init(tctx->hash);
|
||||
av_hash_update(tctx->hash, data, size);
|
||||
len = snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash));
|
||||
av_hash_final_hex(tctx->hash, (uint8_t *)&buf[len], (int)sizeof(buf) - len);
|
||||
avtext_print_string(tctx, key, buf, 0);
|
||||
}
|
||||
|
||||
void avtext_print_integers(AVTextFormatContext *tctx, const char *key,
|
||||
uint8_t *data, int size, const char *format,
|
||||
int columns, int bytes, int offset_add)
|
||||
{
|
||||
AVBPrint bp;
|
||||
unsigned offset = 0;
|
||||
|
||||
if (!key || !data || !format || columns <= 0 || bytes <= 0)
|
||||
return;
|
||||
|
||||
av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
av_bprintf(&bp, "\n");
|
||||
while (size) {
|
||||
av_bprintf(&bp, "%08x: ", offset);
|
||||
for (int i = 0, l = FFMIN(size, columns); 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, key, 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 AVERROR(EINVAL);
|
||||
|
||||
if (wctx->writer) {
|
||||
if (wctx->writer->uninit)
|
||||
ret = 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 (!pwctx || !writer)
|
||||
return AVERROR(EINVAL);
|
||||
|
||||
if (!((wctx = av_mallocz(sizeof(AVTextWriterContext))))) {
|
||||
ret = AVERROR(ENOMEM);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (writer->priv_size && !((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 *const registered_formatters[] =
|
||||
{
|
||||
&avtextformatter_default,
|
||||
&avtextformatter_compact,
|
||||
&avtextformatter_csv,
|
||||
&avtextformatter_flat,
|
||||
&avtextformatter_ini,
|
||||
&avtextformatter_json,
|
||||
&avtextformatter_xml,
|
||||
&avtextformatter_mermaid,
|
||||
&avtextformatter_mermaidhtml,
|
||||
NULL
|
||||
};
|
||||
|
||||
const AVTextFormatter *avtext_get_formatter_by_name(const char *name)
|
||||
{
|
||||
for (int i = 0; registered_formatters[i]; i++) {
|
||||
const char *end;
|
||||
if (av_strstart(name, registered_formatters[i]->name, &end) &&
|
||||
(*end == '\0' || *end == '='))
|
||||
return registered_formatters[i];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
/*
|
||||
* 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 <stdint.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 AVTextFormatSectionContext {
|
||||
char *context_id;
|
||||
const char *context_type;
|
||||
int context_flags;
|
||||
} AVTextFormatSectionContext;
|
||||
|
||||
|
||||
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
|
||||
#define AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE 32 ///< ...
|
||||
#define AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS 64 ///< ...
|
||||
#define AV_TEXTFORMAT_SECTION_PRINT_TAGS 128 ///< ...
|
||||
#define AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH 256 ///< ...
|
||||
|
||||
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;
|
||||
const char *id_key; ///< name of the key to be used as the id
|
||||
const char *src_id_key; ///< name of the key to be used as the source id for diagram connections
|
||||
const char *dest_id_key; ///< name of the key to be used as the target id for diagram connections
|
||||
const char *linktype_key; ///< name of the key to be used as the link type for diagram connections (AVTextFormatLinkType)
|
||||
} AVTextFormatSection;
|
||||
|
||||
typedef struct AVTextFormatContext AVTextFormatContext;
|
||||
|
||||
#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1
|
||||
#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2
|
||||
#define AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER 4
|
||||
|
||||
typedef enum {
|
||||
AV_TEXTFORMAT_STRING_VALIDATION_FAIL,
|
||||
AV_TEXTFORMAT_STRING_VALIDATION_REPLACE,
|
||||
AV_TEXTFORMAT_STRING_VALIDATION_IGNORE,
|
||||
AV_TEXTFORMAT_STRING_VALIDATION_NB
|
||||
} StringValidation;
|
||||
|
||||
typedef enum {
|
||||
AV_TEXTFORMAT_LINKTYPE_SRCDEST,
|
||||
AV_TEXTFORMAT_LINKTYPE_DESTSRC,
|
||||
AV_TEXTFORMAT_LINKTYPE_BIDIR,
|
||||
AV_TEXTFORMAT_LINKTYPE_NONDIR,
|
||||
AV_TEXTFORMAT_LINKTYPE_HIDDEN,
|
||||
AV_TEXTFORMAT_LINKTYPE_ONETOMANY = AV_TEXTFORMAT_LINKTYPE_SRCDEST,
|
||||
AV_TEXTFORMAT_LINKTYPE_MANYTOONE = AV_TEXTFORMAT_LINKTYPE_DESTSRC,
|
||||
AV_TEXTFORMAT_LINKTYPE_ONETOONE = AV_TEXTFORMAT_LINKTYPE_BIDIR,
|
||||
AV_TEXTFORMAT_LINKTYPE_MANYTOMANY = AV_TEXTFORMAT_LINKTYPE_NONDIR,
|
||||
} AVTextFormatLinkType;
|
||||
|
||||
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);
|
||||
int (*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_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 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 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;
|
||||
};
|
||||
|
||||
typedef struct AVTextFormatOptions {
|
||||
int show_optional_fields;
|
||||
int show_value_unit;
|
||||
int use_value_prefix;
|
||||
int use_byte_value_binary_prefix;
|
||||
int use_value_sexagesimal_format;
|
||||
} AVTextFormatOptions;
|
||||
|
||||
#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 AVTextFormatSection *sections, int nb_sections, AVTextFormatOptions options, 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 flags);
|
||||
|
||||
int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags);
|
||||
|
||||
void avtext_print_unit_integer(AVTextFormatContext *tctx, const char *key, int64_t val, 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 *key, const uint8_t *data, int size);
|
||||
|
||||
void avtext_print_data_hash(AVTextFormatContext *tctx, const char *key, const uint8_t *data, int size);
|
||||
|
||||
void avtext_print_integers(AVTextFormatContext *tctx, const char *key, 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;
|
||||
extern const AVTextFormatter avtextformatter_mermaid;
|
||||
extern const AVTextFormatter avtextformatter_mermaidhtml;
|
||||
|
||||
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include "libavformat/avio.h"
|
||||
#include "libavutil/bprint.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);
|
||||
int (*uninit)(AVTextWriterContext *wctx);
|
||||
void (*writer_w8)(AVTextWriterContext *wctx, int b);
|
||||
void (*writer_put_str)(AVTextWriterContext *wctx, const char *str);
|
||||
void (*writer_vprintf)(AVTextWriterContext *wctx, const char *fmt, va_list vl);
|
||||
} 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 avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer);
|
||||
|
||||
#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */
|
||||
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 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/bprint.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
|
||||
/* 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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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;
|
||||
const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
if (!compact->nested_section[wctx->level] &&
|
||||
compact->terminate_line[wctx->level] &&
|
||||
!(section->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,
|
||||
};
|
||||
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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/bprint.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
/* 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)
|
||||
{
|
||||
unsigned i;
|
||||
|
||||
for (i = 0; src[i] && i < dst_size - 1; i++)
|
||||
dst[i] = (char)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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
|
||||
char buf[32];
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -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/bprint.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
/* 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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
/* 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,
|
||||
};
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
* 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/bprint.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
/* 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;
|
||||
|
||||
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);
|
||||
/* fallthrough */
|
||||
default:
|
||||
if ((unsigned char)c < 32)
|
||||
av_bprintf(dst, "\\x00%02x", (unsigned char)c);
|
||||
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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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) {
|
||||
unsigned 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, ".%u", 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,
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Internal utilities for text formatters.
|
||||
*/
|
||||
|
||||
#ifndef FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
|
||||
#define FFTOOLS_TEXTFORMAT_TF_INTERNAL_H
|
||||
|
||||
#include "avtextformat.h"
|
||||
|
||||
#define DEFINE_FORMATTER_CLASS(name) \
|
||||
static const AVClass name##_class = { \
|
||||
.class_name = #name, \
|
||||
.item_name = av_default_item_name, \
|
||||
.option = name##_options \
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Safely validate and access a section at a given level
|
||||
*/
|
||||
static inline const AVTextFormatSection *tf_get_section(AVTextFormatContext *tfc, int level)
|
||||
{
|
||||
if (!tfc || level < 0 || level >= SECTION_MAX_NB_LEVELS || !tfc->section[level]) {
|
||||
if (tfc)
|
||||
av_log(tfc, AV_LOG_ERROR, "Invalid section access at level %d\n", level);
|
||||
return NULL;
|
||||
}
|
||||
return tfc->section[level];
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely access the parent section
|
||||
*/
|
||||
static inline const AVTextFormatSection *tf_get_parent_section(AVTextFormatContext *tfc, int level)
|
||||
{
|
||||
if (level <= 0)
|
||||
return NULL;
|
||||
|
||||
return tf_get_section(tfc, level - 1);
|
||||
}
|
||||
|
||||
static inline void writer_w8(AVTextFormatContext *wctx, int b)
|
||||
{
|
||||
wctx->writer->writer->writer_w8(wctx->writer, b);
|
||||
}
|
||||
|
||||
static inline void writer_put_str(AVTextFormatContext *wctx, const char *str)
|
||||
{
|
||||
wctx->writer->writer->writer_put_str(wctx->writer, str);
|
||||
}
|
||||
|
||||
static inline void writer_printf(AVTextFormatContext *wctx, const char *fmt, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
wctx->writer->writer->writer_vprintf(wctx->writer, fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
#endif /* FFTOOLS_TEXTFORMAT_TF_INTERNAL_H */
|
||||
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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/bprint.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
/* 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;
|
||||
|
||||
if (!src) {
|
||||
av_log(log_ctx, AV_LOG_WARNING, "Cannot escape NULL string, returning NULL\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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", (unsigned char)*p);
|
||||
} 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)
|
||||
{
|
||||
const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
JSONContext *json = wctx->priv;
|
||||
AVBPrint buf;
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
JSONContext *json = wctx->priv;
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
JSONContext *json = wctx->priv;
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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)
|
||||
{
|
||||
const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
JSONContext *json = wctx->priv;
|
||||
AVBPrint buf;
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,673 @@
|
||||
/*
|
||||
* 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 "tf_internal.h"
|
||||
#include "tf_mermaid.h"
|
||||
#include <libavutil/mem.h>
|
||||
#include <libavutil/avassert.h>
|
||||
#include <libavutil/bprint.h>
|
||||
#include <libavutil/opt.h>
|
||||
|
||||
|
||||
static const char *init_directive = ""
|
||||
"%%{init: {"
|
||||
"\"theme\": \"base\","
|
||||
"\"curve\": \"monotoneX\","
|
||||
"\"rankSpacing\": 10,"
|
||||
"\"nodeSpacing\": 10,"
|
||||
"\"themeCSS\": \"__###__\","
|
||||
"\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
|
||||
"\"themeVariables\": { "
|
||||
"\"clusterBkg\": \"white\", "
|
||||
"\"primaryBorderColor\": \"gray\", "
|
||||
"\"lineColor\": \"gray\", "
|
||||
"\"secondaryTextColor\": \"gray\", "
|
||||
"\"tertiaryBorderColor\": \"gray\", "
|
||||
"\"primaryTextColor\": \"#666\", "
|
||||
"\"secondaryTextColor\": \"red\" "
|
||||
"},"
|
||||
"\"flowchart\": { "
|
||||
"\"subGraphTitleMargin\": { \"top\": -15, \"bottom\": 20 }, "
|
||||
"\"diagramPadding\": 20, "
|
||||
"\"curve\": \"monotoneX\" "
|
||||
"}"
|
||||
" }}%%\n\n";
|
||||
|
||||
static const char* init_directive_er = ""
|
||||
"%%{init: {"
|
||||
"\"theme\": \"base\","
|
||||
"\"layout\": \"elk\","
|
||||
"\"curve\": \"monotoneX\","
|
||||
"\"rankSpacing\": 65,"
|
||||
"\"nodeSpacing\": 60,"
|
||||
"\"themeCSS\": \"__###__\","
|
||||
"\"fontFamily\": \"Roboto,Segoe UI,sans-serif\","
|
||||
"\"themeVariables\": { "
|
||||
"\"clusterBkg\": \"white\", "
|
||||
"\"primaryBorderColor\": \"gray\", "
|
||||
"\"lineColor\": \"gray\", "
|
||||
"\"secondaryTextColor\": \"gray\", "
|
||||
"\"tertiaryBorderColor\": \"gray\", "
|
||||
"\"primaryTextColor\": \"#666\", "
|
||||
"\"secondaryTextColor\": \"red\" "
|
||||
"},"
|
||||
"\"er\": { "
|
||||
"\"diagramPadding\": 12, "
|
||||
"\"entityPadding\": 4, "
|
||||
"\"minEntityWidth\": 150, "
|
||||
"\"minEntityHeight\": 20, "
|
||||
"\"curve\": \"monotoneX\" "
|
||||
"}"
|
||||
" }}%%\n\n";
|
||||
|
||||
static const char *theme_css_er = ""
|
||||
|
||||
// Variables
|
||||
".root { "
|
||||
"--ff-colvideo: #6eaa7b; "
|
||||
"--ff-colaudio: #477fb3; "
|
||||
"--ff-colsubtitle: #ad76ab; "
|
||||
"--ff-coltext: #666; "
|
||||
"} "
|
||||
" g.nodes g.node.default rect.basic.label-container, "
|
||||
" g.nodes g.node.default path { "
|
||||
" rx: 1; "
|
||||
" ry: 1; "
|
||||
" stroke-width: 1px !important; "
|
||||
" stroke: #e9e9e9 !important; "
|
||||
" fill: url(#ff-filtergradient) !important; "
|
||||
" filter: drop-shadow(0px 0px 5.5px rgba(0, 0, 0, 0.05)); "
|
||||
" fill: white !important; "
|
||||
" } "
|
||||
" "
|
||||
" .relationshipLine { "
|
||||
" stroke: gray; "
|
||||
" stroke-width: 1; "
|
||||
" fill: none; "
|
||||
" filter: drop-shadow(0px 0px 3px rgba(0, 0, 0, 0.2)); "
|
||||
" } "
|
||||
" "
|
||||
" g.node.default g.label.name foreignObject > div > span > p, "
|
||||
" g.nodes g.node.default g.label:not(.attribute-name, .attribute-keys, .attribute-type, .attribute-comment) foreignObject > div > span > p { "
|
||||
" font-size: 0.95rem; "
|
||||
" font-weight: 500; "
|
||||
" text-transform: uppercase; "
|
||||
" min-width: 5.5rem; "
|
||||
" margin-bottom: 0.5rem; "
|
||||
" "
|
||||
" } "
|
||||
" "
|
||||
" .edgePaths path { "
|
||||
" marker-end: none; "
|
||||
" marker-start: none; "
|
||||
" "
|
||||
"} ";
|
||||
|
||||
|
||||
/* Mermaid Graph output */
|
||||
|
||||
typedef struct MermaidContext {
|
||||
const AVClass *class;
|
||||
AVDiagramConfig *diagram_config;
|
||||
int subgraph_count;
|
||||
int within_tag;
|
||||
int indent_level;
|
||||
int create_html;
|
||||
|
||||
// Options
|
||||
int enable_link_colors; // Requires Mermaid 11.5
|
||||
|
||||
struct section_data {
|
||||
const char *section_id;
|
||||
const char *section_type;
|
||||
const char *src_id;
|
||||
const char *dest_id;
|
||||
AVTextFormatLinkType link_type;
|
||||
int current_is_textblock;
|
||||
int current_is_stadium;
|
||||
int subgraph_start_incomplete;
|
||||
} section_data[SECTION_MAX_NB_LEVELS];
|
||||
|
||||
unsigned nb_link_captions[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section,
|
||||
AVBPrint link_buf; ///< print buffer for writing diagram links
|
||||
AVDictionary *link_dict;
|
||||
} MermaidContext;
|
||||
|
||||
#undef OFFSET
|
||||
#define OFFSET(x) offsetof(MermaidContext, x)
|
||||
|
||||
static const AVOption mermaid_options[] = {
|
||||
{ "link_coloring", "enable colored links (requires Mermaid >= 11.5)", OFFSET(enable_link_colors), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
|
||||
////{"diagram_css", "CSS for the diagram", OFFSET(diagram_css), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
|
||||
////{"html_template", "Template HTML", OFFSET(html_template), AV_OPT_TYPE_STRING, {.i64=0}, 0, 1 },
|
||||
{ NULL },
|
||||
};
|
||||
|
||||
DEFINE_FORMATTER_CLASS(mermaid);
|
||||
|
||||
void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
mmc->diagram_config = diagram_config;
|
||||
}
|
||||
|
||||
static av_cold int has_link_pair(const AVTextFormatContext *tfc, const char *src, const char *dest)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
AVBPrint buf;
|
||||
|
||||
av_bprint_init(&buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
av_bprintf(&buf, "%s--%s", src, dest);
|
||||
|
||||
if (mmc->link_dict && av_dict_get(mmc->link_dict, buf.str, NULL, 0))
|
||||
return 1;
|
||||
|
||||
av_dict_set(&mmc->link_dict, buf.str, buf.str, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int mermaid_init(AVTextFormatContext *tfc)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
|
||||
av_bprint_init(&mmc->link_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
|
||||
////mmc->enable_link_colors = 1; // Requires Mermaid 11.5
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int mermaid_init_html(AVTextFormatContext *tfc)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
|
||||
int ret = mermaid_init(tfc);
|
||||
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mmc->create_html = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static av_cold int mermaid_uninit(AVTextFormatContext *tfc)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
|
||||
av_bprint_finalize(&mmc->link_buf, NULL);
|
||||
av_dict_free(&mmc->link_dict);
|
||||
|
||||
for (unsigned i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
|
||||
av_freep(&mmc->section_data[i].dest_id);
|
||||
av_freep(&mmc->section_data[i].section_id);
|
||||
av_freep(&mmc->section_data[i].src_id);
|
||||
av_freep(&mmc->section_data[i].section_type);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void set_str(const char **dst, const char *src)
|
||||
{
|
||||
if (*dst)
|
||||
av_freep(dst);
|
||||
|
||||
if (src)
|
||||
*dst = av_strdup(src);
|
||||
}
|
||||
|
||||
#define MM_INDENT() writer_printf(tfc, "%*c", mmc->indent_level * 2, ' ')
|
||||
|
||||
static void mermaid_print_section_header(AVTextFormatContext *tfc, const void *data)
|
||||
{
|
||||
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(tfc, tfc->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
const AVTextFormatSectionContext *sec_ctx = data;
|
||||
|
||||
if (tfc->level == 0) {
|
||||
char *directive;
|
||||
AVBPrint css_buf;
|
||||
const char *diag_directive = mmc->diagram_config->diagram_type == AV_DIAGRAMTYPE_ENTITYRELATIONSHIP ? init_directive_er : init_directive;
|
||||
char *single_line_css = av_strireplace(mmc->diagram_config->diagram_css, "\n", " ");
|
||||
(void)theme_css_er;
|
||||
////char *single_line_css = av_strireplace(theme_css_er, "\n", " ");
|
||||
av_bprint_init(&css_buf, 0, AV_BPRINT_SIZE_UNLIMITED);
|
||||
av_bprint_escape(&css_buf, single_line_css, "'\\", AV_ESCAPE_MODE_BACKSLASH, AV_ESCAPE_FLAG_STRICT);
|
||||
av_freep(&single_line_css);
|
||||
|
||||
directive = av_strireplace(diag_directive, "__###__", css_buf.str);
|
||||
if (mmc->create_html) {
|
||||
uint64_t length;
|
||||
char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
|
||||
if (!token_pos) {
|
||||
av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
|
||||
return;
|
||||
}
|
||||
|
||||
length = token_pos - mmc->diagram_config->html_template;
|
||||
for (uint64_t i = 0; i < length; i++)
|
||||
writer_w8(tfc, mmc->diagram_config->html_template[i]);
|
||||
}
|
||||
|
||||
writer_put_str(tfc, directive);
|
||||
switch (mmc->diagram_config->diagram_type) {
|
||||
case AV_DIAGRAMTYPE_GRAPH:
|
||||
writer_put_str(tfc, "flowchart LR\n");
|
||||
////writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsla(0, 0%, 30%, 0.02);\"/><stop offset=\"50%\" style=\"stop-color:hsla(0, 0%, 30%, 0);\"/><stop offset=\"100%\" style=\"stop-color:hsla(0, 0%, 30%, 0.05);\"/></linearGradient></defs></svg>\" }\n");
|
||||
writer_put_str(tfc, " gradient_def@{ shape: text, label: \"<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1\" height=\"1\"><defs><linearGradient id=\"ff-filtergradient\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\"><stop offset=\"0%\" style=\"stop-color:hsl(0, 0%, 98.6%); \"/><stop offset=\"50%\" style=\"stop-color:hsl(0, 0%, 100%); \"/><stop offset=\"100%\" style=\"stop-color:hsl(0, 0%, 96.5%); \"/></linearGradient><radialGradient id=\"ff-radgradient\" cx=\"50%\" cy=\"50%\" r=\"100%\" fx=\"45%\" fy=\"40%\"><stop offset=\"25%\" stop-color=\"hsl(0, 0%, 100%)\" /><stop offset=\"100%\" stop-color=\"hsl(0, 0%, 96%)\" /></radialGradient></defs></svg>\" }\n");
|
||||
break;
|
||||
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||
writer_put_str(tfc, "erDiagram\n");
|
||||
break;
|
||||
}
|
||||
|
||||
av_bprint_finalize(&css_buf, NULL);
|
||||
av_freep(&directive);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
|
||||
|
||||
struct section_data parent_sec_data = mmc->section_data[tfc->level - 1];
|
||||
AVBPrint *parent_buf = &tfc->section_pbuf[tfc->level - 1];
|
||||
|
||||
if (parent_sec_data.subgraph_start_incomplete) {
|
||||
|
||||
if (parent_buf->len > 0)
|
||||
writer_printf(tfc, "%s", parent_buf->str);
|
||||
|
||||
writer_put_str(tfc, "</div>\"]\n");
|
||||
|
||||
mmc->section_data[tfc->level - 1].subgraph_start_incomplete = 0;
|
||||
}
|
||||
}
|
||||
|
||||
av_freep(&mmc->section_data[tfc->level].section_id);
|
||||
av_freep(&mmc->section_data[tfc->level].section_type);
|
||||
av_freep(&mmc->section_data[tfc->level].src_id);
|
||||
av_freep(&mmc->section_data[tfc->level].dest_id);
|
||||
mmc->section_data[tfc->level].current_is_textblock = 0;
|
||||
mmc->section_data[tfc->level].current_is_stadium = 0;
|
||||
mmc->section_data[tfc->level].subgraph_start_incomplete = 0;
|
||||
mmc->section_data[tfc->level].link_type = AV_TEXTFORMAT_LINKTYPE_SRCDEST;
|
||||
|
||||
// NOTE: av_strdup() allocations aren't checked
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH) {
|
||||
|
||||
av_bprint_clear(buf);
|
||||
writer_put_str(tfc, "\n");
|
||||
|
||||
mmc->indent_level++;
|
||||
|
||||
if (sec_ctx->context_id) {
|
||||
MM_INDENT();
|
||||
writer_printf(tfc, "subgraph %s[\"<div class=\"ff-%s\">", sec_ctx->context_id, section->name);
|
||||
} else {
|
||||
av_log(tfc, AV_LOG_ERROR, "Unable to write subgraph start. Missing id field. Section: %s", section->name);
|
||||
}
|
||||
|
||||
mmc->section_data[tfc->level].subgraph_start_incomplete = 1;
|
||||
set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
|
||||
}
|
||||
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
|
||||
|
||||
av_bprint_clear(buf);
|
||||
writer_put_str(tfc, "\n");
|
||||
|
||||
mmc->indent_level++;
|
||||
|
||||
if (sec_ctx->context_id) {
|
||||
|
||||
set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
|
||||
|
||||
switch (mmc->diagram_config->diagram_type) {
|
||||
case AV_DIAGRAMTYPE_GRAPH:
|
||||
if (sec_ctx->context_flags & 1) {
|
||||
|
||||
MM_INDENT();
|
||||
writer_printf(tfc, "%s@{ shape: text, label: \"", sec_ctx->context_id);
|
||||
mmc->section_data[tfc->level].current_is_textblock = 1;
|
||||
} else if (sec_ctx->context_flags & 2) {
|
||||
|
||||
MM_INDENT();
|
||||
writer_printf(tfc, "%s([\"", sec_ctx->context_id);
|
||||
mmc->section_data[tfc->level].current_is_stadium = 1;
|
||||
} else {
|
||||
MM_INDENT();
|
||||
writer_printf(tfc, "%s(\"", sec_ctx->context_id);
|
||||
}
|
||||
|
||||
break;
|
||||
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||
MM_INDENT();
|
||||
writer_printf(tfc, "%s {\n", sec_ctx->context_id);
|
||||
break;
|
||||
}
|
||||
|
||||
} else {
|
||||
av_log(tfc, AV_LOG_ERROR, "Unable to write shape start. Missing id field. Section: %s", section->name);
|
||||
}
|
||||
|
||||
set_str(&mmc->section_data[tfc->level].section_id, sec_ctx->context_id);
|
||||
}
|
||||
|
||||
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS) {
|
||||
|
||||
if (sec_ctx && sec_ctx->context_type)
|
||||
writer_printf(tfc, "<div class=\"ff-%s %s\">", section->name, sec_ctx->context_type);
|
||||
else
|
||||
writer_printf(tfc, "<div class=\"ff-%s\">", section->name);
|
||||
}
|
||||
|
||||
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
|
||||
|
||||
av_bprint_clear(buf);
|
||||
mmc->nb_link_captions[tfc->level] = 0;
|
||||
|
||||
if (sec_ctx && sec_ctx->context_type)
|
||||
set_str(&mmc->section_data[tfc->level].section_type, sec_ctx->context_type);
|
||||
|
||||
////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(tfc, " type=\"%s\"", buf.str);
|
||||
}
|
||||
}
|
||||
|
||||
static void mermaid_print_section_footer(AVTextFormatContext *tfc)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||
struct section_data sec_data = mmc->section_data[tfc->level];
|
||||
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_PRINT_TAGS)
|
||||
writer_put_str(tfc, "</div>");
|
||||
|
||||
if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE) {
|
||||
|
||||
switch (mmc->diagram_config->diagram_type) {
|
||||
case AV_DIAGRAMTYPE_GRAPH:
|
||||
|
||||
if (sec_data.current_is_textblock) {
|
||||
writer_printf(tfc, "\"}\n", section->name);
|
||||
|
||||
if (sec_data.section_id) {
|
||||
MM_INDENT();
|
||||
writer_put_str(tfc, "class ");
|
||||
writer_put_str(tfc, sec_data.section_id);
|
||||
writer_put_str(tfc, " ff-");
|
||||
writer_put_str(tfc, section->name);
|
||||
writer_put_str(tfc, "\n");
|
||||
}
|
||||
} else if (sec_data.current_is_stadium) {
|
||||
writer_printf(tfc, "\"]):::ff-%s\n", section->name);
|
||||
} else {
|
||||
writer_printf(tfc, "\"):::ff-%s\n", section->name);
|
||||
}
|
||||
|
||||
break;
|
||||
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||
MM_INDENT();
|
||||
writer_put_str(tfc, "}\n\n");
|
||||
break;
|
||||
}
|
||||
|
||||
mmc->indent_level--;
|
||||
|
||||
} else if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH)) {
|
||||
|
||||
MM_INDENT();
|
||||
writer_put_str(tfc, "end\n");
|
||||
|
||||
if (sec_data.section_id) {
|
||||
MM_INDENT();
|
||||
writer_put_str(tfc, "class ");
|
||||
writer_put_str(tfc, sec_data.section_id);
|
||||
writer_put_str(tfc, " ff-");
|
||||
writer_put_str(tfc, section->name);
|
||||
writer_put_str(tfc, "\n");
|
||||
}
|
||||
|
||||
mmc->indent_level--;
|
||||
}
|
||||
|
||||
if ((section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS))
|
||||
if (sec_data.src_id && sec_data.dest_id
|
||||
&& !has_link_pair(tfc, sec_data.src_id, sec_data.dest_id))
|
||||
switch (mmc->diagram_config->diagram_type) {
|
||||
case AV_DIAGRAMTYPE_GRAPH:
|
||||
|
||||
if (sec_data.section_type && mmc->enable_link_colors)
|
||||
av_bprintf(&mmc->link_buf, "\n %s %s-%s-%s@==", sec_data.src_id, sec_data.section_type, sec_data.src_id, sec_data.dest_id);
|
||||
else
|
||||
av_bprintf(&mmc->link_buf, "\n %s ==", sec_data.src_id);
|
||||
|
||||
if (buf->len > 0) {
|
||||
av_bprintf(&mmc->link_buf, " \"%s", buf->str);
|
||||
|
||||
for (unsigned i = 0; i < mmc->nb_link_captions[tfc->level]; i++)
|
||||
av_bprintf(&mmc->link_buf, "<br> ");
|
||||
|
||||
av_bprintf(&mmc->link_buf, "\" ==");
|
||||
}
|
||||
|
||||
av_bprintf(&mmc->link_buf, "> %s", sec_data.dest_id);
|
||||
|
||||
break;
|
||||
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||
|
||||
|
||||
av_bprintf(&mmc->link_buf, "\n %s", sec_data.src_id);
|
||||
|
||||
switch (sec_data.link_type) {
|
||||
case AV_TEXTFORMAT_LINKTYPE_ONETOMANY:
|
||||
av_bprintf(&mmc->link_buf, "%s", " ||--o{ ");
|
||||
break;
|
||||
case AV_TEXTFORMAT_LINKTYPE_MANYTOONE:
|
||||
av_bprintf(&mmc->link_buf, "%s", " }o--|| ");
|
||||
break;
|
||||
case AV_TEXTFORMAT_LINKTYPE_ONETOONE:
|
||||
av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
|
||||
break;
|
||||
case AV_TEXTFORMAT_LINKTYPE_MANYTOMANY:
|
||||
av_bprintf(&mmc->link_buf, "%s", " }o--o{ ");
|
||||
break;
|
||||
default:
|
||||
av_bprintf(&mmc->link_buf, "%s", " ||--|| ");
|
||||
break;
|
||||
}
|
||||
|
||||
av_bprintf(&mmc->link_buf, "%s : \"\"", sec_data.dest_id);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (tfc->level == 0) {
|
||||
|
||||
writer_put_str(tfc, "\n");
|
||||
if (mmc->create_html) {
|
||||
char *token_pos = av_stristr(mmc->diagram_config->html_template, "__###__");
|
||||
if (!token_pos) {
|
||||
av_log(tfc, AV_LOG_ERROR, "Unable to locate the required token (__###__) in the html template.");
|
||||
return;
|
||||
}
|
||||
token_pos += strlen("__###__");
|
||||
writer_put_str(tfc, token_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if (tfc->level == 1) {
|
||||
|
||||
if (mmc->link_buf.len > 0) {
|
||||
writer_put_str(tfc, mmc->link_buf.str);
|
||||
av_bprint_clear(&mmc->link_buf);
|
||||
}
|
||||
|
||||
writer_put_str(tfc, "\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void mermaid_print_value(AVTextFormatContext *tfc, const char *key,
|
||||
const char *str, int64_t num, const int is_int)
|
||||
{
|
||||
MermaidContext *mmc = tfc->priv;
|
||||
const AVTextFormatSection *section = tf_get_section(tfc, tfc->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
AVBPrint *buf = &tfc->section_pbuf[tfc->level];
|
||||
struct section_data sec_data = mmc->section_data[tfc->level];
|
||||
int exit = 0;
|
||||
|
||||
if (section->id_key && !strcmp(section->id_key, key)) {
|
||||
set_str(&mmc->section_data[tfc->level].section_id, str);
|
||||
exit = 1;
|
||||
}
|
||||
|
||||
if (section->dest_id_key && !strcmp(section->dest_id_key, key)) {
|
||||
set_str(&mmc->section_data[tfc->level].dest_id, str);
|
||||
exit = 1;
|
||||
}
|
||||
|
||||
if (section->src_id_key && !strcmp(section->src_id_key, key)) {
|
||||
set_str(&mmc->section_data[tfc->level].src_id, str);
|
||||
exit = 1;
|
||||
}
|
||||
|
||||
if (section->linktype_key && !strcmp(section->linktype_key, key)) {
|
||||
mmc->section_data[tfc->level].link_type = (AVTextFormatLinkType)num;
|
||||
exit = 1;
|
||||
}
|
||||
|
||||
if (exit)
|
||||
return;
|
||||
|
||||
if ((section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_SHAPE | AV_TEXTFORMAT_SECTION_PRINT_TAGS))
|
||||
|| (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_SUBGRAPH && sec_data.subgraph_start_incomplete)) {
|
||||
switch (mmc->diagram_config->diagram_type) {
|
||||
case AV_DIAGRAMTYPE_GRAPH:
|
||||
|
||||
if (is_int) {
|
||||
writer_printf(tfc, "<span class=\"%s\">%s: %"PRId64"</span>", key, key, num);
|
||||
} else {
|
||||
const char *tmp = av_strireplace(str, "\"", "'");
|
||||
writer_printf(tfc, "<span class=\"%s\">%s</span>", key, tmp);
|
||||
av_freep(&tmp);
|
||||
}
|
||||
|
||||
break;
|
||||
case AV_DIAGRAMTYPE_ENTITYRELATIONSHIP:
|
||||
|
||||
if (!is_int && str)
|
||||
{
|
||||
const char *col_type;
|
||||
|
||||
if (key[0] == '_')
|
||||
return;
|
||||
|
||||
if (sec_data.section_id && !strcmp(str, sec_data.section_id))
|
||||
col_type = "PK";
|
||||
else if (sec_data.dest_id && !strcmp(str, sec_data.dest_id))
|
||||
col_type = "FK";
|
||||
else if (sec_data.src_id && !strcmp(str, sec_data.src_id))
|
||||
col_type = "FK";
|
||||
else
|
||||
col_type = "";
|
||||
|
||||
MM_INDENT();
|
||||
|
||||
writer_printf(tfc, " %s %s %s\n", key, str, col_type);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
} else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_LINKS) {
|
||||
if (buf->len > 0)
|
||||
av_bprintf(buf, "%s", "<br>");
|
||||
|
||||
av_bprintf(buf, "");
|
||||
if (is_int)
|
||||
av_bprintf(buf, "<span>%s: %"PRId64"</span>", key, num);
|
||||
else
|
||||
av_bprintf(buf, "<span>%s</span>", str);
|
||||
|
||||
mmc->nb_link_captions[tfc->level]++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void mermaid_print_str(AVTextFormatContext *tfc, const char *key, const char *value)
|
||||
{
|
||||
mermaid_print_value(tfc, key, value, 0, 0);
|
||||
}
|
||||
|
||||
static void mermaid_print_int(AVTextFormatContext *tfc, const char *key, int64_t value)
|
||||
{
|
||||
mermaid_print_value(tfc, key, NULL, value, 1);
|
||||
}
|
||||
|
||||
const AVTextFormatter avtextformatter_mermaid = {
|
||||
.name = "mermaid",
|
||||
.priv_size = sizeof(MermaidContext),
|
||||
.init = mermaid_init,
|
||||
.uninit = mermaid_uninit,
|
||||
.print_section_header = mermaid_print_section_header,
|
||||
.print_section_footer = mermaid_print_section_footer,
|
||||
.print_integer = mermaid_print_int,
|
||||
.print_string = mermaid_print_str,
|
||||
.flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
|
||||
.priv_class = &mermaid_class,
|
||||
};
|
||||
|
||||
|
||||
const AVTextFormatter avtextformatter_mermaidhtml = {
|
||||
.name = "mermaidhtml",
|
||||
.priv_size = sizeof(MermaidContext),
|
||||
.init = mermaid_init_html,
|
||||
.uninit = mermaid_uninit,
|
||||
.print_section_header = mermaid_print_section_header,
|
||||
.print_section_footer = mermaid_print_section_footer,
|
||||
.print_integer = mermaid_print_int,
|
||||
.print_string = mermaid_print_str,
|
||||
.flags = AV_TEXTFORMAT_FLAG_IS_DIAGRAM_FORMATTER,
|
||||
.priv_class = &mermaid_class,
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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_TF_MERMAID_H
|
||||
#define FFTOOLS_TEXTFORMAT_TF_MERMAID_H
|
||||
|
||||
typedef enum {
|
||||
AV_DIAGRAMTYPE_GRAPH,
|
||||
AV_DIAGRAMTYPE_ENTITYRELATIONSHIP,
|
||||
} AVDiagramType;
|
||||
|
||||
typedef struct AVDiagramConfig {
|
||||
AVDiagramType diagram_type;
|
||||
const char *diagram_css;
|
||||
const char *html_template;
|
||||
} AVDiagramConfig;
|
||||
|
||||
|
||||
void av_diagram_init(AVTextFormatContext *tfc, AVDiagramConfig *diagram_config);
|
||||
|
||||
void av_mermaid_set_html_template(AVTextFormatContext *tfc, const char *html_template);
|
||||
|
||||
|
||||
#endif /* FFTOOLS_TEXTFORMAT_TF_MERMAID_H */
|
||||
@@ -0,0 +1,212 @@
|
||||
/*
|
||||
* 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 <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "avtextformat.h"
|
||||
#include "libavutil/bprint.h"
|
||||
#include "libavutil/error.h"
|
||||
#include "libavutil/opt.h"
|
||||
#include "tf_internal.h"
|
||||
|
||||
/* 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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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 AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
|
||||
|
||||
if (!section)
|
||||
return;
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
* 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 <string.h>
|
||||
|
||||
#include "avtextwriters.h"
|
||||
#include "libavutil/avassert.h"
|
||||
|
||||
#include "libavutil/error.h"
|
||||
|
||||
/* AVIO Writer */
|
||||
|
||||
# define WRITER_NAME "aviowriter"
|
||||
|
||||
typedef struct IOWriterContext {
|
||||
const AVClass *class;
|
||||
AVIOContext *avio_context;
|
||||
int close_on_uninit;
|
||||
} IOWriterContext;
|
||||
|
||||
static av_cold int iowriter_uninit(AVTextWriterContext *wctx)
|
||||
{
|
||||
IOWriterContext *ctx = wctx->priv;
|
||||
int ret = 0;
|
||||
|
||||
if (ctx->close_on_uninit)
|
||||
ret = avio_closep(&ctx->avio_context);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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, (const unsigned char *)str, (int)strlen(str));
|
||||
}
|
||||
|
||||
static void io_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
|
||||
{
|
||||
IOWriterContext *ctx = wctx->priv;
|
||||
|
||||
avio_vprintf(ctx->avio_context, fmt, vl);
|
||||
}
|
||||
|
||||
|
||||
const AVTextWriter avtextwriter_avio = {
|
||||
.name = WRITER_NAME,
|
||||
.priv_size = sizeof(IOWriterContext),
|
||||
.uninit = iowriter_uninit,
|
||||
.writer_put_str = io_put_str,
|
||||
.writer_vprintf = io_vprintf,
|
||||
.writer_w8 = io_w8
|
||||
};
|
||||
|
||||
int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename)
|
||||
{
|
||||
IOWriterContext *ctx;
|
||||
int ret;
|
||||
|
||||
if (!output_filename || !output_filename[0]) {
|
||||
av_log(NULL, AV_LOG_ERROR, "The output_filename cannot be NULL or empty\n");
|
||||
return AVERROR(EINVAL);
|
||||
}
|
||||
|
||||
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 = 1;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit)
|
||||
{
|
||||
IOWriterContext *ctx;
|
||||
int ret;
|
||||
|
||||
av_assert0(avio_ctx);
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* 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_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
|
||||
{
|
||||
BufferWriterContext *ctx = wctx->priv;
|
||||
|
||||
av_vbprintf(ctx->buffer, fmt, vl);
|
||||
}
|
||||
|
||||
|
||||
const AVTextWriter avtextwriter_buffer = {
|
||||
.name = WRITER_NAME,
|
||||
.priv_size = sizeof(BufferWriterContext),
|
||||
.priv_class = &bufferwriter_class,
|
||||
.writer_put_str = buffer_put_str,
|
||||
.writer_vprintf = buffer_vprintf,
|
||||
.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;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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_vprintf(AVTextWriterContext *wctx, const char *fmt, va_list vl)
|
||||
{
|
||||
vprintf(fmt, vl);
|
||||
}
|
||||
|
||||
|
||||
static const AVTextWriter avtextwriter_stdout = {
|
||||
.name = WRITER_NAME,
|
||||
.priv_size = sizeof(StdOutWriterContext),
|
||||
.priv_class = &stdoutwriter_class,
|
||||
.writer_put_str = stdout_put_str,
|
||||
.writer_vprintf = stdout_vprintf,
|
||||
.writer_w8 = stdout_w8
|
||||
};
|
||||
|
||||
int avtextwriter_create_stdout(AVTextWriterContext **pwctx)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = avtextwriter_context_open(pwctx, &avtextwriter_stdout);
|
||||
|
||||
return ret;
|
||||
}
|
||||
Reference in New Issue
Block a user