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