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 D49894D9E1 for ; Thu, 27 Feb 2025 14:03:51 +0000 (UTC) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id BA12B68D086; Thu, 27 Feb 2025 16:02:31 +0200 (EET) Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 7005668CEF0 for ; Thu, 27 Feb 2025 16:02:29 +0200 (EET) Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-22128b7d587so18023755ad.3 for ; Thu, 27 Feb 2025 06:02:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1740664947; x=1741269747; darn=ffmpeg.org; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date :references:in-reply-to:message-id:from:from:to:cc:subject:date :message-id:reply-to; bh=hbW2XnFBuF5F8K1JWmMrIEXEGW7rmE/Qoio2kn51OJI=; b=VnxFzU9X4jRwgPYUF+1M5NrAZ+ZqP84aIfjd5nuaWM2DQQmaJJ0R6KMuHxbcEHZR0j /dXOvsa1vsnUpOB34yY3NhrGsJsnjrrk2pyuwKLXoII9sMjgcSR8871I9SJajgUNIw9z C60p1XFOOt8WIu/UHwIjKAwLruZ6uC8BmSrXbtWzZkeFJ2HDboYr3/ZaP60+az0PMWay lDcbQS64NhuzyWi8S4IggUnCnhDnBgx3vHaC8uIaheypBHvWmPbDWEElAphX8wR1U6gL G17y2w3YTwbW0NfGrbtghQdmOAr3VTWHOjt82dgtYtuxUfcVvlrAwCqb6ppvCNKO2L9j MYGw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1740664948; x=1741269748; h=cc:to:mime-version:content-transfer-encoding:fcc:subject:date :references:in-reply-to:message-id:from:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to; bh=hbW2XnFBuF5F8K1JWmMrIEXEGW7rmE/Qoio2kn51OJI=; b=BMqRVUFWBVfN+fSLYPwvmXwN6W2WaMwEgil66uwGSF+cu67fwU3o+8fsFHkmwiVdwv qVRISF4vd70yI8pqgoJzqXwt6qysQgl1rVAHGRtVSW5qEMKjXohacxavt7KU3S7kdomt yEcuCC2plrajL3LYd4IxdaOFPOS7lAoiPbaSHMQFMnsBoRwDIpYnucNZr7H1slH5OurP FFQiDln6TTBJGP75cp8XL9z0Y90tSnom7MNhteWuu5QBIWrczScCzXmdaM3V9ifMCAKB PlIkz6mUvuXjvJ08ZzhXKBSK0K/3OQgD/3dj1Hd5jrGCKb1c5jfT//72kFIAJaUCjnTZ 8g5w== X-Gm-Message-State: AOJu0Yz5a3QIsaw0ikHSDtiTo9KTXkGNU7fnpvmCAptN29su3MzOBFUd TK/JcZNaxGsVHR+T6SIMk7eKmEjxVZeXiwaYCdVlsmDLjdhgwbUDlHS84Q== X-Gm-Gg: ASbGncultRV02zDlPY4+i5mdaLP14iIB7ZDt164/TSUug6BmaPPDfwDOLzvwqK6rM/k CSPaVHQDyctSVk9ZmvNQdvyrwVKUlgjgGg80FG4kZq3EbglHG33ndC062NPrqriZ+CqCQWhXicf XRMuwbw9m6IO3S8o24MnNwLxV9TxNkFN7oMCyuGBAvCxGRiLhyoIFFCy5k5TwCWofa2XR+LYj3t eoeJguNHz8n754K/p5Ka/VjPliFoIWTixoiWNmB2Is/w4nQ7yOd0QlQmilJ1b7D6jx8FsmHevvN QFkjVTvQH1bRWfB/tMiNgNNS2XQDC5HOBUDNdKrjPHIP X-Google-Smtp-Source: AGHT+IGIyrtODbVYcmavKt1HBLRieDVc/pfRYDPqmmqxrKjUflFEjtkbcchkukv9e2l/73UCr+Vvzg== X-Received: by 2002:a05:6a20:e68f:b0:1e1:ab8b:dda1 with SMTP id adf61e73a8af0-1f10ae72447mr13186266637.35.1740664946822; Thu, 27 Feb 2025 06:02:26 -0800 (PST) Received: from [127.0.0.1] (master.gitmailbox.com. [34.83.118.50]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-aee7dee0b96sm1424132a12.75.2025.02.27.06.02.26 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Thu, 27 Feb 2025 06:02:26 -0800 (PST) From: softworkz X-Google-Original-From: softworkz Message-Id: <47c4978975166f7c41d7e4cd0b32f2ca92203be6.1740664900.git.ffmpegagent@gmail.com> In-Reply-To: References: Date: Thu, 27 Feb 2025 14:01:39 +0000 Fcc: Sent MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 7/8] ffprobe/avtextformat: Move formatters to avutil 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: softworkz 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: From: softworkz Signed-off-by: softworkz --- fftools/ffprobe.c | 936 +----------------------------- libavutil/Makefile | 6 + libavutil/avtextformat.h | 8 + libavutil/textformat/tf_compact.c | 285 +++++++++ libavutil/textformat/tf_default.c | 150 +++++ libavutil/textformat/tf_flat.c | 177 ++++++ libavutil/textformat/tf_ini.c | 165 ++++++ libavutil/textformat/tf_json.c | 220 +++++++ libavutil/textformat/tf_xml.c | 224 +++++++ 9 files changed, 1244 insertions(+), 927 deletions(-) create mode 100644 libavutil/textformat/tf_compact.c create mode 100644 libavutil/textformat/tf_default.c create mode 100644 libavutil/textformat/tf_flat.c create mode 100644 libavutil/textformat/tf_ini.c create mode 100644 libavutil/textformat/tf_json.c create mode 100644 libavutil/textformat/tf_xml.c diff --git a/fftools/ffprobe.c b/fftools/ffprobe.c index 25eb3f0b61..28d499ac80 100644 --- a/fftools/ffprobe.c +++ b/fftools/ffprobe.c @@ -404,13 +404,7 @@ static void log_callback(void *ptr, int level, const char *fmt, va_list vl) #endif } -/* WRITERS API */ - - - -#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) -#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) -#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, fmt_, __VA_ARGS__) +/* FORMATTERS */ #define MAX_REGISTERED_FORMATTERS_NB 64 @@ -439,919 +433,7 @@ static const AVTextFormatter *formatter_get_by_name(const char *name) } -/* FORMATTERS */ - -#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); -} - -static const AVTextFormatter default_formatter = { - .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, -}; - -/* Compact output */ - -/** - * Apply C-language-like string escaping. - */ -static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) -{ - const char *p; - - for (p = src; *p; p++) { - switch (*p) { - case '\b': av_bprintf(dst, "%s", "\\b"); break; - case '\f': av_bprintf(dst, "%s", "\\f"); break; - case '\n': av_bprintf(dst, "%s", "\\n"); break; - case '\r': av_bprintf(dst, "%s", "\\r"); break; - case '\\': av_bprintf(dst, "%s", "\\\\"); break; - default: - if (*p == sep) - av_bprint_chars(dst, '\\', 1); - av_bprint_chars(dst, *p, 1); - } - } - return dst->str; -} - -/** - * Quote fields containing special characters, check RFC4180. - */ -static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) -{ - char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; - int needs_quoting = !!src[strcspn(src, meta_chars)]; - - if (needs_quoting) - av_bprint_chars(dst, '"', 1); - - for (; *src; src++) { - if (*src == '"') - av_bprint_chars(dst, '"', 1); - av_bprint_chars(dst, *src, 1); - } - if (needs_quoting) - av_bprint_chars(dst, '"', 1); - return dst->str; -} - -static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) -{ - return src; -} - -typedef struct CompactContext { - const AVClass *class; - char *item_sep_str; - char item_sep; - int nokey; - int print_section; - char *escape_mode_str; - const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); - int nested_section[SECTION_MAX_NB_LEVELS]; - int has_nested_elems[SECTION_MAX_NB_LEVELS]; - int terminate_line[SECTION_MAX_NB_LEVELS]; -} CompactContext; - -#undef OFFSET -#define OFFSET(x) offsetof(CompactContext, x) - -static const AVOption compact_options[]= { - {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, - {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, - {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, - {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, - {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, - {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, - {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, - {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, - {NULL}, -}; - -DEFINE_FORMATTER_CLASS(compact); - -static av_cold int compact_init(AVTextFormatContext *wctx) -{ - CompactContext *compact = wctx->priv; - - if (strlen(compact->item_sep_str) != 1) { - av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", - compact->item_sep_str); - return AVERROR(EINVAL); - } - compact->item_sep = compact->item_sep_str[0]; - - if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; - else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; - else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; - else { - av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); - return AVERROR(EINVAL); - } - - return 0; -} - -static void compact_print_section_header(AVTextFormatContext *wctx, const void *data) -{ - CompactContext *compact = wctx->priv; - const struct AVTextFormatSection *section = wctx->section[wctx->level]; - const struct AVTextFormatSection *parent_section = wctx->level ? - wctx->section[wctx->level-1] : NULL; - compact->terminate_line[wctx->level] = 1; - compact->has_nested_elems[wctx->level] = 0; - - av_bprint_clear(&wctx->section_pbuf[wctx->level]); - if (parent_section && - (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE || - (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) && - !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) { - - /* define a prefix for elements not contained in an array or - in a wrapper, or for array elements with a type */ - const char *element_name = (char *)av_x_if_null(section->element_name, section->name); - AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level]; - - compact->nested_section[wctx->level] = 1; - compact->has_nested_elems[wctx->level-1] = 1; - - av_bprintf(section_pbuf, "%s%s", - wctx->section_pbuf[wctx->level-1].str, element_name); - - if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { - // add /TYPE to prefix - av_bprint_chars(section_pbuf, '/', 1); - - // normalize section type, replace special characters and lower case - for (const char *p = section->get_type(data); *p; p++) { - char c = - (*p >= '0' && *p <= '9') || - (*p >= 'a' && *p <= 'z') || - (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_'; - av_bprint_chars(section_pbuf, c, 1); - } - } - av_bprint_chars(section_pbuf, ':', 1); - - wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; - } else { - if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) && - wctx->level && wctx->nb_item[wctx->level-1]) - writer_w8(wctx, compact->item_sep); - if (compact->print_section && - !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) - writer_printf(wctx, "%s%c", section->name, compact->item_sep); - } -} - -static void compact_print_section_footer(AVTextFormatContext *wctx) -{ - CompactContext *compact = wctx->priv; - - if (!compact->nested_section[wctx->level] && - compact->terminate_line[wctx->level] && - !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) - writer_w8(wctx, '\n'); -} - -static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value) -{ - CompactContext *compact = wctx->priv; - AVBPrint buf; - - if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); - if (!compact->nokey) - writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); - av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); - writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx)); - av_bprint_finalize(&buf, NULL); -} - -static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) -{ - CompactContext *compact = wctx->priv; - - if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); - if (!compact->nokey) - writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); - writer_printf(wctx, "%"PRId64, value); -} - -static const AVTextFormatter compact_formatter = { - .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); - -static const AVTextFormatter csv_formatter = { - .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, -}; - -/* 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); -} - -static const AVTextFormatter flat_formatter = { - .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, -}; - -/* 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); -} - -static const AVTextFormatter ini_formatter = { - .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, -}; - -/* 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); -} - -static const AVTextFormatter json_formatter = { - .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, -}; - -/* 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); -} - -static AVTextFormatter xml_formatter = { - .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, -}; - -static void writer_register_all(void) +static void formatters_register_all(void) { static int initialized; @@ -1359,13 +441,13 @@ static void writer_register_all(void) return; initialized = 1; - writer_register(&default_formatter); - writer_register(&compact_formatter); - writer_register(&csv_formatter); - writer_register(&flat_formatter); - writer_register(&ini_formatter); - writer_register(&json_formatter); - writer_register(&xml_formatter); + formatter_register(&avtextformatter_default); + formatter_register(&avtextformatter_compact); + formatter_register(&avtextformatter_csv); + formatter_register(&avtextformatter_flat); + formatter_register(&avtextformatter_ini); + formatter_register(&avtextformatter_json); + formatter_register(&avtextformatter_xml); } #define print_fmt(k, f, ...) do { \ diff --git a/libavutil/Makefile b/libavutil/Makefile index 78c2c0d707..ce72aec1b7 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -189,6 +189,12 @@ OBJS = adler32.o \ xga_font_data.o \ xtea.o \ tea.o \ + textformat/tf_compact.o \ + textformat/tf_default.o \ + textformat/tf_flat.o \ + textformat/tf_ini.o \ + textformat/tf_json.o \ + textformat/tf_xml.o \ tx.o \ tx_float.o \ tx_double.o \ diff --git a/libavutil/avtextformat.h b/libavutil/avtextformat.h index c7cdfe4144..4f428d74c6 100644 --- a/libavutil/avtextformat.h +++ b/libavutil/avtextformat.h @@ -162,4 +162,12 @@ void avtext_print_data_hash(AVTextFormatContext *wctx, const char *name, const u void avtext_print_integers(AVTextFormatContext *wctx, const char *name, uint8_t *data, int size, const char *format, int columns, int bytes, int offset_add); +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 /* AVUTIL_AVTEXTFORMAT_H */ diff --git a/libavutil/textformat/tf_compact.c b/libavutil/textformat/tf_compact.c new file mode 100644 index 0000000000..fa8d3f2223 --- /dev/null +++ b/libavutil/textformat/tf_compact.c @@ -0,0 +1,285 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, fmt_, __VA_ARGS__) + + +#define DEFINE_FORMATTER_CLASS(name) \ +static const char *name##_get_name(void *ctx) \ +{ \ + return #name ; \ +} \ +static const AVClass name##_class = { \ + .class_name = #name, \ + .item_name = name##_get_name, \ + .option = name##_options \ +} + + +/* Compact output */ + +/** + * Apply C-language-like string escaping. + */ +static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + const char *p; + + for (p = src; *p; p++) { + switch (*p) { + case '\b': av_bprintf(dst, "%s", "\\b"); break; + case '\f': av_bprintf(dst, "%s", "\\f"); break; + case '\n': av_bprintf(dst, "%s", "\\n"); break; + case '\r': av_bprintf(dst, "%s", "\\r"); break; + case '\\': av_bprintf(dst, "%s", "\\\\"); break; + default: + if (*p == sep) + av_bprint_chars(dst, '\\', 1); + av_bprint_chars(dst, *p, 1); + } + } + return dst->str; +} + +/** + * Quote fields containing special characters, check RFC4180. + */ +static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; + int needs_quoting = !!src[strcspn(src, meta_chars)]; + + if (needs_quoting) + av_bprint_chars(dst, '"', 1); + + for (; *src; src++) { + if (*src == '"') + av_bprint_chars(dst, '"', 1); + av_bprint_chars(dst, *src, 1); + } + if (needs_quoting) + av_bprint_chars(dst, '"', 1); + return dst->str; +} + +static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) +{ + return src; +} + +typedef struct CompactContext { + const AVClass *class; + char *item_sep_str; + char item_sep; + int nokey; + int print_section; + char *escape_mode_str; + const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); + int nested_section[SECTION_MAX_NB_LEVELS]; + int has_nested_elems[SECTION_MAX_NB_LEVELS]; + int terminate_line[SECTION_MAX_NB_LEVELS]; +} CompactContext; + +#undef OFFSET +#define OFFSET(x) offsetof(CompactContext, x) + +static const AVOption compact_options[]= { + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_FORMATTER_CLASS(compact); + +static av_cold int compact_init(AVTextFormatContext *wctx) +{ + CompactContext *compact = wctx->priv; + + if (strlen(compact->item_sep_str) != 1) { + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", + compact->item_sep_str); + return AVERROR(EINVAL); + } + compact->item_sep = compact->item_sep_str[0]; + + if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; + else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; + else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; + else { + av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); + return AVERROR(EINVAL); + } + + return 0; +} + +static void compact_print_section_header(AVTextFormatContext *wctx, const void *data) +{ + CompactContext *compact = wctx->priv; + const struct AVTextFormatSection *section = wctx->section[wctx->level]; + const struct AVTextFormatSection *parent_section = wctx->level ? + wctx->section[wctx->level-1] : NULL; + compact->terminate_line[wctx->level] = 1; + compact->has_nested_elems[wctx->level] = 0; + + av_bprint_clear(&wctx->section_pbuf[wctx->level]); + if (parent_section && + (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE || + (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) && + !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) { + + /* define a prefix for elements not contained in an array or + in a wrapper, or for array elements with a type */ + const char *element_name = (char *)av_x_if_null(section->element_name, section->name); + AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level]; + + compact->nested_section[wctx->level] = 1; + compact->has_nested_elems[wctx->level-1] = 1; + + av_bprintf(section_pbuf, "%s%s", + wctx->section_pbuf[wctx->level-1].str, element_name); + + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { + // add /TYPE to prefix + av_bprint_chars(section_pbuf, '/', 1); + + // normalize section type, replace special characters and lower case + for (const char *p = section->get_type(data); *p; p++) { + char c = + (*p >= '0' && *p <= '9') || + (*p >= 'a' && *p <= 'z') || + (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_'; + av_bprint_chars(section_pbuf, c, 1); + } + } + av_bprint_chars(section_pbuf, ':', 1); + + wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; + } else { + if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) && + wctx->level && wctx->nb_item[wctx->level-1]) + writer_w8(wctx, compact->item_sep); + if (compact->print_section && + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) + writer_printf(wctx, "%s%c", section->name, compact->item_sep); + } +} + +static void compact_print_section_footer(AVTextFormatContext *wctx) +{ + CompactContext *compact = wctx->priv; + + if (!compact->nested_section[wctx->level] && + compact->terminate_line[wctx->level] && + !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) + writer_w8(wctx, '\n'); +} + +static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value) +{ + CompactContext *compact = wctx->priv; + AVBPrint buf; + + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); + if (!compact->nokey) + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); + writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx)); + av_bprint_finalize(&buf, NULL); +} + +static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) +{ + CompactContext *compact = wctx->priv; + + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); + if (!compact->nokey) + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); + writer_printf(wctx, "%"PRId64, value); +} + +const AVTextFormatter avtextformatter_compact = { + .name = "compact", + .priv_size = sizeof(CompactContext), + .init = compact_init, + .print_section_header = compact_print_section_header, + .print_section_footer = compact_print_section_footer, + .print_integer = compact_print_int, + .print_string = compact_print_str, + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, + .priv_class = &compact_class, +}; + +/* CSV output */ + +#undef OFFSET +#define OFFSET(x) offsetof(CompactContext, x) + +static const AVOption csv_options[] = { + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, + {NULL}, +}; + +DEFINE_FORMATTER_CLASS(csv); + +const AVTextFormatter avtextformatter_csv = { + .name = "csv", + .priv_size = sizeof(CompactContext), + .init = compact_init, + .print_section_header = compact_print_section_header, + .print_section_footer = compact_print_section_footer, + .print_integer = compact_print_int, + .print_string = compact_print_str, + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, + .priv_class = &csv_class, +}; diff --git a/libavutil/textformat/tf_default.c b/libavutil/textformat/tf_default.c new file mode 100644 index 0000000000..884d7160ff --- /dev/null +++ b/libavutil/textformat/tf_default.c @@ -0,0 +1,150 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, 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/libavutil/textformat/tf_flat.c b/libavutil/textformat/tf_flat.c new file mode 100644 index 0000000000..767762dd0e --- /dev/null +++ b/libavutil/textformat/tf_flat.c @@ -0,0 +1,177 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, 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/libavutil/textformat/tf_ini.c b/libavutil/textformat/tf_ini.c new file mode 100644 index 0000000000..3d0c4ea564 --- /dev/null +++ b/libavutil/textformat/tf_ini.c @@ -0,0 +1,165 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, 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/libavutil/textformat/tf_json.c b/libavutil/textformat/tf_json.c new file mode 100644 index 0000000000..c4dbf5a3d8 --- /dev/null +++ b/libavutil/textformat/tf_json.c @@ -0,0 +1,220 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, 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/libavutil/textformat/tf_xml.c b/libavutil/textformat/tf_xml.c new file mode 100644 index 0000000000..760d9fbf9d --- /dev/null +++ b/libavutil/textformat/tf_xml.c @@ -0,0 +1,224 @@ +/* + * 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 "config.h" +#include "../mem.h" +#include "../avassert.h" +#include "../avtextformat.h" +#include "../bprint.h" +#include "../error.h" +#include "../hash.h" +#include "../intreadwrite.h" +#include "../macros.h" +#include "../opt.h" + +#define writer_w8(wctx_, b_) (wctx_)->writer_w8(wctx_, b_) +#define writer_put_str(wctx_, str_) (wctx_)->writer_put_str(wctx_, str_) +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer_printf(wctx_, 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, +}; + -- ffmpeg-codebot _______________________________________________ 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".