From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id 302FB4C270 for ; Sat, 8 Mar 2025 14:01:17 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 0E58A68F3A8; Sat, 8 Mar 2025 16:01:13 +0200 (EET) Received: from mail-ed1-f49.google.com (mail-ed1-f49.google.com [209.85.208.49]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 52FB368DE58 for ; Sat, 8 Mar 2025 16:01:01 +0200 (EET) Received: by mail-ed1-f49.google.com with SMTP id 4fb4d7f45d1cf-5e5b6f3025dso3815872a12.1; Sat, 08 Mar 2025 06:01:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1741442460; x=1742047260; darn=ffmpeg.org; h=user-agent:in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date:from:to:cc :subject:date:message-id:reply-to; bh=+09n5SE7rmzhbAXEvyCNZXXAbN6fd40M5/4Wq/ptA3U=; b=LiOnfyx571HwGMnmjQpimMAEKzvDRXOAiDDdY7HNBq7L4w68IwTS4WUH15rivAd5JF nKL6uiARoGan7/wSVrbfLGcN9nvdF6wYBXm1/za1nhpTn/pK6JS+/o9WwL0zOWJv8jiZ s7Xk/CSedQUFZvM3ShDRiWTAm55FT7+1mNTvTGffQEukdITpJct4nr0G1ZcFjrBy1O74 ey8hCUVyTKgxfNM5x/sp6ql9UhBz9bb9pnhIO1icQ3R4YRw9JfosBBKwkhELDePBemaL CpV4RD4oEJe36U1eS0nr0Wsoj/hBdOxMjEGw8LH0gwrVZGbWLvk5rNPVKaMx6G00g9sF ON7w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1741442460; x=1742047260; h=user-agent:in-reply-to:content-disposition:mime-version:references :mail-followup-to:message-id:subject:cc:to:from:date :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=+09n5SE7rmzhbAXEvyCNZXXAbN6fd40M5/4Wq/ptA3U=; b=QCIwwNb1JIohSy+TJ9Wnp0idMm98gvbZ5dAF9ggcl/mlkFnaxrKv5STv2Yp6kjQe4P AH04jgyaJ8KzGFy0tbdSQMBGDWhh5whcGVsSRNoRWyFQB+OZCxqTu9lRBW1/lxAtFctq vlFdtAkvCarWYXR0A1fFqw3ShfpZIiQDNbiZU5Wx44N7kdQWPtIR57LaneUlNCGV+gpL W6HSzYslkBCH0oVcuxS13sdspnR4TSZaIht5kcb5M1N3nhOk65FrkSGfCXdqwYAdupHx OV4otwiu/4UOJBqHdEIJ7R+6+MxWlCcK6vZLThJ6HXtnkP3sWhOoCErzC867oZeNXNIC 8xlw== X-Gm-Message-State: AOJu0YwXlVjyh7UUAdivO7U4YCRdv9Tx7+54+m3+TygrwE8wDrpRaK5e GbM4maGd4TBu2m947Wk8cpL2jNRFXwP5NgJqzn6qoB3sFi+c4xipYKDoOw== X-Gm-Gg: ASbGncvgX/ScoQJkiVvv6lYynmoYhcsqMfCy1fhPq3cPi8T+QQseWr2AF94+4+jCHso ymVhGx7IppATM/FjRcaq7zhyUH4PfCseQYzCjzln4RpFh08zKmltf6DvDmMvkqn5ADpLiKFaHbQ OXcvKtnIRsPfcE+1WidTGKn+zDGCzCAFkK/8MUli2s4WBmzKvrTp2AOKIx3JecMo0zmwtwq8QWP 5JxwAD723wbR33ARfYsS2HJEoBu03r09wlseR+x6yh2eYQ47xmnedELk5X7yv8oqiBnnRxJs2d4 SP+dnwOkOplm72NPXHE9bwJTbLFCRoiqBOVtJTpIoLRueDU3JMcynsvq3wqVjvQfnkWL7v/Ud1W QIAyXWIEL X-Google-Smtp-Source: AGHT+IE8MP7hi8kEqii0seEXjLbpv5gL8Mrs4FakoFm1JAlLt+UYUy17UG0TRzeubjHzsYdnfg71fQ== X-Received: by 2002:a17:907:1c94:b0:ac2:806e:bb55 with SMTP id a640c23a62f3a-ac2806ebdebmr114372966b.19.1741442459147; Sat, 08 Mar 2025 06:00:59 -0800 (PST) Received: from mariano (dynamic-adsl-84-220-189-10.clienti.tiscali.it. [84.220.189.10]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-ac2397380ebsm444274466b.97.2025.03.08.06.00.58 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sat, 08 Mar 2025 06:00:58 -0800 (PST) Received: by mariano (Postfix, from userid 1000) id B9CD3BFCE8; Sat, 8 Mar 2025 15:00:56 +0100 (CET) Date: Sat, 8 Mar 2025 15:00:56 +0100 From: Stefano Sabatini To: FFmpeg development discussions and patches Message-ID: Mail-Followup-To: FFmpeg development discussions and patches , Soft Works , softworkz , Andreas Rheinhardt References: <6239813ba0e293bd427cecf8437aadef778ea3ee.1740823324.git.ffmpegagent@gmail.com> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <6239813ba0e293bd427cecf8437aadef778ea3ee.1740823324.git.ffmpegagent@gmail.com> User-Agent: Mutt/2.1.4 (2021-12-11) Subject: Re: [FFmpeg-devel] [PATCH v3 1/7] fftools/textformat: Extract and generalize textformat api from ffprobe.c X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Soft Works , softworkz , Andreas Rheinhardt Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Archived-At: List-Archive: List-Post: Sorry for delayed review, due to a sickness on my side for the past three days. On date Saturday 2025-03-01 10:01:58 +0000, softworkz wrote: > From: softworkz > > Signed-off-by: softworkz > --- > fftools/textformat/avtextformat.c | 671 +++++++++++++++++++++++++++++ > fftools/textformat/avtextformat.h | 171 ++++++++ > fftools/textformat/avtextwriters.h | 68 +++ > fftools/textformat/tf_compact.c | 282 ++++++++++++ > fftools/textformat/tf_default.c | 145 +++++++ > fftools/textformat/tf_flat.c | 174 ++++++++ > fftools/textformat/tf_ini.c | 160 +++++++ > fftools/textformat/tf_json.c | 215 +++++++++ > fftools/textformat/tf_xml.c | 221 ++++++++++ > fftools/textformat/tw_avio.c | 129 ++++++ > fftools/textformat/tw_buffer.c | 92 ++++ > fftools/textformat/tw_stdout.c | 82 ++++ > 12 files changed, 2410 insertions(+) > create mode 100644 fftools/textformat/avtextformat.c > create mode 100644 fftools/textformat/avtextformat.h > create mode 100644 fftools/textformat/avtextwriters.h > create mode 100644 fftools/textformat/tf_compact.c > create mode 100644 fftools/textformat/tf_default.c > create mode 100644 fftools/textformat/tf_flat.c > create mode 100644 fftools/textformat/tf_ini.c > create mode 100644 fftools/textformat/tf_json.c > create mode 100644 fftools/textformat/tf_xml.c > create mode 100644 fftools/textformat/tw_avio.c > create mode 100644 fftools/textformat/tw_buffer.c > create mode 100644 fftools/textformat/tw_stdout.c > > diff --git a/fftools/textformat/avtextformat.c b/fftools/textformat/avtextformat.c > new file mode 100644 > index 0000000000..1fba78b103 > --- /dev/null > +++ b/fftools/textformat/avtextformat.c > @@ -0,0 +1,671 @@ > +/* > + * Copyright (c) The ffmpeg developers nit: FFmpeg here and in the other headers > + * > + * 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 > +#include > +#include > +#include > +#include > + > +#include "libavutil/mem.h" > +#include "libavutil/avassert.h" > +#include "libavutil/bprint.h" > +#include "libavutil/error.h" > +#include "libavutil/hash.h" > +#include "libavutil/intreadwrite.h" > +#include "libavutil/macros.h" > +#include "libavutil/opt.h" > +#include "avtextformat.h" > + > +#define SECTION_ID_NONE -1 > + > +#define SHOW_OPTIONAL_FIELDS_AUTO -1 > +#define SHOW_OPTIONAL_FIELDS_NEVER 0 > +#define SHOW_OPTIONAL_FIELDS_ALWAYS 1 > + > +static const struct { > + double bin_val; > + double dec_val; > + const char *bin_str; > + const char *dec_str; > +} si_prefixes[] = { > + { 1.0, 1.0, "", "" }, > + { 1.024e3, 1e3, "Ki", "K" }, > + { 1.048576e6, 1e6, "Mi", "M" }, > + { 1.073741824e9, 1e9, "Gi", "G" }, > + { 1.099511627776e12, 1e12, "Ti", "T" }, > + { 1.125899906842624e15, 1e15, "Pi", "P" }, > +}; > + > +static const char *avtext_context_get_formatter_name(void *p) why the prefix for a static const? > +{ > + 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 *trextcontext_child_next(void *obj, void *prev) trext -> text typo? > +{ > + 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 = avtext_context_get_formatter_name, > + .option = textcontext_options, > + .version = LIBAVUTIL_VERSION_INT, > + .child_next = trextcontext_child_next, > +}; > + > +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) > +{ > + int i; > + av_bprintf(bp, "0X"); > + for (i = 0; i < ubuf_size; i++) > + av_bprintf(bp, "%02X", ubuf[i]); > +} > + > +int avtext_context_close(AVTextFormatContext **ptctx) > +{ > + AVTextFormatContext *tctx = *ptctx; > + int i; > + int ret = 0; > + > + if (!tctx) > + return -1; let's aid the programmer with a message error code, return EINVAL? > + > + av_hash_freep(&tctx->hash); > + > + av_hash_freep(&tctx->hash); > + > + if (tctx->formatter->uninit) > + tctx->formatter->uninit(tctx); > + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) > + av_bprint_finalize(&tctx->section_pbuf[i], NULL); > + if (tctx->formatter->priv_class) > + av_opt_free(tctx->priv); > + av_freep(&tctx->priv); > + av_opt_free(tctx); > + av_freep(ptctx); > + return ret; > +} > + > + > +int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args, > + const struct AVTextFormatSection *sections, int nb_sections, > + int show_value_unit, > + int use_value_prefix, > + int use_byte_value_binary_prefix, > + int use_value_sexagesimal_format, > + int show_optional_fields, > + char *show_data_hash) > +{ > + AVTextFormatContext *tctx; > + int i, ret = 0; > + > + if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) { > + ret = AVERROR(ENOMEM); > + goto fail; > + } > + > + if (!(tctx->priv = av_mallocz(formatter->priv_size))) { > + ret = AVERROR(ENOMEM); > + goto fail; > + } > + > + tctx->show_value_unit = show_value_unit; > + tctx->use_value_prefix = use_value_prefix; > + tctx->use_byte_value_binary_prefix = use_byte_value_binary_prefix; > + tctx->use_value_sexagesimal_format = use_value_sexagesimal_format; > + tctx->show_optional_fields = show_optional_fields; > + > + if (nb_sections > SECTION_MAX_NB_SECTIONS) { > + av_log(tctx, AV_LOG_ERROR, "The number of section definitions (%d) is larger than the maximum allowed (%d)\n", nb_sections, SECTION_MAX_NB_SECTIONS); set ret = AVERROR(EINVAL) or this will return 0 > + goto fail; > + } > + > + tctx->class = &textcontext_class; > + tctx->formatter = formatter; > + tctx->level = -1; > + tctx->sections = sections; > + tctx->nb_sections = nb_sections; > + tctx->writer = writer_context; > + > + av_opt_set_defaults(tctx); > + > + if (formatter->priv_class) { > + void *priv_ctx = tctx->priv; > + *(const AVClass **)priv_ctx = formatter->priv_class; > + av_opt_set_defaults(priv_ctx); > + } > + > + /* convert options to dictionary */ > + if (args) { non blocking, but probably we want to provide a more programmer friendly interface, so there is no need to serialize and deserialiaze the data here > + AVDictionary *opts = NULL; > + const AVDictionaryEntry *opt = NULL; > + > + if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) { > + av_log(tctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to textformat context\n", args); > + av_dict_free(&opts); > + goto fail; > + } > + > + while ((opt = av_dict_iterate(opts, opt))) { > + if ((ret = av_opt_set(tctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { > + av_log(tctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to textformat context\n", > + opt->key, opt->value); > + av_dict_free(&opts); > + goto fail; > + } > + } > + > + av_dict_free(&opts); > + } > + > + if (show_data_hash) { > + if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) { > + if (ret == AVERROR(EINVAL)) { > + const char *n; > + av_log(NULL, AV_LOG_ERROR, "Unknown hash algorithm '%s'\nKnown algorithms:", show_data_hash); > + for (i = 0; (n = av_hash_names(i)); i++) > + av_log(NULL, AV_LOG_ERROR, " %s", n); > + av_log(NULL, AV_LOG_ERROR, "\n"); > + } > + return ret; > + } > + } > + > + /* validate replace string */ > + { > + const uint8_t *p = tctx->string_validation_replacement; > + const uint8_t *endp = p + strlen(p); > + while (*p) { > + const uint8_t *p0 = p; > + int32_t code; > + ret = av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags); > + if (ret < 0) { > + AVBPrint bp; > + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); > + bprint_bytes(&bp, p0, p-p0), > + av_log(tctx, AV_LOG_ERROR, > + "Invalid UTF8 sequence %s found in string validation replace '%s'\n", > + bp.str, tctx->string_validation_replacement); > + return ret; > + } > + } > + } > + > + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) > + av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED); > + > + if (tctx->formatter->init) > + ret = tctx->formatter->init(tctx); > + if (ret < 0) > + goto fail; > + > + *ptctx = tctx; > + > + return 0; > + > +fail: > + avtext_context_close(&tctx); > + return ret; > +} > + > +/* Temporary definitions during refactoring */ > +static const char unit_second_str[] = "s" ; > +static const char unit_hertz_str[] = "Hz" ; > +static const char unit_byte_str[] = "byte" ; > +static const char unit_bit_per_second_str[] = "bit/s"; > + > + > +void avtext_print_section_header(AVTextFormatContext *tctx, > + const void *data, > + int section_id) > +{ > + tctx->level++; > + av_assert0(tctx->level < SECTION_MAX_NB_LEVELS); > + > + tctx->nb_item[tctx->level] = 0; > + memset(tctx->nb_item_type[tctx->level], 0, sizeof(tctx->nb_item_type[tctx->level])); > + tctx->section[tctx->level] = &tctx->sections[section_id]; > + > + if (tctx->formatter->print_section_header) > + tctx->formatter->print_section_header(tctx, data); > +} > + > +void avtext_print_section_footer(AVTextFormatContext *tctx) > +{ > + int section_id = tctx->section[tctx->level]->id; > + int parent_section_id = tctx->level ? > + tctx->section[tctx->level-1]->id : SECTION_ID_NONE; > + > + if (parent_section_id != SECTION_ID_NONE) { > + tctx->nb_item[tctx->level - 1]++; > + tctx->nb_item_type[tctx->level - 1][section_id]++; > + } > + > + if (tctx->formatter->print_section_footer) > + tctx->formatter->print_section_footer(tctx); > + tctx->level--; > +} > + > +void avtext_print_integer(AVTextFormatContext *tctx, > + const char *key, int64_t val) > +{ > + const struct AVTextFormatSection *section = tctx->section[tctx->level]; > + > + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { > + tctx->formatter->print_integer(tctx, key, val); > + tctx->nb_item[tctx->level]++; > + } > +} > + > +static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src) > +{ > + const uint8_t *p, *endp; > + AVBPrint dstbuf; > + int invalid_chars_nb = 0, ret = 0; > + > + av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED); > + > + endp = src + strlen(src); > + for (p = src; *p;) { > + uint32_t code; > + int invalid = 0; > + const uint8_t *p0 = p; > + > + if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) { > + AVBPrint bp; > + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); > + bprint_bytes(&bp, p0, p-p0); > + av_log(tctx, AV_LOG_DEBUG, > + "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src); > + invalid = 1; > + } > + > + if (invalid) { > + invalid_chars_nb++; > + > + switch (tctx->string_validation) { > + case AV_TEXTFORMAT_STRING_VALIDATION_FAIL: > + av_log(tctx, AV_LOG_ERROR, > + "Invalid UTF-8 sequence found in string '%s'\n", src); > + ret = AVERROR_INVALIDDATA; > + goto end; > + break; > + > + case AV_TEXTFORMAT_STRING_VALIDATION_REPLACE: > + av_bprintf(&dstbuf, "%s", tctx->string_validation_replacement); > + break; > + } > + } > + > + if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE) > + av_bprint_append_data(&dstbuf, p0, p-p0); > + } > + > + if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) { > + av_log(tctx, AV_LOG_WARNING, > + "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n", > + invalid_chars_nb, src, tctx->string_validation_replacement); > + } > + > +end: > + av_bprint_finalize(&dstbuf, dstp); > + return ret; > +} > + > +struct unit_value { > + union { double d; int64_t i; } val; > + const char *unit; > +}; > + > +static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv) > +{ > + double vald; > + int64_t vali; > + int show_float = 0; > + > + if (uv.unit == unit_second_str) { > + vald = uv.val.d; > + show_float = 1; > + } else { > + vald = vali = uv.val.i; > + } > + > + if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) { > + double secs; > + int hours, mins; > + secs = vald; > + mins = (int)secs / 60; > + secs = secs - mins * 60; > + hours = mins / 60; > + mins %= 60; > + snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); > + } else { > + const char *prefix_string = ""; > + > + if (tctx->use_value_prefix && vald > 1) { > + int64_t index; > + > + if (uv.unit == unit_byte_str && tctx->use_byte_value_binary_prefix) { > + index = (int64_t) (log2(vald)) / 10; > + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); > + vald /= si_prefixes[index].bin_val; > + prefix_string = si_prefixes[index].bin_str; > + } else { > + index = (int64_t) (log10(vald)) / 3; > + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); > + vald /= si_prefixes[index].dec_val; > + prefix_string = si_prefixes[index].dec_str; > + } > + vali = vald; > + } > + > + if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald)) > + snprintf(buf, buf_size, "%f", vald); > + else > + snprintf(buf, buf_size, "%"PRId64, vali); > + av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || tctx->show_value_unit ? " " : "", > + prefix_string, tctx->show_value_unit ? uv.unit : ""); > + } > + > + return buf; > +} > + > + > +void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit) > +{ > + char val_str[128]; > + struct unit_value uv; > + uv.val.i = value; > + uv.unit = unit; > + avtext_print_string(tctx, key, value_string(tctx, val_str, sizeof(val_str), uv), 0); > +} > + > + > +int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags) > +{ > + const struct AVTextFormatSection *section = tctx->section[tctx->level]; > + int ret = 0; > + > + if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER || > + (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO > + && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL) > + && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))) > + return 0; > + > + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { > + if (flags & AV_TEXTFORMAT_PRINT_STRING_VALIDATE) { > + char *key1 = NULL, *val1 = NULL; > + ret = validate_string(tctx, &key1, key); > + if (ret < 0) goto end; > + ret = validate_string(tctx, &val1, val); > + if (ret < 0) goto end; > + tctx->formatter->print_string(tctx, key1, val1); > + end: > + if (ret < 0) { > + av_log(tctx, AV_LOG_ERROR, > + "Invalid key=value string combination %s=%s in section %s\n", > + key, val, section->unique_name); > + } > + av_free(key1); > + av_free(val1); > + } else { > + tctx->formatter->print_string(tctx, key, val); > + } > + > + tctx->nb_item[tctx->level]++; > + } > + > + return ret; > +} > + > +void avtext_print_rational(AVTextFormatContext *tctx, > + const char *key, AVRational q, char sep) > +{ > + AVBPrint buf; > + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); > + av_bprintf(&buf, "%d%c%d", q.num, sep, q.den); > + avtext_print_string(tctx, key, buf.str, 0); > +} > + > +void avtext_print_time(AVTextFormatContext *tctx, const char *key, > + int64_t ts, const AVRational *time_base, int is_duration) > +{ > + char buf[128]; > + > + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { > + avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL); > + } else { > + double d = ts * av_q2d(*time_base); > + struct unit_value uv; > + uv.val.d = d; > + uv.unit = unit_second_str; > + value_string(tctx, buf, sizeof(buf), uv); > + avtext_print_string(tctx, key, buf, 0); > + } > +} > + > +void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration) > +{ > + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { > + avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL); > + } else { > + avtext_print_integer(tctx, key, ts); > + } > +} > + > +void avtext_print_data(AVTextFormatContext *tctx, const char *name, > + const uint8_t *data, int size) > +{ > + AVBPrint bp; > + int offset = 0, l, i; > + > + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); > + av_bprintf(&bp, "\n"); > + while (size) { > + av_bprintf(&bp, "%08x: ", offset); > + l = FFMIN(size, 16); > + for (i = 0; i < l; i++) { > + av_bprintf(&bp, "%02x", data[i]); > + if (i & 1) > + av_bprintf(&bp, " "); > + } > + av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2); > + for (i = 0; i < l; i++) > + av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1); > + av_bprintf(&bp, "\n"); > + offset += l; > + data += l; > + size -= l; > + } > + avtext_print_string(tctx, name, bp.str, 0); > + av_bprint_finalize(&bp, NULL); > +} > + > +void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, > + const uint8_t *data, int size) > +{ > + char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 }; > + > + if (!tctx->hash) > + return; > + av_hash_init(tctx->hash); > + av_hash_update(tctx->hash, data, size); > + snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash)); > + p = buf + strlen(buf); > + av_hash_final_hex(tctx->hash, p, buf + sizeof(buf) - p); > + avtext_print_string(tctx, name, buf, 0); > +} > + > +void avtext_print_integers(AVTextFormatContext *tctx, const char *name, > + uint8_t *data, int size, const char *format, > + int columns, int bytes, int offset_add) > +{ > + AVBPrint bp; > + int offset = 0, l, i; > + > + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); > + av_bprintf(&bp, "\n"); > + while (size) { > + av_bprintf(&bp, "%08x: ", offset); > + l = FFMIN(size, columns); > + for (i = 0; i < l; i++) { > + if (bytes == 1) av_bprintf(&bp, format, *data); > + else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data)); > + else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data)); > + data += bytes; > + size --; > + } > + av_bprintf(&bp, "\n"); > + offset += offset_add; > + } > + avtext_print_string(tctx, name, bp.str, 0); > + av_bprint_finalize(&bp, NULL); > +} > + > +static const char *avtextwriter_context_get_writer_name(void *p) you can also drop the public av prefix here > +{ > + 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 = avtextwriter_context_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 -1; AVERROR(EINVAL) > + > + if (wctx->writer->uninit) > + wctx->writer->uninit(wctx); > + if (wctx->writer->priv_class) > + av_opt_free(wctx->priv); > + av_freep(&wctx->priv); > + av_freep(pwctx); > + return ret; > +} > + > + > +int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer) > +{ > + AVTextWriterContext *wctx; > + int ret = 0; > + > + if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) { > + ret = AVERROR(ENOMEM); > + goto fail; > + } > + > + if (!(wctx->priv = av_mallocz(writer->priv_size))) { > + ret = AVERROR(ENOMEM); > + goto fail; > + } > + > + if (writer->priv_class) { > + void *priv_ctx = wctx->priv; > + *(const AVClass **)priv_ctx = writer->priv_class; > + av_opt_set_defaults(priv_ctx); > + } > + > + wctx->class = &textwriter_class; > + wctx->writer = writer; > + > + av_opt_set_defaults(wctx); > + > + > + if (wctx->writer->init) > + ret = wctx->writer->init(wctx); > + if (ret < 0) > + goto fail; > + > + *pwctx = wctx; > + > + return 0; > + > +fail: > + avtextwriter_context_close(&wctx); > + return ret; > +} > + > +static const AVTextFormatter *registered_formatters[7+1]; maybe use a const here, also I'd be more happy if we had a more dynamic registration system to avoid the hardcoded bits > +static void formatters_register_all(void) > +{ > + static int initialized; > + > + if (initialized) > + return; > + initialized = 1; > + > + registered_formatters[0] = &avtextformatter_default; > + registered_formatters[1] = &avtextformatter_compact; > + registered_formatters[2] = &avtextformatter_csv; > + registered_formatters[3] = &avtextformatter_flat; > + registered_formatters[4] = &avtextformatter_ini; > + registered_formatters[5] = &avtextformatter_json; > + registered_formatters[6] = &avtextformatter_xml; > +} > + > +const AVTextFormatter *avtext_get_formatter_by_name(const char *name) > +{ > + formatters_register_all(); > + > + for (int i = 0; registered_formatters[i]; i++) > + if (!strcmp(registered_formatters[i]->name, name)) > + return registered_formatters[i]; > + > + return NULL; > +} > diff --git a/fftools/textformat/avtextformat.h b/fftools/textformat/avtextformat.h > new file mode 100644 > index 0000000000..b7b6e0eea0 > --- /dev/null > +++ b/fftools/textformat/avtextformat.h > @@ -0,0 +1,171 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#ifndef FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H > +#define FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H > + > +#include > +#include > +#include "libavutil/attributes.h" > +#include "libavutil/dict.h" > +#include "libavformat/avio.h" > +#include "libavutil/bprint.h" > +#include "libavutil/rational.h" > +#include "libavutil/hash.h" > +#include "avtextwriters.h" > + > +#define SECTION_MAX_NB_CHILDREN 11 > + > + > +struct AVTextFormatSection { > + int id; ///< unique id identifying a section > + const char *name; > + > +#define AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level > +#define AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type > +#define AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. > + /// For these sections the element_name field is mandatory. > +#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements > +#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type > + > + int flags; > + const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 > + const char *element_name; ///< name of the contained element, if provided > + const char *unique_name; ///< unique section name, in case the name is ambiguous > + AVDictionary *entries_to_show; > + const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined > + int show_all_entries; > +} AVTextFormatSection; > + > +typedef struct AVTextFormatContext AVTextFormatContext; > + > +#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1 > +#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2 > + > +typedef enum { > + AV_TEXTFORMAT_STRING_VALIDATION_FAIL, > + AV_TEXTFORMAT_STRING_VALIDATION_REPLACE, > + AV_TEXTFORMAT_STRING_VALIDATION_IGNORE, > + AV_TEXTFORMAT_STRING_VALIDATION_NB > +} StringValidation; > + > +typedef struct AVTextFormatter { > + const AVClass *priv_class; ///< private class of the formatter, if any > + int priv_size; ///< private size for the formatter context > + const char *name; > + > + int (*init) (AVTextFormatContext *tctx); > + void (*uninit)(AVTextFormatContext *tctx); > + > + void (*print_section_header)(AVTextFormatContext *tctx, const void *data); > + void (*print_section_footer)(AVTextFormatContext *tctx); > + void (*print_integer) (AVTextFormatContext *tctx, const char *, int64_t); > + void (*print_rational) (AVTextFormatContext *tctx, AVRational *q, char *sep); > + void (*print_string) (AVTextFormatContext *tctx, const char *, const char *); > + int flags; ///< a combination or AV_TEXTFORMAT__FLAG_* > +} AVTextFormatter; > + > +#define SECTION_MAX_NB_LEVELS 12 > +#define SECTION_MAX_NB_SECTIONS 100 > + > +struct AVTextFormatContext { > + const AVClass *class; ///< class of the formatter > + const AVTextFormatter *formatter; ///< the AVTextFormatter of which this is an instance > + AVTextWriterContext *writer; ///< the AVTextWriterContext > + > + char *name; ///< name of this formatter instance > + void *priv; ///< private data for use by the filter > + > + const struct AVTextFormatSection *sections; ///< array containing all sections > + int nb_sections; ///< number of sections > + > + int level; ///< current level, starting from 0 > + > + /** number of the item printed in the given section, starting from 0 */ > + unsigned int nb_item[SECTION_MAX_NB_LEVELS]; > + unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS]; > + > + /** section per each level */ > + const struct AVTextFormatSection *section[SECTION_MAX_NB_LEVELS]; > + AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, > + /// used by various formatters > + > + int show_optional_fields; > + int show_value_unit; > + int use_value_prefix; > + int use_byte_value_binary_prefix; > + int use_value_sexagesimal_format; > + > + struct AVHashContext *hash; > + > + int string_validation; > + char *string_validation_replacement; > + unsigned int string_validation_utf8_flags; > +}; > + > +#define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1 > +#define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2 > + > +int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer, const char *args, > + const struct AVTextFormatSection *sections, int nb_sections, > + int show_value_unit, > + int use_value_prefix, > + int use_byte_value_binary_prefix, > + int use_value_sexagesimal_format, > + int show_optional_fields, this might be later changed to flags to simplify the interface > + char *show_data_hash); > + > +int avtext_context_close(AVTextFormatContext **tctx); > + > + > +void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id); > + > +void avtext_print_section_footer(AVTextFormatContext *tctx); > + > +void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val); > + > +int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags); > + > +void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit); > + > +void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep); > + > +void avtext_print_time(AVTextFormatContext *tctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration); > + > +void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration); > + > +void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size); > + > +void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size); > + > +void avtext_print_integers(AVTextFormatContext *tctx, const char *name, uint8_t *data, int size, > + const char *format, int columns, int bytes, int offset_add); > + > +const AVTextFormatter *avtext_get_formatter_by_name(const char *name); > + > +extern const AVTextFormatter avtextformatter_default; > +extern const AVTextFormatter avtextformatter_compact; > +extern const AVTextFormatter avtextformatter_csv; > +extern const AVTextFormatter avtextformatter_flat; > +extern const AVTextFormatter avtextformatter_ini; > +extern const AVTextFormatter avtextformatter_json; > +extern const AVTextFormatter avtextformatter_xml; > + > +#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */ > diff --git a/fftools/textformat/avtextwriters.h b/fftools/textformat/avtextwriters.h > new file mode 100644 > index 0000000000..b344881d05 > --- /dev/null > +++ b/fftools/textformat/avtextwriters.h > @@ -0,0 +1,68 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#ifndef FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H > +#define FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H > + > +#include > +#include > +#include "libavutil/attributes.h" > +#include "libavutil/dict.h" > +#include "libavformat/avio.h" > +#include "libavutil/bprint.h" > +#include "libavutil/rational.h" > +#include "libavutil/hash.h" > + > +typedef struct AVTextWriterContext AVTextWriterContext; > + > +typedef struct AVTextWriter { > + const AVClass *priv_class; ///< private class of the writer, if any > + int priv_size; ///< private size for the writer private class > + const char *name; > + > + int (* init)(AVTextWriterContext *wctx); > + void (* uninit)(AVTextWriterContext *wctx); > + void (* writer_w8)(AVTextWriterContext *wctx, int b); > + void (* writer_put_str)(AVTextWriterContext *wctx, const char *str); > + void (* writer_printf)(AVTextWriterContext *wctx, const char *fmt, ...); > +} AVTextWriter; > + > +typedef struct AVTextWriterContext { > + const AVClass *class; ///< class of the writer > + const AVTextWriter *writer; > + const char *name; > + void *priv; ///< private data for use by the writer > + > +} AVTextWriterContext; > + > + > +int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer); > + > +int avtextwriter_context_close(AVTextWriterContext **pwctx); > + > +int avtextwriter_create_stdout(AVTextWriterContext **pwctx); > + > +int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit); > + > +int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit); > + > +int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer); > + > +#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */ > diff --git a/fftools/textformat/tf_compact.c b/fftools/textformat/tf_compact.c > new file mode 100644 > index 0000000000..ad07ca4bd0 > --- /dev/null > +++ b/fftools/textformat/tf_compact.c > @@ -0,0 +1,282 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > +#include > +#include > + > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > + > +/* Compact output */ > + > +/** > + * Apply C-language-like string escaping. > + */ > +static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) > +{ > + const char *p; > + > + for (p = src; *p; p++) { > + switch (*p) { > + case '\b': av_bprintf(dst, "%s", "\\b"); break; > + case '\f': av_bprintf(dst, "%s", "\\f"); break; > + case '\n': av_bprintf(dst, "%s", "\\n"); break; > + case '\r': av_bprintf(dst, "%s", "\\r"); break; > + case '\\': av_bprintf(dst, "%s", "\\\\"); break; > + default: > + if (*p == sep) > + av_bprint_chars(dst, '\\', 1); > + av_bprint_chars(dst, *p, 1); > + } > + } > + return dst->str; > +} > + > +/** > + * Quote fields containing special characters, check RFC4180. > + */ totally unrelated fun fact, I later discovered this is not supported by MS Excel: https://answers.microsoft.com/en-us/msoffice/forum/all/why-excel-does-not-support-rfc-4180-standard-for/d0e379b1-ec3e-40d0-ad4f-33b20693c030 > +static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) > +{ > + char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; > + int needs_quoting = !!src[strcspn(src, meta_chars)]; > + > + if (needs_quoting) > + av_bprint_chars(dst, '"', 1); > + > + for (; *src; src++) { > + if (*src == '"') > + av_bprint_chars(dst, '"', 1); > + av_bprint_chars(dst, *src, 1); > + } > + if (needs_quoting) > + av_bprint_chars(dst, '"', 1); > + return dst->str; > +} > + > +static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) > +{ > + return src; > +} > + > +typedef struct CompactContext { > + const AVClass *class; > + char *item_sep_str; > + char item_sep; > + int nokey; > + int print_section; > + char *escape_mode_str; > + const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); > + int nested_section[SECTION_MAX_NB_LEVELS]; > + int has_nested_elems[SECTION_MAX_NB_LEVELS]; > + int terminate_line[SECTION_MAX_NB_LEVELS]; > +} CompactContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(CompactContext, x) > + > +static const AVOption compact_options[]= { > + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, > + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, > + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, > + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, > + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(compact); > + > +static av_cold int compact_init(AVTextFormatContext *wctx) > +{ > + CompactContext *compact = wctx->priv; > + > + if (strlen(compact->item_sep_str) != 1) { > + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", > + compact->item_sep_str); > + return AVERROR(EINVAL); > + } > + compact->item_sep = compact->item_sep_str[0]; > + > + if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; > + else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; > + else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; > + else { > + av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); > + return AVERROR(EINVAL); > + } > + > + return 0; > +} > + > +static void compact_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + CompactContext *compact = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + compact->terminate_line[wctx->level] = 1; > + compact->has_nested_elems[wctx->level] = 0; > + > + av_bprint_clear(&wctx->section_pbuf[wctx->level]); > + if (parent_section && > + (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE || > + (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) && > + !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) { > + > + /* define a prefix for elements not contained in an array or > + in a wrapper, or for array elements with a type */ > + const char *element_name = (char *)av_x_if_null(section->element_name, section->name); > + AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level]; > + > + compact->nested_section[wctx->level] = 1; > + compact->has_nested_elems[wctx->level-1] = 1; > + > + av_bprintf(section_pbuf, "%s%s", > + wctx->section_pbuf[wctx->level-1].str, element_name); > + > + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { > + // add /TYPE to prefix > + av_bprint_chars(section_pbuf, '/', 1); > + > + // normalize section type, replace special characters and lower case > + for (const char *p = section->get_type(data); *p; p++) { > + char c = > + (*p >= '0' && *p <= '9') || > + (*p >= 'a' && *p <= 'z') || > + (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_'; > + av_bprint_chars(section_pbuf, c, 1); > + } > + } > + av_bprint_chars(section_pbuf, ':', 1); > + > + wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; > + } else { > + if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) && > + wctx->level && wctx->nb_item[wctx->level-1]) > + writer_w8(wctx, compact->item_sep); > + if (compact->print_section && > + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) > + writer_printf(wctx, "%s%c", section->name, compact->item_sep); > + } > +} > + > +static void compact_print_section_footer(AVTextFormatContext *wctx) > +{ > + CompactContext *compact = wctx->priv; > + > + if (!compact->nested_section[wctx->level] && > + compact->terminate_line[wctx->level] && > + !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) > + writer_w8(wctx, '\n'); > +} > + > +static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value) > +{ > + CompactContext *compact = wctx->priv; > + AVBPrint buf; > + > + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); > + if (!compact->nokey) > + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx)); > + av_bprint_finalize(&buf, NULL); > +} > + > +static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + CompactContext *compact = wctx->priv; > + > + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); > + if (!compact->nokey) > + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); > + writer_printf(wctx, "%"PRId64, value); > +} > + > +const AVTextFormatter avtextformatter_compact = { > + .name = "compact", > + .priv_size = sizeof(CompactContext), > + .init = compact_init, > + .print_section_header = compact_print_section_header, > + .print_section_footer = compact_print_section_footer, > + .print_integer = compact_print_int, > + .print_string = compact_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, > + .priv_class = &compact_class, > +}; > + > +/* CSV output */ > + > +#undef OFFSET > +#define OFFSET(x) offsetof(CompactContext, x) > + > +static const AVOption csv_options[] = { > + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, > + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, > + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, > + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, > + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(csv); > + > +const AVTextFormatter avtextformatter_csv = { > + .name = "csv", > + .priv_size = sizeof(CompactContext), > + .init = compact_init, > + .print_section_header = compact_print_section_header, > + .print_section_footer = compact_print_section_footer, > + .print_integer = compact_print_int, > + .print_string = compact_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, > + .priv_class = &csv_class, > +}; > diff --git a/fftools/textformat/tf_default.c b/fftools/textformat/tf_default.c > new file mode 100644 > index 0000000000..9625dd813b > --- /dev/null > +++ b/fftools/textformat/tf_default.c > @@ -0,0 +1,145 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > +/* Default output */ > + > +typedef struct DefaultContext { > + const AVClass *class; > + int nokey; > + int noprint_wrappers; > + int nested_section[SECTION_MAX_NB_LEVELS]; > +} DefaultContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(DefaultContext, x) > + > +static const AVOption default_options[] = { > + { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + { "nw", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(default); > + > +/* lame uppercasing routine, assumes the string is lower case ASCII */ > +static inline char *upcase_string(char *dst, size_t dst_size, const char *src) > +{ > + int i; > + for (i = 0; src[i] && i < dst_size-1; i++) > + dst[i] = av_toupper(src[i]); > + dst[i] = 0; > + return dst; > +} > + > +static void default_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + DefaultContext *def = wctx->priv; > + char buf[32]; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + av_bprint_clear(&wctx->section_pbuf[wctx->level]); > + if (parent_section && > + !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) { > + def->nested_section[wctx->level] = 1; > + av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", > + wctx->section_pbuf[wctx->level-1].str, > + upcase_string(buf, sizeof(buf), > + av_x_if_null(section->element_name, section->name))); > + } > + > + if (def->noprint_wrappers || def->nested_section[wctx->level]) > + return; > + > + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) > + writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name)); > +} > + > +static void default_print_section_footer(AVTextFormatContext *wctx) > +{ > + DefaultContext *def = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + char buf[32]; > + > + if (def->noprint_wrappers || def->nested_section[wctx->level]) > + return; > + > + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) > + writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name)); > +} > + > +static void default_print_str(AVTextFormatContext *wctx, const char *key, const char *value) > +{ > + DefaultContext *def = wctx->priv; > + > + if (!def->nokey) > + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); > + writer_printf(wctx, "%s\n", value); > +} > + > +static void default_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + DefaultContext *def = wctx->priv; > + > + if (!def->nokey) > + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); > + writer_printf(wctx, "%"PRId64"\n", value); > +} > + > +const AVTextFormatter avtextformatter_default = { > + .name = "default", > + .priv_size = sizeof(DefaultContext), > + .print_section_header = default_print_section_header, > + .print_section_footer = default_print_section_footer, > + .print_integer = default_print_int, > + .print_string = default_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, > + .priv_class = &default_class, > +}; > \ No newline at end of file > diff --git a/fftools/textformat/tf_flat.c b/fftools/textformat/tf_flat.c > new file mode 100644 > index 0000000000..afdc494aee > --- /dev/null > +++ b/fftools/textformat/tf_flat.c > @@ -0,0 +1,174 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > + > +/* Flat output */ > + > +typedef struct FlatContext { > + const AVClass *class; > + const char *sep_str; > + char sep; > + int hierarchical; > +} FlatContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(FlatContext, x) > + > +static const AVOption flat_options[]= { > + {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 }, > + {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 }, > + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(flat); > + > +static av_cold int flat_init(AVTextFormatContext *wctx) > +{ > + FlatContext *flat = wctx->priv; > + > + if (strlen(flat->sep_str) != 1) { > + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", > + flat->sep_str); > + return AVERROR(EINVAL); > + } > + flat->sep = flat->sep_str[0]; > + > + return 0; > +} > + > +static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep) > +{ > + const char *p; > + > + for (p = src; *p; p++) { > + if (!((*p >= '0' && *p <= '9') || > + (*p >= 'a' && *p <= 'z') || > + (*p >= 'A' && *p <= 'Z'))) > + av_bprint_chars(dst, '_', 1); > + else > + av_bprint_chars(dst, *p, 1); > + } > + return dst->str; > +} > + > +static const char *flat_escape_value_str(AVBPrint *dst, const char *src) > +{ > + const char *p; > + > + for (p = src; *p; p++) { > + switch (*p) { > + case '\n': av_bprintf(dst, "%s", "\\n"); break; > + case '\r': av_bprintf(dst, "%s", "\\r"); break; > + case '\\': av_bprintf(dst, "%s", "\\\\"); break; > + case '"': av_bprintf(dst, "%s", "\\\""); break; > + case '`': av_bprintf(dst, "%s", "\\`"); break; > + case '$': av_bprintf(dst, "%s", "\\$"); break; > + default: av_bprint_chars(dst, *p, 1); break; > + } > + } > + return dst->str; > +} > + > +static void flat_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + FlatContext *flat = wctx->priv; > + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + /* build section header */ > + av_bprint_clear(buf); > + if (!parent_section) > + return; > + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); > + > + if (flat->hierarchical || > + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) { > + av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str); > + > + if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { > + int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ? > + wctx->nb_item_type[wctx->level-1][section->id] : > + wctx->nb_item[wctx->level-1]; > + av_bprintf(buf, "%d%s", n, flat->sep_str); > + } > + } > +} > + > +static void flat_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value); > +} > + > +static void flat_print_str(AVTextFormatContext *wctx, const char *key, const char *value) > +{ > + FlatContext *flat = wctx->priv; > + AVBPrint buf; > + > + writer_put_str(wctx, wctx->section_pbuf[wctx->level].str); > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep)); > + av_bprint_clear(&buf); > + writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value)); > + av_bprint_finalize(&buf, NULL); > +} > + > +const AVTextFormatter avtextformatter_flat = { > + .name = "flat", > + .priv_size = sizeof(FlatContext), > + .init = flat_init, > + .print_section_header = flat_print_section_header, > + .print_integer = flat_print_int, > + .print_string = flat_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, > + .priv_class = &flat_class, > +}; > diff --git a/fftools/textformat/tf_ini.c b/fftools/textformat/tf_ini.c > new file mode 100644 > index 0000000000..99a9af5690 > --- /dev/null > +++ b/fftools/textformat/tf_ini.c > @@ -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 > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > +/* Default output */ > + > +typedef struct DefaultContext { > + const AVClass *class; > + int nokey; > + int noprint_wrappers; > + int nested_section[SECTION_MAX_NB_LEVELS]; > +} DefaultContext; > + > +/* INI format output */ > + > +typedef struct INIContext { > + const AVClass *class; > + int hierarchical; > +} INIContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(INIContext, x) > + > +static const AVOption ini_options[] = { > + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(ini); > + > +static char *ini_escape_str(AVBPrint *dst, const char *src) > +{ > + int i = 0; > + char c = 0; > + > + while (c = src[i++]) { > + switch (c) { > + case '\b': av_bprintf(dst, "%s", "\\b"); break; > + case '\f': av_bprintf(dst, "%s", "\\f"); break; > + case '\n': av_bprintf(dst, "%s", "\\n"); break; > + case '\r': av_bprintf(dst, "%s", "\\r"); break; > + case '\t': av_bprintf(dst, "%s", "\\t"); break; > + case '\\': > + case '#' : > + case '=' : > + case ':' : av_bprint_chars(dst, '\\', 1); > + default: > + if ((unsigned char)c < 32) > + av_bprintf(dst, "\\x00%02x", c & 0xff); > + else > + av_bprint_chars(dst, c, 1); > + break; > + } > + } > + return dst->str; > +} > + > +static void ini_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + INIContext *ini = wctx->priv; > + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + av_bprint_clear(buf); > + if (!parent_section) { > + writer_put_str(wctx, "# ffprobe output\n\n"); > + return; > + } > + > + if (wctx->nb_item[wctx->level-1]) > + writer_w8(wctx, '\n'); > + > + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); > + if (ini->hierarchical || > + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) { > + av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name); > + > + if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { > + int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ? > + wctx->nb_item_type[wctx->level-1][section->id] : > + wctx->nb_item[wctx->level-1]; > + av_bprintf(buf, ".%d", n); > + } > + } > + > + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) > + writer_printf(wctx, "[%s]\n", buf->str); > +} > + > +static void ini_print_str(AVTextFormatContext *wctx, const char *key, const char *value) > +{ > + AVBPrint buf; > + > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + writer_printf(wctx, "%s=", ini_escape_str(&buf, key)); > + av_bprint_clear(&buf); > + writer_printf(wctx, "%s\n", ini_escape_str(&buf, value)); > + av_bprint_finalize(&buf, NULL); > +} > + > +static void ini_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + writer_printf(wctx, "%s=%"PRId64"\n", key, value); > +} > + > +const AVTextFormatter avtextformatter_ini = { > + .name = "ini", > + .priv_size = sizeof(INIContext), > + .print_section_header = ini_print_section_header, > + .print_integer = ini_print_int, > + .print_string = ini_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, > + .priv_class = &ini_class, > +}; > diff --git a/fftools/textformat/tf_json.c b/fftools/textformat/tf_json.c > new file mode 100644 > index 0000000000..4579c8a3d9 > --- /dev/null > +++ b/fftools/textformat/tf_json.c > @@ -0,0 +1,215 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > + > +/* JSON output */ > + > +typedef struct JSONContext { > + const AVClass *class; > + int indent_level; > + int compact; > + const char *item_sep, *item_start_end; > +} JSONContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(JSONContext, x) > + > +static const AVOption json_options[]= { > + { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + { "c", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + { NULL } > +}; > + > +DEFINE_FORMATTER_CLASS(json); > + > +static av_cold int json_init(AVTextFormatContext *wctx) > +{ > + JSONContext *json = wctx->priv; > + > + json->item_sep = json->compact ? ", " : ",\n"; > + json->item_start_end = json->compact ? " " : "\n"; > + > + return 0; > +} > + > +static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx) > +{ > + static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0}; > + static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0}; > + const char *p; > + > + for (p = src; *p; p++) { > + char *s = strchr(json_escape, *p); > + if (s) { > + av_bprint_chars(dst, '\\', 1); > + av_bprint_chars(dst, json_subst[s - json_escape], 1); > + } else if ((unsigned char)*p < 32) { > + av_bprintf(dst, "\\u00%02x", *p & 0xff); > + } else { > + av_bprint_chars(dst, *p, 1); > + } > + } > + return dst->str; > +} > + > +#define JSON_INDENT() writer_printf(wctx, "%*c", json->indent_level * 4, ' ') > + > +static void json_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + JSONContext *json = wctx->priv; > + AVBPrint buf; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + if (wctx->level && wctx->nb_item[wctx->level-1]) > + writer_put_str(wctx, ",\n"); > + > + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) { > + writer_put_str(wctx, "{\n"); > + json->indent_level++; > + } else { > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + json_escape_str(&buf, section->name, wctx); > + JSON_INDENT(); > + > + json->indent_level++; > + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { > + writer_printf(wctx, "\"%s\": [\n", buf.str); > + } else if (parent_section && !(parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) { > + writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end); > + } else { > + writer_printf(wctx, "{%s", json->item_start_end); > + > + /* this is required so the parser can distinguish between packets and frames */ > + if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE) { > + if (!json->compact) > + JSON_INDENT(); > + writer_printf(wctx, "\"type\": \"%s\"", section->name); > + wctx->nb_item[wctx->level]++; > + } > + } > + av_bprint_finalize(&buf, NULL); > + } > +} > + > +static void json_print_section_footer(AVTextFormatContext *wctx) > +{ > + JSONContext *json = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + > + if (wctx->level == 0) { > + json->indent_level--; > + writer_put_str(wctx, "\n}\n"); > + } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { > + writer_w8(wctx, '\n'); > + json->indent_level--; > + JSON_INDENT(); > + writer_w8(wctx, ']'); > + } else { > + writer_put_str(wctx, json->item_start_end); > + json->indent_level--; > + if (!json->compact) > + JSON_INDENT(); > + writer_w8(wctx, '}'); > + } > +} > + > +static inline void json_print_item_str(AVTextFormatContext *wctx, > + const char *key, const char *value) > +{ > + AVBPrint buf; > + > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx)); > + av_bprint_clear(&buf); > + writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx)); > + av_bprint_finalize(&buf, NULL); > +} > + > +static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value) > +{ > + JSONContext *json = wctx->priv; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE)) > + writer_put_str(wctx, json->item_sep); > + if (!json->compact) > + JSON_INDENT(); > + json_print_item_str(wctx, key, value); > +} > + > +static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + JSONContext *json = wctx->priv; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + AVBPrint buf; > + > + if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE)) > + writer_put_str(wctx, json->item_sep); > + if (!json->compact) > + JSON_INDENT(); > + > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + writer_printf(wctx, "\"%s\": %"PRId64, json_escape_str(&buf, key, wctx), value); > + av_bprint_finalize(&buf, NULL); > +} > + > +const AVTextFormatter avtextformatter_json = { > + .name = "json", > + .priv_size = sizeof(JSONContext), > + .init = json_init, > + .print_section_header = json_print_section_header, > + .print_section_footer = json_print_section_footer, > + .print_integer = json_print_int, > + .print_string = json_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, > + .priv_class = &json_class, > +}; > + > diff --git a/fftools/textformat/tf_xml.c b/fftools/textformat/tf_xml.c > new file mode 100644 > index 0000000000..04c43fb85d > --- /dev/null > +++ b/fftools/textformat/tf_xml.c > @@ -0,0 +1,221 @@ > +/* > + * Copyright (c) The ffmpeg developers > + * > + * This file is part of FFmpeg. > + * > + * FFmpeg is free software; you can redistribute it and/or > + * modify it under the terms of the GNU Lesser General Public > + * License as published by the Free Software Foundation; either > + * version 2.1 of the License, or (at your option) any later version. > + * > + * FFmpeg is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > + * Lesser General Public License for more details. > + * > + * You should have received a copy of the GNU Lesser General Public > + * License along with FFmpeg; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA > + */ > + > +#include > +#include > +#include > +#include > +#include > + > +#include "avtextformat.h" > +#include > +#include > +#include > +#include > +#include > +#include > + > +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) > +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) > +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) > + > +#define DEFINE_FORMATTER_CLASS(name) \ > +static const char *name##_get_name(void *ctx) \ > +{ \ > + return #name ; \ > +} \ > +static const AVClass name##_class = { \ > + .class_name = #name, \ > + .item_name = name##_get_name, \ > + .option = name##_options \ > +} > + > +/* XML output */ > + > +typedef struct XMLContext { > + const AVClass *class; > + int within_tag; > + int indent_level; > + int fully_qualified; > + int xsd_strict; > +} XMLContext; > + > +#undef OFFSET > +#define OFFSET(x) offsetof(XMLContext, x) > + > +static const AVOption xml_options[] = { > + {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, > + {NULL}, > +}; > + > +DEFINE_FORMATTER_CLASS(xml); > + > +static av_cold int xml_init(AVTextFormatContext *wctx) > +{ > + XMLContext *xml = wctx->priv; > + > + if (xml->xsd_strict) { > + xml->fully_qualified = 1; > +#define CHECK_COMPLIANCE(opt, opt_name) \ > + if (opt) { \ > + av_log(wctx, AV_LOG_ERROR, \ > + "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \ > + "You need to disable such option with '-no%s'\n", opt_name, opt_name); \ > + return AVERROR(EINVAL); \ > + } > + ////CHECK_COMPLIANCE(show_private_data, "private"); > + CHECK_COMPLIANCE(wctx->show_value_unit, "unit"); > + CHECK_COMPLIANCE(wctx->use_value_prefix, "prefix"); > + } > + > + return 0; > +} > + > +#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ') > + > +static void xml_print_section_header(AVTextFormatContext *wctx, const void *data) > +{ > + XMLContext *xml = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + const struct AVTextFormatSection *parent_section = wctx->level ? > + wctx->section[wctx->level-1] : NULL; > + > + if (wctx->level == 0) { > + const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " > + "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" " > + "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\""; > + > + writer_put_str(wctx, "\n"); > + writer_printf(wctx, "<%sffprobe%s>\n", > + xml->fully_qualified ? "ffprobe:" : "", > + xml->fully_qualified ? qual : ""); > + return; > + } > + > + if (xml->within_tag) { > + xml->within_tag = 0; > + writer_put_str(wctx, ">\n"); > + } > + > + if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) && > + wctx->level && wctx->nb_item[wctx->level-1]) > + writer_w8(wctx, '\n'); > + xml->indent_level++; > + > + if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) { > + XML_INDENT(); writer_printf(wctx, "<%s", section->name); > + > + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { > + AVBPrint buf; > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + av_bprint_escape(&buf, section->get_type(data), NULL, > + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); > + writer_printf(wctx, " type=\"%s\"", buf.str); > + } > + writer_printf(wctx, ">\n", section->name); > + } else { > + XML_INDENT(); writer_printf(wctx, "<%s ", section->name); > + xml->within_tag = 1; > + } > +} > + > +static void xml_print_section_footer(AVTextFormatContext *wctx) > +{ > + XMLContext *xml = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + > + if (wctx->level == 0) { > + writer_printf(wctx, "\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, "\n", section->name); > + xml->indent_level--; > + } > +} > + > +static void xml_print_value(AVTextFormatContext *wctx, const char *key, > + const char *str, int64_t num, const int is_int) > +{ > + AVBPrint buf; > + XMLContext *xml = wctx->priv; > + const struct AVTextFormatSection *section = wctx->section[wctx->level]; > + > + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); > + > + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) { > + xml->indent_level++; > + XML_INDENT(); > + av_bprint_escape(&buf, key, NULL, > + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); > + writer_printf(wctx, "<%s key=\"%s\"", > + section->element_name, buf.str); > + av_bprint_clear(&buf); > + > + if (is_int) { > + writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num); > + } else { > + av_bprint_escape(&buf, str, NULL, > + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); > + writer_printf(wctx, " value=\"%s\"/>\n", buf.str); > + } > + xml->indent_level--; > + } else { > + if (wctx->nb_item[wctx->level]) > + writer_w8(wctx, ' '); > + > + if (is_int) { > + writer_printf(wctx, "%s=\"%"PRId64"\"", key, num); > + } else { > + av_bprint_escape(&buf, str, NULL, > + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); > + writer_printf(wctx, "%s=\"%s\"", key, buf.str); > + } > + } > + > + av_bprint_finalize(&buf, NULL); > +} > + > +static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) { > + xml_print_value(wctx, key, value, 0, 0); > +} > + > +static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) > +{ > + xml_print_value(wctx, key, NULL, value, 1); > +} > + > +const AVTextFormatter avtextformatter_xml = { > + .name = "xml", > + .priv_size = sizeof(XMLContext), > + .init = xml_init, > + .print_section_header = xml_print_section_header, > + .print_section_footer = xml_print_section_footer, > + .print_integer = xml_print_int, > + .print_string = xml_print_str, > + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, > + .priv_class = &xml_class, > +}; I didn't review the formatters code assuming this was copied and adapted from the ffprobe.c file. [...] The rest looks good to me. _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".