Unified · Split
| @@ -0,0 +1,672 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "libavutil/mem.h" | |
| 28 | +#include "libavutil/avassert.h" | |
| 29 | +#include "libavutil/bprint.h" | |
| 30 | +#include "libavutil/error.h" | |
| 31 | +#include "libavutil/hash.h" | |
| 32 | +#include "libavutil/intreadwrite.h" | |
| 33 | +#include "libavutil/macros.h" | |
| 34 | +#include "libavutil/opt.h" | |
| 35 | +#include "avtextformat.h" | |
| 36 | + | |
| 37 | +#define SECTION_ID_NONE -1 | |
| 38 | + | |
| 39 | +#define SHOW_OPTIONAL_FIELDS_AUTO -1 | |
| 40 | +#define SHOW_OPTIONAL_FIELDS_NEVER 0 | |
| 41 | +#define SHOW_OPTIONAL_FIELDS_ALWAYS 1 | |
| 42 | + | |
| 43 | +static const struct { | |
| 44 | + double bin_val; | |
| 45 | + double dec_val; | |
| 46 | + const char *bin_str; | |
| 47 | + const char *dec_str; | |
| 48 | +} si_prefixes[] = { | |
| 49 | + { 1.0, 1.0, "", "" }, | |
| 50 | + { 1.024e3, 1e3, "Ki", "K" }, | |
| 51 | + { 1.048576e6, 1e6, "Mi", "M" }, | |
| 52 | + { 1.073741824e9, 1e9, "Gi", "G" }, | |
| 53 | + { 1.099511627776e12, 1e12, "Ti", "T" }, | |
| 54 | + { 1.125899906842624e15, 1e15, "Pi", "P" }, | |
| 55 | +}; | |
| 56 | + | |
| 57 | +static const char *textcontext_get_formatter_name(void *p) | |
| 58 | +{ | |
| 59 | + AVTextFormatContext *tctx = p; | |
| 60 | + return tctx->formatter->name; | |
| 61 | +} | |
| 62 | + | |
| 63 | +#define OFFSET(x) offsetof(AVTextFormatContext, x) | |
| 64 | + | |
| 65 | +static const AVOption textcontext_options[] = { | |
| 66 | + { "string_validation", "set string validation mode", | |
| 67 | + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" }, | |
| 68 | + { "sv", "set string validation mode", | |
| 69 | + OFFSET(string_validation), AV_OPT_TYPE_INT, {.i64=AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, 0, AV_TEXTFORMAT_STRING_VALIDATION_NB-1, .unit = "sv" }, | |
| 70 | + { "ignore", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_IGNORE}, .unit = "sv" }, | |
| 71 | + { "replace", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_REPLACE}, .unit = "sv" }, | |
| 72 | + { "fail", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = AV_TEXTFORMAT_STRING_VALIDATION_FAIL}, .unit = "sv" }, | |
| 73 | + { "string_validation_replacement", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str=""}}, | |
| 74 | + { "svr", "set string validation replacement string", OFFSET(string_validation_replacement), AV_OPT_TYPE_STRING, {.str="\xEF\xBF\xBD"}}, | |
| 75 | + { NULL } | |
| 76 | +}; | |
| 77 | + | |
| 78 | +static void *textcontext_child_next(void *obj, void *prev) | |
| 79 | +{ | |
| 80 | + AVTextFormatContext *ctx = obj; | |
| 81 | + if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv) | |
| 82 | + return ctx->priv; | |
| 83 | + return NULL; | |
| 84 | +} | |
| 85 | + | |
| 86 | +static const AVClass textcontext_class = { | |
| 87 | + .class_name = "AVTextContext", | |
| 88 | + .item_name = textcontext_get_formatter_name, | |
| 89 | + .option = textcontext_options, | |
| 90 | + .version = LIBAVUTIL_VERSION_INT, | |
| 91 | + .child_next = textcontext_child_next, | |
| 92 | +}; | |
| 93 | + | |
| 94 | +static void bprint_bytes(AVBPrint *bp, const uint8_t *ubuf, size_t ubuf_size) | |
| 95 | +{ | |
| 96 | + int i; | |
| 97 | + av_bprintf(bp, "0X"); | |
| 98 | + for (i = 0; i < ubuf_size; i++) | |
| 99 | + av_bprintf(bp, "%02X", ubuf[i]); | |
| 100 | +} | |
| 101 | + | |
| 102 | +int avtext_context_close(AVTextFormatContext **ptctx) | |
| 103 | +{ | |
| 104 | + AVTextFormatContext *tctx = *ptctx; | |
| 105 | + int i; | |
| 106 | + int ret = 0; | |
| 107 | + | |
| 108 | + if (!tctx) | |
| 109 | + return EINVAL; | |
| 110 | + | |
| 111 | + av_hash_freep(&tctx->hash); | |
| 112 | + | |
| 113 | + av_hash_freep(&tctx->hash); | |
| 114 | + | |
| 115 | + if (tctx->formatter->uninit) | |
| 116 | + tctx->formatter->uninit(tctx); | |
| 117 | + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) | |
| 118 | + av_bprint_finalize(&tctx->section_pbuf[i], NULL); | |
| 119 | + if (tctx->formatter->priv_class) | |
| 120 | + av_opt_free(tctx->priv); | |
| 121 | + av_freep(&tctx->priv); | |
| 122 | + av_opt_free(tctx); | |
| 123 | + av_freep(ptctx); | |
| 124 | + return ret; | |
| 125 | +} | |
| 126 | + | |
| 127 | + | |
| 128 | +int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args, | |
| 129 | + const struct AVTextFormatSection *sections, int nb_sections, | |
| 130 | + int show_value_unit, | |
| 131 | + int use_value_prefix, | |
| 132 | + int use_byte_value_binary_prefix, | |
| 133 | + int use_value_sexagesimal_format, | |
| 134 | + int show_optional_fields, | |
| 135 | + char *show_data_hash) | |
| 136 | +{ | |
| 137 | + AVTextFormatContext *tctx; | |
| 138 | + int i, ret = 0; | |
| 139 | + | |
| 140 | + if (!(tctx = av_mallocz(sizeof(AVTextFormatContext)))) { | |
| 141 | + ret = AVERROR(ENOMEM); | |
| 142 | + goto fail; | |
| 143 | + } | |
| 144 | + | |
| 145 | + if (!(tctx->priv = av_mallocz(formatter->priv_size))) { | |
| 146 | + ret = AVERROR(ENOMEM); | |
| 147 | + goto fail; | |
| 148 | + } | |
| 149 | + | |
| 150 | + tctx->show_value_unit = show_value_unit; | |
| 151 | + tctx->use_value_prefix = use_value_prefix; | |
| 152 | + tctx->use_byte_value_binary_prefix = use_byte_value_binary_prefix; | |
| 153 | + tctx->use_value_sexagesimal_format = use_value_sexagesimal_format; | |
| 154 | + tctx->show_optional_fields = show_optional_fields; | |
| 155 | + | |
| 156 | + if (nb_sections > SECTION_MAX_NB_SECTIONS) { | |
| 157 | + 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); | |
| 158 | + ret = AVERROR(EINVAL); | |
| 159 | + goto fail; | |
| 160 | + } | |
| 161 | + | |
| 162 | + tctx->class = &textcontext_class; | |
| 163 | + tctx->formatter = formatter; | |
| 164 | + tctx->level = -1; | |
| 165 | + tctx->sections = sections; | |
| 166 | + tctx->nb_sections = nb_sections; | |
| 167 | + tctx->writer = writer_context; | |
| 168 | + | |
| 169 | + av_opt_set_defaults(tctx); | |
| 170 | + | |
| 171 | + if (formatter->priv_class) { | |
| 172 | + void *priv_ctx = tctx->priv; | |
| 173 | + *(const AVClass **)priv_ctx = formatter->priv_class; | |
| 174 | + av_opt_set_defaults(priv_ctx); | |
| 175 | + } | |
| 176 | + | |
| 177 | + /* convert options to dictionary */ | |
| 178 | + if (args) { | |
| 179 | + AVDictionary *opts = NULL; | |
| 180 | + const AVDictionaryEntry *opt = NULL; | |
| 181 | + | |
| 182 | + if ((ret = av_dict_parse_string(&opts, args, "=", ":", 0)) < 0) { | |
| 183 | + av_log(tctx, AV_LOG_ERROR, "Failed to parse option string '%s' provided to textformat context\n", args); | |
| 184 | + av_dict_free(&opts); | |
| 185 | + goto fail; | |
| 186 | + } | |
| 187 | + | |
| 188 | + while ((opt = av_dict_iterate(opts, opt))) { | |
| 189 | + if ((ret = av_opt_set(tctx, opt->key, opt->value, AV_OPT_SEARCH_CHILDREN)) < 0) { | |
| 190 | + av_log(tctx, AV_LOG_ERROR, "Failed to set option '%s' with value '%s' provided to textformat context\n", | |
| 191 | + opt->key, opt->value); | |
| 192 | + av_dict_free(&opts); | |
| 193 | + goto fail; | |
| 194 | + } | |
| 195 | + } | |
| 196 | + | |
| 197 | + av_dict_free(&opts); | |
| 198 | + } | |
| 199 | + | |
| 200 | + if (show_data_hash) { | |
| 201 | + if ((ret = av_hash_alloc(&tctx->hash, show_data_hash)) < 0) { | |
| 202 | + if (ret == AVERROR(EINVAL)) { | |
| 203 | + const char *n; | |
| 204 | + av_log(NULL, AV_LOG_ERROR, "Unknown hash algorithm '%s'\nKnown algorithms:", show_data_hash); | |
| 205 | + for (i = 0; (n = av_hash_names(i)); i++) | |
| 206 | + av_log(NULL, AV_LOG_ERROR, " %s", n); | |
| 207 | + av_log(NULL, AV_LOG_ERROR, "\n"); | |
| 208 | + } | |
| 209 | + return ret; | |
| 210 | + } | |
| 211 | + } | |
| 212 | + | |
| 213 | + /* validate replace string */ | |
| 214 | + { | |
| 215 | + const uint8_t *p = tctx->string_validation_replacement; | |
| 216 | + const uint8_t *endp = p + strlen(p); | |
| 217 | + while (*p) { | |
| 218 | + const uint8_t *p0 = p; | |
| 219 | + int32_t code; | |
| 220 | + ret = av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags); | |
| 221 | + if (ret < 0) { | |
| 222 | + AVBPrint bp; | |
| 223 | + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
| 224 | + bprint_bytes(&bp, p0, p-p0), | |
| 225 | + av_log(tctx, AV_LOG_ERROR, | |
| 226 | + "Invalid UTF8 sequence %s found in string validation replace '%s'\n", | |
| 227 | + bp.str, tctx->string_validation_replacement); | |
| 228 | + return ret; | |
| 229 | + } | |
| 230 | + } | |
| 231 | + } | |
| 232 | + | |
| 233 | + for (i = 0; i < SECTION_MAX_NB_LEVELS; i++) | |
| 234 | + av_bprint_init(&tctx->section_pbuf[i], 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 235 | + | |
| 236 | + if (tctx->formatter->init) | |
| 237 | + ret = tctx->formatter->init(tctx); | |
| 238 | + if (ret < 0) | |
| 239 | + goto fail; | |
| 240 | + | |
| 241 | + *ptctx = tctx; | |
| 242 | + | |
| 243 | + return 0; | |
| 244 | + | |
| 245 | +fail: | |
| 246 | + avtext_context_close(&tctx); | |
| 247 | + return ret; | |
| 248 | +} | |
| 249 | + | |
| 250 | +/* Temporary definitions during refactoring */ | |
| 251 | +static const char unit_second_str[] = "s" ; | |
| 252 | +static const char unit_hertz_str[] = "Hz" ; | |
| 253 | +static const char unit_byte_str[] = "byte" ; | |
| 254 | +static const char unit_bit_per_second_str[] = "bit/s"; | |
| 255 | + | |
| 256 | + | |
| 257 | +void avtext_print_section_header(AVTextFormatContext *tctx, | |
| 258 | + const void *data, | |
| 259 | + int section_id) | |
| 260 | +{ | |
| 261 | + tctx->level++; | |
| 262 | + av_assert0(tctx->level < SECTION_MAX_NB_LEVELS); | |
| 263 | + | |
| 264 | + tctx->nb_item[tctx->level] = 0; | |
| 265 | + memset(tctx->nb_item_type[tctx->level], 0, sizeof(tctx->nb_item_type[tctx->level])); | |
| 266 | + tctx->section[tctx->level] = &tctx->sections[section_id]; | |
| 267 | + | |
| 268 | + if (tctx->formatter->print_section_header) | |
| 269 | + tctx->formatter->print_section_header(tctx, data); | |
| 270 | +} | |
| 271 | + | |
| 272 | +void avtext_print_section_footer(AVTextFormatContext *tctx) | |
| 273 | +{ | |
| 274 | + int section_id = tctx->section[tctx->level]->id; | |
| 275 | + int parent_section_id = tctx->level ? | |
| 276 | + tctx->section[tctx->level-1]->id : SECTION_ID_NONE; | |
| 277 | + | |
| 278 | + if (parent_section_id != SECTION_ID_NONE) { | |
| 279 | + tctx->nb_item[tctx->level - 1]++; | |
| 280 | + tctx->nb_item_type[tctx->level - 1][section_id]++; | |
| 281 | + } | |
| 282 | + | |
| 283 | + if (tctx->formatter->print_section_footer) | |
| 284 | + tctx->formatter->print_section_footer(tctx); | |
| 285 | + tctx->level--; | |
| 286 | +} | |
| 287 | + | |
| 288 | +void avtext_print_integer(AVTextFormatContext *tctx, | |
| 289 | + const char *key, int64_t val) | |
| 290 | +{ | |
| 291 | + const struct AVTextFormatSection *section = tctx->section[tctx->level]; | |
| 292 | + | |
| 293 | + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { | |
| 294 | + tctx->formatter->print_integer(tctx, key, val); | |
| 295 | + tctx->nb_item[tctx->level]++; | |
| 296 | + } | |
| 297 | +} | |
| 298 | + | |
| 299 | +static inline int validate_string(AVTextFormatContext *tctx, char **dstp, const char *src) | |
| 300 | +{ | |
| 301 | + const uint8_t *p, *endp; | |
| 302 | + AVBPrint dstbuf; | |
| 303 | + int invalid_chars_nb = 0, ret = 0; | |
| 304 | + | |
| 305 | + av_bprint_init(&dstbuf, 0, AV_BPRINT_SIZE_UNLIMITED); | |
| 306 | + | |
| 307 | + endp = src + strlen(src); | |
| 308 | + for (p = src; *p;) { | |
| 309 | + uint32_t code; | |
| 310 | + int invalid = 0; | |
| 311 | + const uint8_t *p0 = p; | |
| 312 | + | |
| 313 | + if (av_utf8_decode(&code, &p, endp, tctx->string_validation_utf8_flags) < 0) { | |
| 314 | + AVBPrint bp; | |
| 315 | + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
| 316 | + bprint_bytes(&bp, p0, p-p0); | |
| 317 | + av_log(tctx, AV_LOG_DEBUG, | |
| 318 | + "Invalid UTF-8 sequence %s found in string '%s'\n", bp.str, src); | |
| 319 | + invalid = 1; | |
| 320 | + } | |
| 321 | + | |
| 322 | + if (invalid) { | |
| 323 | + invalid_chars_nb++; | |
| 324 | + | |
| 325 | + switch (tctx->string_validation) { | |
| 326 | + case AV_TEXTFORMAT_STRING_VALIDATION_FAIL: | |
| 327 | + av_log(tctx, AV_LOG_ERROR, | |
| 328 | + "Invalid UTF-8 sequence found in string '%s'\n", src); | |
| 329 | + ret = AVERROR_INVALIDDATA; | |
| 330 | + goto end; | |
| 331 | + break; | |
| 332 | + | |
| 333 | + case AV_TEXTFORMAT_STRING_VALIDATION_REPLACE: | |
| 334 | + av_bprintf(&dstbuf, "%s", tctx->string_validation_replacement); | |
| 335 | + break; | |
| 336 | + } | |
| 337 | + } | |
| 338 | + | |
| 339 | + if (!invalid || tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_IGNORE) | |
| 340 | + av_bprint_append_data(&dstbuf, p0, p-p0); | |
| 341 | + } | |
| 342 | + | |
| 343 | + if (invalid_chars_nb && tctx->string_validation == AV_TEXTFORMAT_STRING_VALIDATION_REPLACE) { | |
| 344 | + av_log(tctx, AV_LOG_WARNING, | |
| 345 | + "%d invalid UTF-8 sequence(s) found in string '%s', replaced with '%s'\n", | |
| 346 | + invalid_chars_nb, src, tctx->string_validation_replacement); | |
| 347 | + } | |
| 348 | + | |
| 349 | +end: | |
| 350 | + av_bprint_finalize(&dstbuf, dstp); | |
| 351 | + return ret; | |
| 352 | +} | |
| 353 | + | |
| 354 | +struct unit_value { | |
| 355 | + union { double d; int64_t i; } val; | |
| 356 | + const char *unit; | |
| 357 | +}; | |
| 358 | + | |
| 359 | +static char *value_string(AVTextFormatContext *tctx, char *buf, int buf_size, struct unit_value uv) | |
| 360 | +{ | |
| 361 | + double vald; | |
| 362 | + int64_t vali; | |
| 363 | + int show_float = 0; | |
| 364 | + | |
| 365 | + if (uv.unit == unit_second_str) { | |
| 366 | + vald = uv.val.d; | |
| 367 | + show_float = 1; | |
| 368 | + } else { | |
| 369 | + vald = vali = uv.val.i; | |
| 370 | + } | |
| 371 | + | |
| 372 | + if (uv.unit == unit_second_str && tctx->use_value_sexagesimal_format) { | |
| 373 | + double secs; | |
| 374 | + int hours, mins; | |
| 375 | + secs = vald; | |
| 376 | + mins = (int)secs / 60; | |
| 377 | + secs = secs - mins * 60; | |
| 378 | + hours = mins / 60; | |
| 379 | + mins %= 60; | |
| 380 | + snprintf(buf, buf_size, "%d:%02d:%09.6f", hours, mins, secs); | |
| 381 | + } else { | |
| 382 | + const char *prefix_string = ""; | |
| 383 | + | |
| 384 | + if (tctx->use_value_prefix && vald > 1) { | |
| 385 | + int64_t index; | |
| 386 | + | |
| 387 | + if (uv.unit == unit_byte_str && tctx->use_byte_value_binary_prefix) { | |
| 388 | + index = (int64_t) (log2(vald)) / 10; | |
| 389 | + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); | |
| 390 | + vald /= si_prefixes[index].bin_val; | |
| 391 | + prefix_string = si_prefixes[index].bin_str; | |
| 392 | + } else { | |
| 393 | + index = (int64_t) (log10(vald)) / 3; | |
| 394 | + index = av_clip(index, 0, FF_ARRAY_ELEMS(si_prefixes) - 1); | |
| 395 | + vald /= si_prefixes[index].dec_val; | |
| 396 | + prefix_string = si_prefixes[index].dec_str; | |
| 397 | + } | |
| 398 | + vali = vald; | |
| 399 | + } | |
| 400 | + | |
| 401 | + if (show_float || (tctx->use_value_prefix && vald != (int64_t)vald)) | |
| 402 | + snprintf(buf, buf_size, "%f", vald); | |
| 403 | + else | |
| 404 | + snprintf(buf, buf_size, "%"PRId64, vali); | |
| 405 | + av_strlcatf(buf, buf_size, "%s%s%s", *prefix_string || tctx->show_value_unit ? " " : "", | |
| 406 | + prefix_string, tctx->show_value_unit ? uv.unit : ""); | |
| 407 | + } | |
| 408 | + | |
| 409 | + return buf; | |
| 410 | +} | |
| 411 | + | |
| 412 | + | |
| 413 | +void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit) | |
| 414 | +{ | |
| 415 | + char val_str[128]; | |
| 416 | + struct unit_value uv; | |
| 417 | + uv.val.i = value; | |
| 418 | + uv.unit = unit; | |
| 419 | + avtext_print_string(tctx, key, value_string(tctx, val_str, sizeof(val_str), uv), 0); | |
| 420 | +} | |
| 421 | + | |
| 422 | + | |
| 423 | +int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags) | |
| 424 | +{ | |
| 425 | + const struct AVTextFormatSection *section = tctx->section[tctx->level]; | |
| 426 | + int ret = 0; | |
| 427 | + | |
| 428 | + if (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_NEVER || | |
| 429 | + (tctx->show_optional_fields == SHOW_OPTIONAL_FIELDS_AUTO | |
| 430 | + && (flags & AV_TEXTFORMAT_PRINT_STRING_OPTIONAL) | |
| 431 | + && !(tctx->formatter->flags & AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS))) | |
| 432 | + return 0; | |
| 433 | + | |
| 434 | + if (section->show_all_entries || av_dict_get(section->entries_to_show, key, NULL, 0)) { | |
| 435 | + if (flags & AV_TEXTFORMAT_PRINT_STRING_VALIDATE) { | |
| 436 | + char *key1 = NULL, *val1 = NULL; | |
| 437 | + ret = validate_string(tctx, &key1, key); | |
| 438 | + if (ret < 0) goto end; | |
| 439 | + ret = validate_string(tctx, &val1, val); | |
| 440 | + if (ret < 0) goto end; | |
| 441 | + tctx->formatter->print_string(tctx, key1, val1); | |
| 442 | + end: | |
| 443 | + if (ret < 0) { | |
| 444 | + av_log(tctx, AV_LOG_ERROR, | |
| 445 | + "Invalid key=value string combination %s=%s in section %s\n", | |
| 446 | + key, val, section->unique_name); | |
| 447 | + } | |
| 448 | + av_free(key1); | |
| 449 | + av_free(val1); | |
| 450 | + } else { | |
| 451 | + tctx->formatter->print_string(tctx, key, val); | |
| 452 | + } | |
| 453 | + | |
| 454 | + tctx->nb_item[tctx->level]++; | |
| 455 | + } | |
| 456 | + | |
| 457 | + return ret; | |
| 458 | +} | |
| 459 | + | |
| 460 | +void avtext_print_rational(AVTextFormatContext *tctx, | |
| 461 | + const char *key, AVRational q, char sep) | |
| 462 | +{ | |
| 463 | + AVBPrint buf; | |
| 464 | + av_bprint_init(&buf, 0, AV_BPRINT_SIZE_AUTOMATIC); | |
| 465 | + av_bprintf(&buf, "%d%c%d", q.num, sep, q.den); | |
| 466 | + avtext_print_string(tctx, key, buf.str, 0); | |
| 467 | +} | |
| 468 | + | |
| 469 | +void avtext_print_time(AVTextFormatContext *tctx, const char *key, | |
| 470 | + int64_t ts, const AVRational *time_base, int is_duration) | |
| 471 | +{ | |
| 472 | + char buf[128]; | |
| 473 | + | |
| 474 | + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { | |
| 475 | + avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL); | |
| 476 | + } else { | |
| 477 | + double d = ts * av_q2d(*time_base); | |
| 478 | + struct unit_value uv; | |
| 479 | + uv.val.d = d; | |
| 480 | + uv.unit = unit_second_str; | |
| 481 | + value_string(tctx, buf, sizeof(buf), uv); | |
| 482 | + avtext_print_string(tctx, key, buf, 0); | |
| 483 | + } | |
| 484 | +} | |
| 485 | + | |
| 486 | +void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration) | |
| 487 | +{ | |
| 488 | + if ((!is_duration && ts == AV_NOPTS_VALUE) || (is_duration && ts == 0)) { | |
| 489 | + avtext_print_string(tctx, key, "N/A", AV_TEXTFORMAT_PRINT_STRING_OPTIONAL); | |
| 490 | + } else { | |
| 491 | + avtext_print_integer(tctx, key, ts); | |
| 492 | + } | |
| 493 | +} | |
| 494 | + | |
| 495 | +void avtext_print_data(AVTextFormatContext *tctx, const char *name, | |
| 496 | + const uint8_t *data, int size) | |
| 497 | +{ | |
| 498 | + AVBPrint bp; | |
| 499 | + int offset = 0, l, i; | |
| 500 | + | |
| 501 | + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); | |
| 502 | + av_bprintf(&bp, "\n"); | |
| 503 | + while (size) { | |
| 504 | + av_bprintf(&bp, "%08x: ", offset); | |
| 505 | + l = FFMIN(size, 16); | |
| 506 | + for (i = 0; i < l; i++) { | |
| 507 | + av_bprintf(&bp, "%02x", data[i]); | |
| 508 | + if (i & 1) | |
| 509 | + av_bprintf(&bp, " "); | |
| 510 | + } | |
| 511 | + av_bprint_chars(&bp, ' ', 41 - 2 * i - i / 2); | |
| 512 | + for (i = 0; i < l; i++) | |
| 513 | + av_bprint_chars(&bp, data[i] - 32U < 95 ? data[i] : '.', 1); | |
| 514 | + av_bprintf(&bp, "\n"); | |
| 515 | + offset += l; | |
| 516 | + data += l; | |
| 517 | + size -= l; | |
| 518 | + } | |
| 519 | + avtext_print_string(tctx, name, bp.str, 0); | |
| 520 | + av_bprint_finalize(&bp, NULL); | |
| 521 | +} | |
| 522 | + | |
| 523 | +void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, | |
| 524 | + const uint8_t *data, int size) | |
| 525 | +{ | |
| 526 | + char *p, buf[AV_HASH_MAX_SIZE * 2 + 64] = { 0 }; | |
| 527 | + | |
| 528 | + if (!tctx->hash) | |
| 529 | + return; | |
| 530 | + av_hash_init(tctx->hash); | |
| 531 | + av_hash_update(tctx->hash, data, size); | |
| 532 | + snprintf(buf, sizeof(buf), "%s:", av_hash_get_name(tctx->hash)); | |
| 533 | + p = buf + strlen(buf); | |
| 534 | + av_hash_final_hex(tctx->hash, p, buf + sizeof(buf) - p); | |
| 535 | + avtext_print_string(tctx, name, buf, 0); | |
| 536 | +} | |
| 537 | + | |
| 538 | +void avtext_print_integers(AVTextFormatContext *tctx, const char *name, | |
| 539 | + uint8_t *data, int size, const char *format, | |
| 540 | + int columns, int bytes, int offset_add) | |
| 541 | +{ | |
| 542 | + AVBPrint bp; | |
| 543 | + int offset = 0, l, i; | |
| 544 | + | |
| 545 | + av_bprint_init(&bp, 0, AV_BPRINT_SIZE_UNLIMITED); | |
| 546 | + av_bprintf(&bp, "\n"); | |
| 547 | + while (size) { | |
| 548 | + av_bprintf(&bp, "%08x: ", offset); | |
| 549 | + l = FFMIN(size, columns); | |
| 550 | + for (i = 0; i < l; i++) { | |
| 551 | + if (bytes == 1) av_bprintf(&bp, format, *data); | |
| 552 | + else if (bytes == 2) av_bprintf(&bp, format, AV_RN16(data)); | |
| 553 | + else if (bytes == 4) av_bprintf(&bp, format, AV_RN32(data)); | |
| 554 | + data += bytes; | |
| 555 | + size --; | |
| 556 | + } | |
| 557 | + av_bprintf(&bp, "\n"); | |
| 558 | + offset += offset_add; | |
| 559 | + } | |
| 560 | + avtext_print_string(tctx, name, bp.str, 0); | |
| 561 | + av_bprint_finalize(&bp, NULL); | |
| 562 | +} | |
| 563 | + | |
| 564 | +static const char *writercontext_get_writer_name(void *p) | |
| 565 | +{ | |
| 566 | + AVTextWriterContext *wctx = p; | |
| 567 | + return wctx->writer->name; | |
| 568 | +} | |
| 569 | + | |
| 570 | +static void *writercontext_child_next(void *obj, void *prev) | |
| 571 | +{ | |
| 572 | + AVTextFormatContext *ctx = obj; | |
| 573 | + if (!prev && ctx->formatter && ctx->formatter->priv_class && ctx->priv) | |
| 574 | + return ctx->priv; | |
| 575 | + return NULL; | |
| 576 | +} | |
| 577 | + | |
| 578 | +static const AVClass textwriter_class = { | |
| 579 | + .class_name = "AVTextWriterContext", | |
| 580 | + .item_name = writercontext_get_writer_name, | |
| 581 | + .version = LIBAVUTIL_VERSION_INT, | |
| 582 | + .child_next = writercontext_child_next, | |
| 583 | +}; | |
| 584 | + | |
| 585 | + | |
| 586 | +int avtextwriter_context_close(AVTextWriterContext **pwctx) | |
| 587 | +{ | |
| 588 | + AVTextWriterContext *wctx = *pwctx; | |
| 589 | + int ret = 0; | |
| 590 | + | |
| 591 | + if (!wctx) | |
| 592 | + return EINVAL; | |
| 593 | + | |
| 594 | + if (wctx->writer->uninit) | |
| 595 | + wctx->writer->uninit(wctx); | |
| 596 | + if (wctx->writer->priv_class) | |
| 597 | + av_opt_free(wctx->priv); | |
| 598 | + av_freep(&wctx->priv); | |
| 599 | + av_freep(pwctx); | |
| 600 | + return ret; | |
| 601 | +} | |
| 602 | + | |
| 603 | + | |
| 604 | +int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer) | |
| 605 | +{ | |
| 606 | + AVTextWriterContext *wctx; | |
| 607 | + int ret = 0; | |
| 608 | + | |
| 609 | + if (!(wctx = av_mallocz(sizeof(AVTextWriterContext)))) { | |
| 610 | + ret = AVERROR(ENOMEM); | |
| 611 | + goto fail; | |
| 612 | + } | |
| 613 | + | |
| 614 | + if (!(wctx->priv = av_mallocz(writer->priv_size))) { | |
| 615 | + ret = AVERROR(ENOMEM); | |
| 616 | + goto fail; | |
| 617 | + } | |
| 618 | + | |
| 619 | + if (writer->priv_class) { | |
| 620 | + void *priv_ctx = wctx->priv; | |
| 621 | + *(const AVClass **)priv_ctx = writer->priv_class; | |
| 622 | + av_opt_set_defaults(priv_ctx); | |
| 623 | + } | |
| 624 | + | |
| 625 | + wctx->class = &textwriter_class; | |
| 626 | + wctx->writer = writer; | |
| 627 | + | |
| 628 | + av_opt_set_defaults(wctx); | |
| 629 | + | |
| 630 | + | |
| 631 | + if (wctx->writer->init) | |
| 632 | + ret = wctx->writer->init(wctx); | |
| 633 | + if (ret < 0) | |
| 634 | + goto fail; | |
| 635 | + | |
| 636 | + *pwctx = wctx; | |
| 637 | + | |
| 638 | + return 0; | |
| 639 | + | |
| 640 | +fail: | |
| 641 | + avtextwriter_context_close(&wctx); | |
| 642 | + return ret; | |
| 643 | +} | |
| 644 | + | |
| 645 | +static const AVTextFormatter *registered_formatters[7+1]; | |
| 646 | +static void formatters_register_all(void) | |
| 647 | +{ | |
| 648 | + static int initialized; | |
| 649 | + | |
| 650 | + if (initialized) | |
| 651 | + return; | |
| 652 | + initialized = 1; | |
| 653 | + | |
| 654 | + registered_formatters[0] = &avtextformatter_default; | |
| 655 | + registered_formatters[1] = &avtextformatter_compact; | |
| 656 | + registered_formatters[2] = &avtextformatter_csv; | |
| 657 | + registered_formatters[3] = &avtextformatter_flat; | |
| 658 | + registered_formatters[4] = &avtextformatter_ini; | |
| 659 | + registered_formatters[5] = &avtextformatter_json; | |
| 660 | + registered_formatters[6] = &avtextformatter_xml; | |
| 661 | +} | |
| 662 | + | |
| 663 | +const AVTextFormatter *avtext_get_formatter_by_name(const char *name) | |
| 664 | +{ | |
| 665 | + formatters_register_all(); | |
| 666 | + | |
| 667 | + for (int i = 0; registered_formatters[i]; i++) | |
| 668 | + if (!strcmp(registered_formatters[i]->name, name)) | |
| 669 | + return registered_formatters[i]; | |
| 670 | + | |
| 671 | + return NULL; | |
| 672 | +} | |
| @@ -0,0 +1,171 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#ifndef FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H | |
| 22 | +#define FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H | |
| 23 | + | |
| 24 | +#include <stddef.h> | |
| 25 | +#include <stdint.h> | |
| 26 | +#include "libavutil/attributes.h" | |
| 27 | +#include "libavutil/dict.h" | |
| 28 | +#include "libavformat/avio.h" | |
| 29 | +#include "libavutil/bprint.h" | |
| 30 | +#include "libavutil/rational.h" | |
| 31 | +#include "libavutil/hash.h" | |
| 32 | +#include "avtextwriters.h" | |
| 33 | + | |
| 34 | +#define SECTION_MAX_NB_CHILDREN 11 | |
| 35 | + | |
| 36 | + | |
| 37 | +typedef struct AVTextFormatSection { | |
| 38 | + int id; ///< unique id identifying a section | |
| 39 | + const char *name; | |
| 40 | + | |
| 41 | +#define AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER 1 ///< the section only contains other sections, but has no data at its own level | |
| 42 | +#define AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY 2 ///< the section contains an array of elements of the same type | |
| 43 | +#define AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS 4 ///< the section may contain a variable number of fields with variable keys. | |
| 44 | + /// For these sections the element_name field is mandatory. | |
| 45 | +#define AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE 8 ///< the section contains a type to distinguish multiple nested elements | |
| 46 | +#define AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE 16 ///< the items in this array section should be numbered individually by type | |
| 47 | + | |
| 48 | + int flags; | |
| 49 | + const int children_ids[SECTION_MAX_NB_CHILDREN+1]; ///< list of children section IDS, terminated by -1 | |
| 50 | + const char *element_name; ///< name of the contained element, if provided | |
| 51 | + const char *unique_name; ///< unique section name, in case the name is ambiguous | |
| 52 | + AVDictionary *entries_to_show; | |
| 53 | + const char *(* get_type)(const void *data); ///< function returning a type if defined, must be defined when SECTION_FLAG_HAS_TYPE is defined | |
| 54 | + int show_all_entries; | |
| 55 | +} AVTextFormatSection; | |
| 56 | + | |
| 57 | +typedef struct AVTextFormatContext AVTextFormatContext; | |
| 58 | + | |
| 59 | +#define AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS 1 | |
| 60 | +#define AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT 2 | |
| 61 | + | |
| 62 | +typedef enum { | |
| 63 | + AV_TEXTFORMAT_STRING_VALIDATION_FAIL, | |
| 64 | + AV_TEXTFORMAT_STRING_VALIDATION_REPLACE, | |
| 65 | + AV_TEXTFORMAT_STRING_VALIDATION_IGNORE, | |
| 66 | + AV_TEXTFORMAT_STRING_VALIDATION_NB | |
| 67 | +} StringValidation; | |
| 68 | + | |
| 69 | +typedef struct AVTextFormatter { | |
| 70 | + const AVClass *priv_class; ///< private class of the formatter, if any | |
| 71 | + int priv_size; ///< private size for the formatter context | |
| 72 | + const char *name; | |
| 73 | + | |
| 74 | + int (*init) (AVTextFormatContext *tctx); | |
| 75 | + void (*uninit)(AVTextFormatContext *tctx); | |
| 76 | + | |
| 77 | + void (*print_section_header)(AVTextFormatContext *tctx, const void *data); | |
| 78 | + void (*print_section_footer)(AVTextFormatContext *tctx); | |
| 79 | + void (*print_integer) (AVTextFormatContext *tctx, const char *, int64_t); | |
| 80 | + void (*print_rational) (AVTextFormatContext *tctx, AVRational *q, char *sep); | |
| 81 | + void (*print_string) (AVTextFormatContext *tctx, const char *, const char *); | |
| 82 | + int flags; ///< a combination or AV_TEXTFORMAT__FLAG_* | |
| 83 | +} AVTextFormatter; | |
| 84 | + | |
| 85 | +#define SECTION_MAX_NB_LEVELS 12 | |
| 86 | +#define SECTION_MAX_NB_SECTIONS 100 | |
| 87 | + | |
| 88 | +struct AVTextFormatContext { | |
| 89 | + const AVClass *class; ///< class of the formatter | |
| 90 | + const AVTextFormatter *formatter; ///< the AVTextFormatter of which this is an instance | |
| 91 | + AVTextWriterContext *writer; ///< the AVTextWriterContext | |
| 92 | + | |
| 93 | + char *name; ///< name of this formatter instance | |
| 94 | + void *priv; ///< private data for use by the filter | |
| 95 | + | |
| 96 | + const struct AVTextFormatSection *sections; ///< array containing all sections | |
| 97 | + int nb_sections; ///< number of sections | |
| 98 | + | |
| 99 | + int level; ///< current level, starting from 0 | |
| 100 | + | |
| 101 | + /** number of the item printed in the given section, starting from 0 */ | |
| 102 | + unsigned int nb_item[SECTION_MAX_NB_LEVELS]; | |
| 103 | + unsigned int nb_item_type[SECTION_MAX_NB_LEVELS][SECTION_MAX_NB_SECTIONS]; | |
| 104 | + | |
| 105 | + /** section per each level */ | |
| 106 | + const struct AVTextFormatSection *section[SECTION_MAX_NB_LEVELS]; | |
| 107 | + AVBPrint section_pbuf[SECTION_MAX_NB_LEVELS]; ///< generic print buffer dedicated to each section, | |
| 108 | + /// used by various formatters | |
| 109 | + | |
| 110 | + int show_optional_fields; | |
| 111 | + int show_value_unit; | |
| 112 | + int use_value_prefix; | |
| 113 | + int use_byte_value_binary_prefix; | |
| 114 | + int use_value_sexagesimal_format; | |
| 115 | + | |
| 116 | + struct AVHashContext *hash; | |
| 117 | + | |
| 118 | + int string_validation; | |
| 119 | + char *string_validation_replacement; | |
| 120 | + unsigned int string_validation_utf8_flags; | |
| 121 | +}; | |
| 122 | + | |
| 123 | +#define AV_TEXTFORMAT_PRINT_STRING_OPTIONAL 1 | |
| 124 | +#define AV_TEXTFORMAT_PRINT_STRING_VALIDATE 2 | |
| 125 | + | |
| 126 | +int avtext_context_open(AVTextFormatContext **ptctx, const AVTextFormatter *formatter, AVTextWriterContext *writer_context, const char *args, | |
| 127 | + const struct AVTextFormatSection *sections, int nb_sections, | |
| 128 | + int show_value_unit, | |
| 129 | + int use_value_prefix, | |
| 130 | + int use_byte_value_binary_prefix, | |
| 131 | + int use_value_sexagesimal_format, | |
| 132 | + int show_optional_fields, | |
| 133 | + char *show_data_hash); | |
| 134 | + | |
| 135 | +int avtext_context_close(AVTextFormatContext **tctx); | |
| 136 | + | |
| 137 | + | |
| 138 | +void avtext_print_section_header(AVTextFormatContext *tctx, const void *data, int section_id); | |
| 139 | + | |
| 140 | +void avtext_print_section_footer(AVTextFormatContext *tctx); | |
| 141 | + | |
| 142 | +void avtext_print_integer(AVTextFormatContext *tctx, const char *key, int64_t val); | |
| 143 | + | |
| 144 | +int avtext_print_string(AVTextFormatContext *tctx, const char *key, const char *val, int flags); | |
| 145 | + | |
| 146 | +void avtext_print_unit_int(AVTextFormatContext *tctx, const char *key, int value, const char *unit); | |
| 147 | + | |
| 148 | +void avtext_print_rational(AVTextFormatContext *tctx, const char *key, AVRational q, char sep); | |
| 149 | + | |
| 150 | +void avtext_print_time(AVTextFormatContext *tctx, const char *key, int64_t ts, const AVRational *time_base, int is_duration); | |
| 151 | + | |
| 152 | +void avtext_print_ts(AVTextFormatContext *tctx, const char *key, int64_t ts, int is_duration); | |
| 153 | + | |
| 154 | +void avtext_print_data(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size); | |
| 155 | + | |
| 156 | +void avtext_print_data_hash(AVTextFormatContext *tctx, const char *name, const uint8_t *data, int size); | |
| 157 | + | |
| 158 | +void avtext_print_integers(AVTextFormatContext *tctx, const char *name, uint8_t *data, int size, | |
| 159 | + const char *format, int columns, int bytes, int offset_add); | |
| 160 | + | |
| 161 | +const AVTextFormatter *avtext_get_formatter_by_name(const char *name); | |
| 162 | + | |
| 163 | +extern const AVTextFormatter avtextformatter_default; | |
| 164 | +extern const AVTextFormatter avtextformatter_compact; | |
| 165 | +extern const AVTextFormatter avtextformatter_csv; | |
| 166 | +extern const AVTextFormatter avtextformatter_flat; | |
| 167 | +extern const AVTextFormatter avtextformatter_ini; | |
| 168 | +extern const AVTextFormatter avtextformatter_json; | |
| 169 | +extern const AVTextFormatter avtextformatter_xml; | |
| 170 | + | |
| 171 | +#endif /* FFTOOLS_TEXTFORMAT_AVTEXTFORMAT_H */ | |
| @@ -0,0 +1,68 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#ifndef FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H | |
| 22 | +#define FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H | |
| 23 | + | |
| 24 | +#include <stddef.h> | |
| 25 | +#include <stdint.h> | |
| 26 | +#include "libavutil/attributes.h" | |
| 27 | +#include "libavutil/dict.h" | |
| 28 | +#include "libavformat/avio.h" | |
| 29 | +#include "libavutil/bprint.h" | |
| 30 | +#include "libavutil/rational.h" | |
| 31 | +#include "libavutil/hash.h" | |
| 32 | + | |
| 33 | +typedef struct AVTextWriterContext AVTextWriterContext; | |
| 34 | + | |
| 35 | +typedef struct AVTextWriter { | |
| 36 | + const AVClass *priv_class; ///< private class of the writer, if any | |
| 37 | + int priv_size; ///< private size for the writer private class | |
| 38 | + const char *name; | |
| 39 | + | |
| 40 | + int (* init)(AVTextWriterContext *wctx); | |
| 41 | + void (* uninit)(AVTextWriterContext *wctx); | |
| 42 | + void (* writer_w8)(AVTextWriterContext *wctx, int b); | |
| 43 | + void (* writer_put_str)(AVTextWriterContext *wctx, const char *str); | |
| 44 | + void (* writer_printf)(AVTextWriterContext *wctx, const char *fmt, ...); | |
| 45 | +} AVTextWriter; | |
| 46 | + | |
| 47 | +typedef struct AVTextWriterContext { | |
| 48 | + const AVClass *class; ///< class of the writer | |
| 49 | + const AVTextWriter *writer; | |
| 50 | + const char *name; | |
| 51 | + void *priv; ///< private data for use by the writer | |
| 52 | + | |
| 53 | +} AVTextWriterContext; | |
| 54 | + | |
| 55 | + | |
| 56 | +int avtextwriter_context_open(AVTextWriterContext **pwctx, const AVTextWriter *writer); | |
| 57 | + | |
| 58 | +int avtextwriter_context_close(AVTextWriterContext **pwctx); | |
| 59 | + | |
| 60 | +int avtextwriter_create_stdout(AVTextWriterContext **pwctx); | |
| 61 | + | |
| 62 | +int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit); | |
| 63 | + | |
| 64 | +int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit); | |
| 65 | + | |
| 66 | +int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer); | |
| 67 | + | |
| 68 | +#endif /* FFTOOLS_TEXTFORMAT_AVTEXTWRITERS_H */ | |
| @@ -0,0 +1,282 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/error.h> | |
| 32 | +#include <libavutil/macros.h> | |
| 33 | +#include <libavutil/opt.h> | |
| 34 | + | |
| 35 | + | |
| 36 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 37 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 38 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 39 | + | |
| 40 | + | |
| 41 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 42 | +static const char *name##_get_name(void *ctx) \ | |
| 43 | +{ \ | |
| 44 | + return #name ; \ | |
| 45 | +} \ | |
| 46 | +static const AVClass name##_class = { \ | |
| 47 | + .class_name = #name, \ | |
| 48 | + .item_name = name##_get_name, \ | |
| 49 | + .option = name##_options \ | |
| 50 | +} | |
| 51 | + | |
| 52 | + | |
| 53 | +/* Compact output */ | |
| 54 | + | |
| 55 | +/** | |
| 56 | + * Apply C-language-like string escaping. | |
| 57 | + */ | |
| 58 | +static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |
| 59 | +{ | |
| 60 | + const char *p; | |
| 61 | + | |
| 62 | + for (p = src; *p; p++) { | |
| 63 | + switch (*p) { | |
| 64 | + case '\b': av_bprintf(dst, "%s", "\\b"); break; | |
| 65 | + case '\f': av_bprintf(dst, "%s", "\\f"); break; | |
| 66 | + case '\n': av_bprintf(dst, "%s", "\\n"); break; | |
| 67 | + case '\r': av_bprintf(dst, "%s", "\\r"); break; | |
| 68 | + case '\\': av_bprintf(dst, "%s", "\\\\"); break; | |
| 69 | + default: | |
| 70 | + if (*p == sep) | |
| 71 | + av_bprint_chars(dst, '\\', 1); | |
| 72 | + av_bprint_chars(dst, *p, 1); | |
| 73 | + } | |
| 74 | + } | |
| 75 | + return dst->str; | |
| 76 | +} | |
| 77 | + | |
| 78 | +/** | |
| 79 | + * Quote fields containing special characters, check RFC4180. | |
| 80 | + */ | |
| 81 | +static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |
| 82 | +{ | |
| 83 | + char meta_chars[] = { sep, '"', '\n', '\r', '\0' }; | |
| 84 | + int needs_quoting = !!src[strcspn(src, meta_chars)]; | |
| 85 | + | |
| 86 | + if (needs_quoting) | |
| 87 | + av_bprint_chars(dst, '"', 1); | |
| 88 | + | |
| 89 | + for (; *src; src++) { | |
| 90 | + if (*src == '"') | |
| 91 | + av_bprint_chars(dst, '"', 1); | |
| 92 | + av_bprint_chars(dst, *src, 1); | |
| 93 | + } | |
| 94 | + if (needs_quoting) | |
| 95 | + av_bprint_chars(dst, '"', 1); | |
| 96 | + return dst->str; | |
| 97 | +} | |
| 98 | + | |
| 99 | +static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx) | |
| 100 | +{ | |
| 101 | + return src; | |
| 102 | +} | |
| 103 | + | |
| 104 | +typedef struct CompactContext { | |
| 105 | + const AVClass *class; | |
| 106 | + char *item_sep_str; | |
| 107 | + char item_sep; | |
| 108 | + int nokey; | |
| 109 | + int print_section; | |
| 110 | + char *escape_mode_str; | |
| 111 | + const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx); | |
| 112 | + int nested_section[SECTION_MAX_NB_LEVELS]; | |
| 113 | + int has_nested_elems[SECTION_MAX_NB_LEVELS]; | |
| 114 | + int terminate_line[SECTION_MAX_NB_LEVELS]; | |
| 115 | +} CompactContext; | |
| 116 | + | |
| 117 | +#undef OFFSET | |
| 118 | +#define OFFSET(x) offsetof(CompactContext, x) | |
| 119 | + | |
| 120 | +static const AVOption compact_options[]= { | |
| 121 | + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, | |
| 122 | + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 }, | |
| 123 | + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 124 | + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 125 | + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, | |
| 126 | + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 }, | |
| 127 | + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 128 | + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 129 | + {NULL}, | |
| 130 | +}; | |
| 131 | + | |
| 132 | +DEFINE_FORMATTER_CLASS(compact); | |
| 133 | + | |
| 134 | +static av_cold int compact_init(AVTextFormatContext *wctx) | |
| 135 | +{ | |
| 136 | + CompactContext *compact = wctx->priv; | |
| 137 | + | |
| 138 | + if (strlen(compact->item_sep_str) != 1) { | |
| 139 | + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", | |
| 140 | + compact->item_sep_str); | |
| 141 | + return AVERROR(EINVAL); | |
| 142 | + } | |
| 143 | + compact->item_sep = compact->item_sep_str[0]; | |
| 144 | + | |
| 145 | + if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str; | |
| 146 | + else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str; | |
| 147 | + else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str; | |
| 148 | + else { | |
| 149 | + av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str); | |
| 150 | + return AVERROR(EINVAL); | |
| 151 | + } | |
| 152 | + | |
| 153 | + return 0; | |
| 154 | +} | |
| 155 | + | |
| 156 | +static void compact_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 157 | +{ | |
| 158 | + CompactContext *compact = wctx->priv; | |
| 159 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 160 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 161 | + wctx->section[wctx->level-1] : NULL; | |
| 162 | + compact->terminate_line[wctx->level] = 1; | |
| 163 | + compact->has_nested_elems[wctx->level] = 0; | |
| 164 | + | |
| 165 | + av_bprint_clear(&wctx->section_pbuf[wctx->level]); | |
| 166 | + if (parent_section && | |
| 167 | + (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE || | |
| 168 | + (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) && | |
| 169 | + !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) { | |
| 170 | + | |
| 171 | + /* define a prefix for elements not contained in an array or | |
| 172 | + in a wrapper, or for array elements with a type */ | |
| 173 | + const char *element_name = (char *)av_x_if_null(section->element_name, section->name); | |
| 174 | + AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level]; | |
| 175 | + | |
| 176 | + compact->nested_section[wctx->level] = 1; | |
| 177 | + compact->has_nested_elems[wctx->level-1] = 1; | |
| 178 | + | |
| 179 | + av_bprintf(section_pbuf, "%s%s", | |
| 180 | + wctx->section_pbuf[wctx->level-1].str, element_name); | |
| 181 | + | |
| 182 | + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { | |
| 183 | + // add /TYPE to prefix | |
| 184 | + av_bprint_chars(section_pbuf, '/', 1); | |
| 185 | + | |
| 186 | + // normalize section type, replace special characters and lower case | |
| 187 | + for (const char *p = section->get_type(data); *p; p++) { | |
| 188 | + char c = | |
| 189 | + (*p >= '0' && *p <= '9') || | |
| 190 | + (*p >= 'a' && *p <= 'z') || | |
| 191 | + (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_'; | |
| 192 | + av_bprint_chars(section_pbuf, c, 1); | |
| 193 | + } | |
| 194 | + } | |
| 195 | + av_bprint_chars(section_pbuf, ':', 1); | |
| 196 | + | |
| 197 | + wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1]; | |
| 198 | + } else { | |
| 199 | + if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) && | |
| 200 | + wctx->level && wctx->nb_item[wctx->level-1]) | |
| 201 | + writer_w8(wctx, compact->item_sep); | |
| 202 | + if (compact->print_section && | |
| 203 | + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) | |
| 204 | + writer_printf(wctx, "%s%c", section->name, compact->item_sep); | |
| 205 | + } | |
| 206 | +} | |
| 207 | + | |
| 208 | +static void compact_print_section_footer(AVTextFormatContext *wctx) | |
| 209 | +{ | |
| 210 | + CompactContext *compact = wctx->priv; | |
| 211 | + | |
| 212 | + if (!compact->nested_section[wctx->level] && | |
| 213 | + compact->terminate_line[wctx->level] && | |
| 214 | + !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) | |
| 215 | + writer_w8(wctx, '\n'); | |
| 216 | +} | |
| 217 | + | |
| 218 | +static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value) | |
| 219 | +{ | |
| 220 | + CompactContext *compact = wctx->priv; | |
| 221 | + AVBPrint buf; | |
| 222 | + | |
| 223 | + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); | |
| 224 | + if (!compact->nokey) | |
| 225 | + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); | |
| 226 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 227 | + writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx)); | |
| 228 | + av_bprint_finalize(&buf, NULL); | |
| 229 | +} | |
| 230 | + | |
| 231 | +static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 232 | +{ | |
| 233 | + CompactContext *compact = wctx->priv; | |
| 234 | + | |
| 235 | + if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep); | |
| 236 | + if (!compact->nokey) | |
| 237 | + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); | |
| 238 | + writer_printf(wctx, "%"PRId64, value); | |
| 239 | +} | |
| 240 | + | |
| 241 | +const AVTextFormatter avtextformatter_compact = { | |
| 242 | + .name = "compact", | |
| 243 | + .priv_size = sizeof(CompactContext), | |
| 244 | + .init = compact_init, | |
| 245 | + .print_section_header = compact_print_section_header, | |
| 246 | + .print_section_footer = compact_print_section_footer, | |
| 247 | + .print_integer = compact_print_int, | |
| 248 | + .print_string = compact_print_str, | |
| 249 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, | |
| 250 | + .priv_class = &compact_class, | |
| 251 | +}; | |
| 252 | + | |
| 253 | +/* CSV output */ | |
| 254 | + | |
| 255 | +#undef OFFSET | |
| 256 | +#define OFFSET(x) offsetof(CompactContext, x) | |
| 257 | + | |
| 258 | +static const AVOption csv_options[] = { | |
| 259 | + {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, | |
| 260 | + {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 }, | |
| 261 | + {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 262 | + {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 263 | + {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, | |
| 264 | + {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 }, | |
| 265 | + {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 266 | + {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 267 | + {NULL}, | |
| 268 | +}; | |
| 269 | + | |
| 270 | +DEFINE_FORMATTER_CLASS(csv); | |
| 271 | + | |
| 272 | +const AVTextFormatter avtextformatter_csv = { | |
| 273 | + .name = "csv", | |
| 274 | + .priv_size = sizeof(CompactContext), | |
| 275 | + .init = compact_init, | |
| 276 | + .print_section_header = compact_print_section_header, | |
| 277 | + .print_section_footer = compact_print_section_footer, | |
| 278 | + .print_integer = compact_print_int, | |
| 279 | + .print_string = compact_print_str, | |
| 280 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, | |
| 281 | + .priv_class = &csv_class, | |
| 282 | +}; | |
| @@ -0,0 +1,145 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/opt.h> | |
| 32 | + | |
| 33 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 34 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 35 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 36 | + | |
| 37 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 38 | +static const char *name##_get_name(void *ctx) \ | |
| 39 | +{ \ | |
| 40 | + return #name ; \ | |
| 41 | +} \ | |
| 42 | +static const AVClass name##_class = { \ | |
| 43 | + .class_name = #name, \ | |
| 44 | + .item_name = name##_get_name, \ | |
| 45 | + .option = name##_options \ | |
| 46 | +} | |
| 47 | + | |
| 48 | +/* Default output */ | |
| 49 | + | |
| 50 | +typedef struct DefaultContext { | |
| 51 | + const AVClass *class; | |
| 52 | + int nokey; | |
| 53 | + int noprint_wrappers; | |
| 54 | + int nested_section[SECTION_MAX_NB_LEVELS]; | |
| 55 | +} DefaultContext; | |
| 56 | + | |
| 57 | +#undef OFFSET | |
| 58 | +#define OFFSET(x) offsetof(DefaultContext, x) | |
| 59 | + | |
| 60 | +static const AVOption default_options[] = { | |
| 61 | + { "noprint_wrappers", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 62 | + { "nw", "do not print headers and footers", OFFSET(noprint_wrappers), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 63 | + { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 64 | + { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 65 | + {NULL}, | |
| 66 | +}; | |
| 67 | + | |
| 68 | +DEFINE_FORMATTER_CLASS(default); | |
| 69 | + | |
| 70 | +/* lame uppercasing routine, assumes the string is lower case ASCII */ | |
| 71 | +static inline char *upcase_string(char *dst, size_t dst_size, const char *src) | |
| 72 | +{ | |
| 73 | + int i; | |
| 74 | + for (i = 0; src[i] && i < dst_size-1; i++) | |
| 75 | + dst[i] = av_toupper(src[i]); | |
| 76 | + dst[i] = 0; | |
| 77 | + return dst; | |
| 78 | +} | |
| 79 | + | |
| 80 | +static void default_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 81 | +{ | |
| 82 | + DefaultContext *def = wctx->priv; | |
| 83 | + char buf[32]; | |
| 84 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 85 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 86 | + wctx->section[wctx->level-1] : NULL; | |
| 87 | + | |
| 88 | + av_bprint_clear(&wctx->section_pbuf[wctx->level]); | |
| 89 | + if (parent_section && | |
| 90 | + !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) { | |
| 91 | + def->nested_section[wctx->level] = 1; | |
| 92 | + av_bprintf(&wctx->section_pbuf[wctx->level], "%s%s:", | |
| 93 | + wctx->section_pbuf[wctx->level-1].str, | |
| 94 | + upcase_string(buf, sizeof(buf), | |
| 95 | + av_x_if_null(section->element_name, section->name))); | |
| 96 | + } | |
| 97 | + | |
| 98 | + if (def->noprint_wrappers || def->nested_section[wctx->level]) | |
| 99 | + return; | |
| 100 | + | |
| 101 | + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) | |
| 102 | + writer_printf(wctx, "[%s]\n", upcase_string(buf, sizeof(buf), section->name)); | |
| 103 | +} | |
| 104 | + | |
| 105 | +static void default_print_section_footer(AVTextFormatContext *wctx) | |
| 106 | +{ | |
| 107 | + DefaultContext *def = wctx->priv; | |
| 108 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 109 | + char buf[32]; | |
| 110 | + | |
| 111 | + if (def->noprint_wrappers || def->nested_section[wctx->level]) | |
| 112 | + return; | |
| 113 | + | |
| 114 | + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) | |
| 115 | + writer_printf(wctx, "[/%s]\n", upcase_string(buf, sizeof(buf), section->name)); | |
| 116 | +} | |
| 117 | + | |
| 118 | +static void default_print_str(AVTextFormatContext *wctx, const char *key, const char *value) | |
| 119 | +{ | |
| 120 | + DefaultContext *def = wctx->priv; | |
| 121 | + | |
| 122 | + if (!def->nokey) | |
| 123 | + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); | |
| 124 | + writer_printf(wctx, "%s\n", value); | |
| 125 | +} | |
| 126 | + | |
| 127 | +static void default_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 128 | +{ | |
| 129 | + DefaultContext *def = wctx->priv; | |
| 130 | + | |
| 131 | + if (!def->nokey) | |
| 132 | + writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key); | |
| 133 | + writer_printf(wctx, "%"PRId64"\n", value); | |
| 134 | +} | |
| 135 | + | |
| 136 | +const AVTextFormatter avtextformatter_default = { | |
| 137 | + .name = "default", | |
| 138 | + .priv_size = sizeof(DefaultContext), | |
| 139 | + .print_section_header = default_print_section_header, | |
| 140 | + .print_section_footer = default_print_section_footer, | |
| 141 | + .print_integer = default_print_int, | |
| 142 | + .print_string = default_print_str, | |
| 143 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS, | |
| 144 | + .priv_class = &default_class, | |
| 145 | +}; | |
| @@ -0,0 +1,174 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/error.h> | |
| 32 | +#include <libavutil/macros.h> | |
| 33 | +#include <libavutil/opt.h> | |
| 34 | + | |
| 35 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 36 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 37 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 38 | + | |
| 39 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 40 | +static const char *name##_get_name(void *ctx) \ | |
| 41 | +{ \ | |
| 42 | + return #name ; \ | |
| 43 | +} \ | |
| 44 | +static const AVClass name##_class = { \ | |
| 45 | + .class_name = #name, \ | |
| 46 | + .item_name = name##_get_name, \ | |
| 47 | + .option = name##_options \ | |
| 48 | +} | |
| 49 | + | |
| 50 | + | |
| 51 | +/* Flat output */ | |
| 52 | + | |
| 53 | +typedef struct FlatContext { | |
| 54 | + const AVClass *class; | |
| 55 | + const char *sep_str; | |
| 56 | + char sep; | |
| 57 | + int hierarchical; | |
| 58 | +} FlatContext; | |
| 59 | + | |
| 60 | +#undef OFFSET | |
| 61 | +#define OFFSET(x) offsetof(FlatContext, x) | |
| 62 | + | |
| 63 | +static const AVOption flat_options[]= { | |
| 64 | + {"sep_char", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 }, | |
| 65 | + {"s", "set separator", OFFSET(sep_str), AV_OPT_TYPE_STRING, {.str="."}, 0, 0 }, | |
| 66 | + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 67 | + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 68 | + {NULL}, | |
| 69 | +}; | |
| 70 | + | |
| 71 | +DEFINE_FORMATTER_CLASS(flat); | |
| 72 | + | |
| 73 | +static av_cold int flat_init(AVTextFormatContext *wctx) | |
| 74 | +{ | |
| 75 | + FlatContext *flat = wctx->priv; | |
| 76 | + | |
| 77 | + if (strlen(flat->sep_str) != 1) { | |
| 78 | + av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n", | |
| 79 | + flat->sep_str); | |
| 80 | + return AVERROR(EINVAL); | |
| 81 | + } | |
| 82 | + flat->sep = flat->sep_str[0]; | |
| 83 | + | |
| 84 | + return 0; | |
| 85 | +} | |
| 86 | + | |
| 87 | +static const char *flat_escape_key_str(AVBPrint *dst, const char *src, const char sep) | |
| 88 | +{ | |
| 89 | + const char *p; | |
| 90 | + | |
| 91 | + for (p = src; *p; p++) { | |
| 92 | + if (!((*p >= '0' && *p <= '9') || | |
| 93 | + (*p >= 'a' && *p <= 'z') || | |
| 94 | + (*p >= 'A' && *p <= 'Z'))) | |
| 95 | + av_bprint_chars(dst, '_', 1); | |
| 96 | + else | |
| 97 | + av_bprint_chars(dst, *p, 1); | |
| 98 | + } | |
| 99 | + return dst->str; | |
| 100 | +} | |
| 101 | + | |
| 102 | +static const char *flat_escape_value_str(AVBPrint *dst, const char *src) | |
| 103 | +{ | |
| 104 | + const char *p; | |
| 105 | + | |
| 106 | + for (p = src; *p; p++) { | |
| 107 | + switch (*p) { | |
| 108 | + case '\n': av_bprintf(dst, "%s", "\\n"); break; | |
| 109 | + case '\r': av_bprintf(dst, "%s", "\\r"); break; | |
| 110 | + case '\\': av_bprintf(dst, "%s", "\\\\"); break; | |
| 111 | + case '"': av_bprintf(dst, "%s", "\\\""); break; | |
| 112 | + case '`': av_bprintf(dst, "%s", "\\`"); break; | |
| 113 | + case '$': av_bprintf(dst, "%s", "\\$"); break; | |
| 114 | + default: av_bprint_chars(dst, *p, 1); break; | |
| 115 | + } | |
| 116 | + } | |
| 117 | + return dst->str; | |
| 118 | +} | |
| 119 | + | |
| 120 | +static void flat_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 121 | +{ | |
| 122 | + FlatContext *flat = wctx->priv; | |
| 123 | + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; | |
| 124 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 125 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 126 | + wctx->section[wctx->level-1] : NULL; | |
| 127 | + | |
| 128 | + /* build section header */ | |
| 129 | + av_bprint_clear(buf); | |
| 130 | + if (!parent_section) | |
| 131 | + return; | |
| 132 | + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); | |
| 133 | + | |
| 134 | + if (flat->hierarchical || | |
| 135 | + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) { | |
| 136 | + av_bprintf(buf, "%s%s", wctx->section[wctx->level]->name, flat->sep_str); | |
| 137 | + | |
| 138 | + if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { | |
| 139 | + int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ? | |
| 140 | + wctx->nb_item_type[wctx->level-1][section->id] : | |
| 141 | + wctx->nb_item[wctx->level-1]; | |
| 142 | + av_bprintf(buf, "%d%s", n, flat->sep_str); | |
| 143 | + } | |
| 144 | + } | |
| 145 | +} | |
| 146 | + | |
| 147 | +static void flat_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 148 | +{ | |
| 149 | + writer_printf(wctx, "%s%s=%"PRId64"\n", wctx->section_pbuf[wctx->level].str, key, value); | |
| 150 | +} | |
| 151 | + | |
| 152 | +static void flat_print_str(AVTextFormatContext *wctx, const char *key, const char *value) | |
| 153 | +{ | |
| 154 | + FlatContext *flat = wctx->priv; | |
| 155 | + AVBPrint buf; | |
| 156 | + | |
| 157 | + writer_put_str(wctx, wctx->section_pbuf[wctx->level].str); | |
| 158 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 159 | + writer_printf(wctx, "%s=", flat_escape_key_str(&buf, key, flat->sep)); | |
| 160 | + av_bprint_clear(&buf); | |
| 161 | + writer_printf(wctx, "\"%s\"\n", flat_escape_value_str(&buf, value)); | |
| 162 | + av_bprint_finalize(&buf, NULL); | |
| 163 | +} | |
| 164 | + | |
| 165 | +const AVTextFormatter avtextformatter_flat = { | |
| 166 | + .name = "flat", | |
| 167 | + .priv_size = sizeof(FlatContext), | |
| 168 | + .init = flat_init, | |
| 169 | + .print_section_header = flat_print_section_header, | |
| 170 | + .print_integer = flat_print_int, | |
| 171 | + .print_string = flat_print_str, | |
| 172 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, | |
| 173 | + .priv_class = &flat_class, | |
| 174 | +}; | |
| @@ -0,0 +1,160 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/opt.h> | |
| 32 | + | |
| 33 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 34 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 35 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 36 | + | |
| 37 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 38 | +static const char *name##_get_name(void *ctx) \ | |
| 39 | +{ \ | |
| 40 | + return #name ; \ | |
| 41 | +} \ | |
| 42 | +static const AVClass name##_class = { \ | |
| 43 | + .class_name = #name, \ | |
| 44 | + .item_name = name##_get_name, \ | |
| 45 | + .option = name##_options \ | |
| 46 | +} | |
| 47 | + | |
| 48 | +/* Default output */ | |
| 49 | + | |
| 50 | +typedef struct DefaultContext { | |
| 51 | + const AVClass *class; | |
| 52 | + int nokey; | |
| 53 | + int noprint_wrappers; | |
| 54 | + int nested_section[SECTION_MAX_NB_LEVELS]; | |
| 55 | +} DefaultContext; | |
| 56 | + | |
| 57 | +/* INI format output */ | |
| 58 | + | |
| 59 | +typedef struct INIContext { | |
| 60 | + const AVClass *class; | |
| 61 | + int hierarchical; | |
| 62 | +} INIContext; | |
| 63 | + | |
| 64 | +#undef OFFSET | |
| 65 | +#define OFFSET(x) offsetof(INIContext, x) | |
| 66 | + | |
| 67 | +static const AVOption ini_options[] = { | |
| 68 | + {"hierarchical", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 69 | + {"h", "specify if the section specification should be hierarchical", OFFSET(hierarchical), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 }, | |
| 70 | + {NULL}, | |
| 71 | +}; | |
| 72 | + | |
| 73 | +DEFINE_FORMATTER_CLASS(ini); | |
| 74 | + | |
| 75 | +static char *ini_escape_str(AVBPrint *dst, const char *src) | |
| 76 | +{ | |
| 77 | + int i = 0; | |
| 78 | + char c = 0; | |
| 79 | + | |
| 80 | + while (c = src[i++]) { | |
| 81 | + switch (c) { | |
| 82 | + case '\b': av_bprintf(dst, "%s", "\\b"); break; | |
| 83 | + case '\f': av_bprintf(dst, "%s", "\\f"); break; | |
| 84 | + case '\n': av_bprintf(dst, "%s", "\\n"); break; | |
| 85 | + case '\r': av_bprintf(dst, "%s", "\\r"); break; | |
| 86 | + case '\t': av_bprintf(dst, "%s", "\\t"); break; | |
| 87 | + case '\\': | |
| 88 | + case '#' : | |
| 89 | + case '=' : | |
| 90 | + case ':' : av_bprint_chars(dst, '\\', 1); | |
| 91 | + default: | |
| 92 | + if ((unsigned char)c < 32) | |
| 93 | + av_bprintf(dst, "\\x00%02x", c & 0xff); | |
| 94 | + else | |
| 95 | + av_bprint_chars(dst, c, 1); | |
| 96 | + break; | |
| 97 | + } | |
| 98 | + } | |
| 99 | + return dst->str; | |
| 100 | +} | |
| 101 | + | |
| 102 | +static void ini_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 103 | +{ | |
| 104 | + INIContext *ini = wctx->priv; | |
| 105 | + AVBPrint *buf = &wctx->section_pbuf[wctx->level]; | |
| 106 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 107 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 108 | + wctx->section[wctx->level-1] : NULL; | |
| 109 | + | |
| 110 | + av_bprint_clear(buf); | |
| 111 | + if (!parent_section) { | |
| 112 | + writer_put_str(wctx, "# ffprobe output\n\n"); | |
| 113 | + return; | |
| 114 | + } | |
| 115 | + | |
| 116 | + if (wctx->nb_item[wctx->level-1]) | |
| 117 | + writer_w8(wctx, '\n'); | |
| 118 | + | |
| 119 | + av_bprintf(buf, "%s", wctx->section_pbuf[wctx->level-1].str); | |
| 120 | + if (ini->hierarchical || | |
| 121 | + !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) { | |
| 122 | + av_bprintf(buf, "%s%s", buf->str[0] ? "." : "", wctx->section[wctx->level]->name); | |
| 123 | + | |
| 124 | + if (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { | |
| 125 | + int n = parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE ? | |
| 126 | + wctx->nb_item_type[wctx->level-1][section->id] : | |
| 127 | + wctx->nb_item[wctx->level-1]; | |
| 128 | + av_bprintf(buf, ".%d", n); | |
| 129 | + } | |
| 130 | + } | |
| 131 | + | |
| 132 | + if (!(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER))) | |
| 133 | + writer_printf(wctx, "[%s]\n", buf->str); | |
| 134 | +} | |
| 135 | + | |
| 136 | +static void ini_print_str(AVTextFormatContext *wctx, const char *key, const char *value) | |
| 137 | +{ | |
| 138 | + AVBPrint buf; | |
| 139 | + | |
| 140 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 141 | + writer_printf(wctx, "%s=", ini_escape_str(&buf, key)); | |
| 142 | + av_bprint_clear(&buf); | |
| 143 | + writer_printf(wctx, "%s\n", ini_escape_str(&buf, value)); | |
| 144 | + av_bprint_finalize(&buf, NULL); | |
| 145 | +} | |
| 146 | + | |
| 147 | +static void ini_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 148 | +{ | |
| 149 | + writer_printf(wctx, "%s=%"PRId64"\n", key, value); | |
| 150 | +} | |
| 151 | + | |
| 152 | +const AVTextFormatter avtextformatter_ini = { | |
| 153 | + .name = "ini", | |
| 154 | + .priv_size = sizeof(INIContext), | |
| 155 | + .print_section_header = ini_print_section_header, | |
| 156 | + .print_integer = ini_print_int, | |
| 157 | + .print_string = ini_print_str, | |
| 158 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS|AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, | |
| 159 | + .priv_class = &ini_class, | |
| 160 | +}; | |
| @@ -0,0 +1,215 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/opt.h> | |
| 32 | + | |
| 33 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 34 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 35 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 36 | + | |
| 37 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 38 | +static const char *name##_get_name(void *ctx) \ | |
| 39 | +{ \ | |
| 40 | + return #name ; \ | |
| 41 | +} \ | |
| 42 | +static const AVClass name##_class = { \ | |
| 43 | + .class_name = #name, \ | |
| 44 | + .item_name = name##_get_name, \ | |
| 45 | + .option = name##_options \ | |
| 46 | +} | |
| 47 | + | |
| 48 | + | |
| 49 | +/* JSON output */ | |
| 50 | + | |
| 51 | +typedef struct JSONContext { | |
| 52 | + const AVClass *class; | |
| 53 | + int indent_level; | |
| 54 | + int compact; | |
| 55 | + const char *item_sep, *item_start_end; | |
| 56 | +} JSONContext; | |
| 57 | + | |
| 58 | +#undef OFFSET | |
| 59 | +#define OFFSET(x) offsetof(JSONContext, x) | |
| 60 | + | |
| 61 | +static const AVOption json_options[]= { | |
| 62 | + { "compact", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 63 | + { "c", "enable compact output", OFFSET(compact), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 64 | + { NULL } | |
| 65 | +}; | |
| 66 | + | |
| 67 | +DEFINE_FORMATTER_CLASS(json); | |
| 68 | + | |
| 69 | +static av_cold int json_init(AVTextFormatContext *wctx) | |
| 70 | +{ | |
| 71 | + JSONContext *json = wctx->priv; | |
| 72 | + | |
| 73 | + json->item_sep = json->compact ? ", " : ",\n"; | |
| 74 | + json->item_start_end = json->compact ? " " : "\n"; | |
| 75 | + | |
| 76 | + return 0; | |
| 77 | +} | |
| 78 | + | |
| 79 | +static const char *json_escape_str(AVBPrint *dst, const char *src, void *log_ctx) | |
| 80 | +{ | |
| 81 | + static const char json_escape[] = {'"', '\\', '\b', '\f', '\n', '\r', '\t', 0}; | |
| 82 | + static const char json_subst[] = {'"', '\\', 'b', 'f', 'n', 'r', 't', 0}; | |
| 83 | + const char *p; | |
| 84 | + | |
| 85 | + for (p = src; *p; p++) { | |
| 86 | + char *s = strchr(json_escape, *p); | |
| 87 | + if (s) { | |
| 88 | + av_bprint_chars(dst, '\\', 1); | |
| 89 | + av_bprint_chars(dst, json_subst[s - json_escape], 1); | |
| 90 | + } else if ((unsigned char)*p < 32) { | |
| 91 | + av_bprintf(dst, "\\u00%02x", *p & 0xff); | |
| 92 | + } else { | |
| 93 | + av_bprint_chars(dst, *p, 1); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + return dst->str; | |
| 97 | +} | |
| 98 | + | |
| 99 | +#define JSON_INDENT() writer_printf(wctx, "%*c", json->indent_level * 4, ' ') | |
| 100 | + | |
| 101 | +static void json_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 102 | +{ | |
| 103 | + JSONContext *json = wctx->priv; | |
| 104 | + AVBPrint buf; | |
| 105 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 106 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 107 | + wctx->section[wctx->level-1] : NULL; | |
| 108 | + | |
| 109 | + if (wctx->level && wctx->nb_item[wctx->level-1]) | |
| 110 | + writer_put_str(wctx, ",\n"); | |
| 111 | + | |
| 112 | + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) { | |
| 113 | + writer_put_str(wctx, "{\n"); | |
| 114 | + json->indent_level++; | |
| 115 | + } else { | |
| 116 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 117 | + json_escape_str(&buf, section->name, wctx); | |
| 118 | + JSON_INDENT(); | |
| 119 | + | |
| 120 | + json->indent_level++; | |
| 121 | + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { | |
| 122 | + writer_printf(wctx, "\"%s\": [\n", buf.str); | |
| 123 | + } else if (parent_section && !(parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) { | |
| 124 | + writer_printf(wctx, "\"%s\": {%s", buf.str, json->item_start_end); | |
| 125 | + } else { | |
| 126 | + writer_printf(wctx, "{%s", json->item_start_end); | |
| 127 | + | |
| 128 | + /* this is required so the parser can distinguish between packets and frames */ | |
| 129 | + if (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE) { | |
| 130 | + if (!json->compact) | |
| 131 | + JSON_INDENT(); | |
| 132 | + writer_printf(wctx, "\"type\": \"%s\"", section->name); | |
| 133 | + wctx->nb_item[wctx->level]++; | |
| 134 | + } | |
| 135 | + } | |
| 136 | + av_bprint_finalize(&buf, NULL); | |
| 137 | + } | |
| 138 | +} | |
| 139 | + | |
| 140 | +static void json_print_section_footer(AVTextFormatContext *wctx) | |
| 141 | +{ | |
| 142 | + JSONContext *json = wctx->priv; | |
| 143 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 144 | + | |
| 145 | + if (wctx->level == 0) { | |
| 146 | + json->indent_level--; | |
| 147 | + writer_put_str(wctx, "\n}\n"); | |
| 148 | + } else if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) { | |
| 149 | + writer_w8(wctx, '\n'); | |
| 150 | + json->indent_level--; | |
| 151 | + JSON_INDENT(); | |
| 152 | + writer_w8(wctx, ']'); | |
| 153 | + } else { | |
| 154 | + writer_put_str(wctx, json->item_start_end); | |
| 155 | + json->indent_level--; | |
| 156 | + if (!json->compact) | |
| 157 | + JSON_INDENT(); | |
| 158 | + writer_w8(wctx, '}'); | |
| 159 | + } | |
| 160 | +} | |
| 161 | + | |
| 162 | +static inline void json_print_item_str(AVTextFormatContext *wctx, | |
| 163 | + const char *key, const char *value) | |
| 164 | +{ | |
| 165 | + AVBPrint buf; | |
| 166 | + | |
| 167 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 168 | + writer_printf(wctx, "\"%s\":", json_escape_str(&buf, key, wctx)); | |
| 169 | + av_bprint_clear(&buf); | |
| 170 | + writer_printf(wctx, " \"%s\"", json_escape_str(&buf, value, wctx)); | |
| 171 | + av_bprint_finalize(&buf, NULL); | |
| 172 | +} | |
| 173 | + | |
| 174 | +static void json_print_str(AVTextFormatContext *wctx, const char *key, const char *value) | |
| 175 | +{ | |
| 176 | + JSONContext *json = wctx->priv; | |
| 177 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 178 | + wctx->section[wctx->level-1] : NULL; | |
| 179 | + | |
| 180 | + if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE)) | |
| 181 | + writer_put_str(wctx, json->item_sep); | |
| 182 | + if (!json->compact) | |
| 183 | + JSON_INDENT(); | |
| 184 | + json_print_item_str(wctx, key, value); | |
| 185 | +} | |
| 186 | + | |
| 187 | +static void json_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 188 | +{ | |
| 189 | + JSONContext *json = wctx->priv; | |
| 190 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 191 | + wctx->section[wctx->level-1] : NULL; | |
| 192 | + AVBPrint buf; | |
| 193 | + | |
| 194 | + if (wctx->nb_item[wctx->level] || (parent_section && parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_NUMBERING_BY_TYPE)) | |
| 195 | + writer_put_str(wctx, json->item_sep); | |
| 196 | + if (!json->compact) | |
| 197 | + JSON_INDENT(); | |
| 198 | + | |
| 199 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 200 | + writer_printf(wctx, "\"%s\": %"PRId64, json_escape_str(&buf, key, wctx), value); | |
| 201 | + av_bprint_finalize(&buf, NULL); | |
| 202 | +} | |
| 203 | + | |
| 204 | +const AVTextFormatter avtextformatter_json = { | |
| 205 | + .name = "json", | |
| 206 | + .priv_size = sizeof(JSONContext), | |
| 207 | + .init = json_init, | |
| 208 | + .print_section_header = json_print_section_header, | |
| 209 | + .print_section_footer = json_print_section_footer, | |
| 210 | + .print_integer = json_print_int, | |
| 211 | + .print_string = json_print_str, | |
| 212 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, | |
| 213 | + .priv_class = &json_class, | |
| 214 | +}; | |
| 215 | + | |
| @@ -0,0 +1,221 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | +#include <stdint.h> | |
| 24 | +#include <stdio.h> | |
| 25 | +#include <string.h> | |
| 26 | + | |
| 27 | +#include "avtextformat.h" | |
| 28 | +#include <libavutil/mem.h> | |
| 29 | +#include <libavutil/avassert.h> | |
| 30 | +#include <libavutil/bprint.h> | |
| 31 | +#include <libavutil/error.h> | |
| 32 | +#include <libavutil/macros.h> | |
| 33 | +#include <libavutil/opt.h> | |
| 34 | + | |
| 35 | +#define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_) | |
| 36 | +#define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_) | |
| 37 | +#define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__) | |
| 38 | + | |
| 39 | +#define DEFINE_FORMATTER_CLASS(name) \ | |
| 40 | +static const char *name##_get_name(void *ctx) \ | |
| 41 | +{ \ | |
| 42 | + return #name ; \ | |
| 43 | +} \ | |
| 44 | +static const AVClass name##_class = { \ | |
| 45 | + .class_name = #name, \ | |
| 46 | + .item_name = name##_get_name, \ | |
| 47 | + .option = name##_options \ | |
| 48 | +} | |
| 49 | + | |
| 50 | +/* XML output */ | |
| 51 | + | |
| 52 | +typedef struct XMLContext { | |
| 53 | + const AVClass *class; | |
| 54 | + int within_tag; | |
| 55 | + int indent_level; | |
| 56 | + int fully_qualified; | |
| 57 | + int xsd_strict; | |
| 58 | +} XMLContext; | |
| 59 | + | |
| 60 | +#undef OFFSET | |
| 61 | +#define OFFSET(x) offsetof(XMLContext, x) | |
| 62 | + | |
| 63 | +static const AVOption xml_options[] = { | |
| 64 | + {"fully_qualified", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 65 | + {"q", "specify if the output should be fully qualified", OFFSET(fully_qualified), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 66 | + {"xsd_strict", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 67 | + {"x", "ensure that the output is XSD compliant", OFFSET(xsd_strict), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 }, | |
| 68 | + {NULL}, | |
| 69 | +}; | |
| 70 | + | |
| 71 | +DEFINE_FORMATTER_CLASS(xml); | |
| 72 | + | |
| 73 | +static av_cold int xml_init(AVTextFormatContext *wctx) | |
| 74 | +{ | |
| 75 | + XMLContext *xml = wctx->priv; | |
| 76 | + | |
| 77 | + if (xml->xsd_strict) { | |
| 78 | + xml->fully_qualified = 1; | |
| 79 | +#define CHECK_COMPLIANCE(opt, opt_name) \ | |
| 80 | + if (opt) { \ | |
| 81 | + av_log(wctx, AV_LOG_ERROR, \ | |
| 82 | + "XSD-compliant output selected but option '%s' was selected, XML output may be non-compliant.\n" \ | |
| 83 | + "You need to disable such option with '-no%s'\n", opt_name, opt_name); \ | |
| 84 | + return AVERROR(EINVAL); \ | |
| 85 | + } | |
| 86 | + ////CHECK_COMPLIANCE(show_private_data, "private"); | |
| 87 | + CHECK_COMPLIANCE(wctx->show_value_unit, "unit"); | |
| 88 | + CHECK_COMPLIANCE(wctx->use_value_prefix, "prefix"); | |
| 89 | + } | |
| 90 | + | |
| 91 | + return 0; | |
| 92 | +} | |
| 93 | + | |
| 94 | +#define XML_INDENT() writer_printf(wctx, "%*c", xml->indent_level * 4, ' ') | |
| 95 | + | |
| 96 | +static void xml_print_section_header(AVTextFormatContext *wctx, const void *data) | |
| 97 | +{ | |
| 98 | + XMLContext *xml = wctx->priv; | |
| 99 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 100 | + const struct AVTextFormatSection *parent_section = wctx->level ? | |
| 101 | + wctx->section[wctx->level-1] : NULL; | |
| 102 | + | |
| 103 | + if (wctx->level == 0) { | |
| 104 | + const char *qual = " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " | |
| 105 | + "xmlns:ffprobe=\"http://www.ffmpeg.org/schema/ffprobe\" " | |
| 106 | + "xsi:schemaLocation=\"http://www.ffmpeg.org/schema/ffprobe ffprobe.xsd\""; | |
| 107 | + | |
| 108 | + writer_put_str(wctx, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"); | |
| 109 | + writer_printf(wctx, "<%sffprobe%s>\n", | |
| 110 | + xml->fully_qualified ? "ffprobe:" : "", | |
| 111 | + xml->fully_qualified ? qual : ""); | |
| 112 | + return; | |
| 113 | + } | |
| 114 | + | |
| 115 | + if (xml->within_tag) { | |
| 116 | + xml->within_tag = 0; | |
| 117 | + writer_put_str(wctx, ">\n"); | |
| 118 | + } | |
| 119 | + | |
| 120 | + if (parent_section && (parent_section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER) && | |
| 121 | + wctx->level && wctx->nb_item[wctx->level-1]) | |
| 122 | + writer_w8(wctx, '\n'); | |
| 123 | + xml->indent_level++; | |
| 124 | + | |
| 125 | + if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY|AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS)) { | |
| 126 | + XML_INDENT(); writer_printf(wctx, "<%s", section->name); | |
| 127 | + | |
| 128 | + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) { | |
| 129 | + AVBPrint buf; | |
| 130 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 131 | + av_bprint_escape(&buf, section->get_type(data), NULL, | |
| 132 | + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); | |
| 133 | + writer_printf(wctx, " type=\"%s\"", buf.str); | |
| 134 | + } | |
| 135 | + writer_printf(wctx, ">\n", section->name); | |
| 136 | + } else { | |
| 137 | + XML_INDENT(); writer_printf(wctx, "<%s ", section->name); | |
| 138 | + xml->within_tag = 1; | |
| 139 | + } | |
| 140 | +} | |
| 141 | + | |
| 142 | +static void xml_print_section_footer(AVTextFormatContext *wctx) | |
| 143 | +{ | |
| 144 | + XMLContext *xml = wctx->priv; | |
| 145 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 146 | + | |
| 147 | + if (wctx->level == 0) { | |
| 148 | + writer_printf(wctx, "</%sffprobe>\n", xml->fully_qualified ? "ffprobe:" : ""); | |
| 149 | + } else if (xml->within_tag) { | |
| 150 | + xml->within_tag = 0; | |
| 151 | + writer_put_str(wctx, "/>\n"); | |
| 152 | + xml->indent_level--; | |
| 153 | + } else { | |
| 154 | + XML_INDENT(); writer_printf(wctx, "</%s>\n", section->name); | |
| 155 | + xml->indent_level--; | |
| 156 | + } | |
| 157 | +} | |
| 158 | + | |
| 159 | +static void xml_print_value(AVTextFormatContext *wctx, const char *key, | |
| 160 | + const char *str, int64_t num, const int is_int) | |
| 161 | +{ | |
| 162 | + AVBPrint buf; | |
| 163 | + XMLContext *xml = wctx->priv; | |
| 164 | + const struct AVTextFormatSection *section = wctx->section[wctx->level]; | |
| 165 | + | |
| 166 | + av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED); | |
| 167 | + | |
| 168 | + if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_VARIABLE_FIELDS) { | |
| 169 | + xml->indent_level++; | |
| 170 | + XML_INDENT(); | |
| 171 | + av_bprint_escape(&buf, key, NULL, | |
| 172 | + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); | |
| 173 | + writer_printf(wctx, "<%s key=\"%s\"", | |
| 174 | + section->element_name, buf.str); | |
| 175 | + av_bprint_clear(&buf); | |
| 176 | + | |
| 177 | + if (is_int) { | |
| 178 | + writer_printf(wctx, " value=\"%"PRId64"\"/>\n", num); | |
| 179 | + } else { | |
| 180 | + av_bprint_escape(&buf, str, NULL, | |
| 181 | + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); | |
| 182 | + writer_printf(wctx, " value=\"%s\"/>\n", buf.str); | |
| 183 | + } | |
| 184 | + xml->indent_level--; | |
| 185 | + } else { | |
| 186 | + if (wctx->nb_item[wctx->level]) | |
| 187 | + writer_w8(wctx, ' '); | |
| 188 | + | |
| 189 | + if (is_int) { | |
| 190 | + writer_printf(wctx, "%s=\"%"PRId64"\"", key, num); | |
| 191 | + } else { | |
| 192 | + av_bprint_escape(&buf, str, NULL, | |
| 193 | + AV_ESCAPE_MODE_XML, AV_ESCAPE_FLAG_XML_DOUBLE_QUOTES); | |
| 194 | + writer_printf(wctx, "%s=\"%s\"", key, buf.str); | |
| 195 | + } | |
| 196 | + } | |
| 197 | + | |
| 198 | + av_bprint_finalize(&buf, NULL); | |
| 199 | +} | |
| 200 | + | |
| 201 | +static inline void xml_print_str(AVTextFormatContext *wctx, const char *key, const char *value) { | |
| 202 | + xml_print_value(wctx, key, value, 0, 0); | |
| 203 | +} | |
| 204 | + | |
| 205 | +static void xml_print_int(AVTextFormatContext *wctx, const char *key, int64_t value) | |
| 206 | +{ | |
| 207 | + xml_print_value(wctx, key, NULL, value, 1); | |
| 208 | +} | |
| 209 | + | |
| 210 | +const AVTextFormatter avtextformatter_xml = { | |
| 211 | + .name = "xml", | |
| 212 | + .priv_size = sizeof(XMLContext), | |
| 213 | + .init = xml_init, | |
| 214 | + .print_section_header = xml_print_section_header, | |
| 215 | + .print_section_footer = xml_print_section_footer, | |
| 216 | + .print_integer = xml_print_int, | |
| 217 | + .print_string = xml_print_str, | |
| 218 | + .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_MIXED_ARRAY_CONTENT, | |
| 219 | + .priv_class = &xml_class, | |
| 220 | +}; | |
| 221 | + | |
| @@ -0,0 +1,129 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | + | |
| 24 | +#include "avtextwriters.h" | |
| 25 | +#include "libavutil/opt.h" | |
| 26 | + | |
| 27 | +/* AVIO Writer */ | |
| 28 | + | |
| 29 | +# define WRITER_NAME "aviowriter" | |
| 30 | + | |
| 31 | +typedef struct IOWriterContext { | |
| 32 | + const AVClass *class; | |
| 33 | + AVIOContext *avio_context; | |
| 34 | + int close_on_uninit; | |
| 35 | +} IOWriterContext; | |
| 36 | + | |
| 37 | +static const char *iowriter_get_name(void *ctx) | |
| 38 | +{ | |
| 39 | + return WRITER_NAME; | |
| 40 | +} | |
| 41 | + | |
| 42 | +static const AVClass iowriter_class = { | |
| 43 | + .class_name = WRITER_NAME, | |
| 44 | + .item_name = iowriter_get_name, | |
| 45 | +}; | |
| 46 | + | |
| 47 | +static av_cold void iowriter_uninit(AVTextWriterContext *wctx) | |
| 48 | +{ | |
| 49 | + IOWriterContext *ctx = wctx->priv; | |
| 50 | + | |
| 51 | + if (ctx->close_on_uninit && ctx->avio_context) { | |
| 52 | + avio_flush(ctx->avio_context); | |
| 53 | + avio_close(ctx->avio_context); | |
| 54 | + } | |
| 55 | +} | |
| 56 | + | |
| 57 | +static void io_w8(AVTextWriterContext *wctx, int b) | |
| 58 | +{ | |
| 59 | + IOWriterContext *ctx = wctx->priv; | |
| 60 | + avio_w8(ctx->avio_context, b); | |
| 61 | +} | |
| 62 | + | |
| 63 | +static void io_put_str(AVTextWriterContext *wctx, const char *str) | |
| 64 | +{ | |
| 65 | + IOWriterContext *ctx = wctx->priv; | |
| 66 | + avio_write(ctx->avio_context, str, strlen(str)); | |
| 67 | +} | |
| 68 | + | |
| 69 | +static void io_printf(AVTextWriterContext *wctx, const char *fmt, ...) | |
| 70 | +{ | |
| 71 | + IOWriterContext *ctx = wctx->priv; | |
| 72 | + va_list ap; | |
| 73 | + | |
| 74 | + va_start(ap, fmt); | |
| 75 | + avio_vprintf(ctx->avio_context, fmt, ap); | |
| 76 | + va_end(ap); | |
| 77 | +} | |
| 78 | + | |
| 79 | + | |
| 80 | +const AVTextWriter avtextwriter_avio = { | |
| 81 | + .name = WRITER_NAME, | |
| 82 | + .priv_size = sizeof(IOWriterContext), | |
| 83 | + .uninit = iowriter_uninit, | |
| 84 | + .priv_class = &iowriter_class, | |
| 85 | + .writer_put_str = io_put_str, | |
| 86 | + .writer_printf = io_printf, | |
| 87 | + .writer_w8 = io_w8 | |
| 88 | +}; | |
| 89 | + | |
| 90 | +int avtextwriter_create_file(AVTextWriterContext **pwctx, const char *output_filename, int close_on_uninit) | |
| 91 | +{ | |
| 92 | + IOWriterContext *ctx; | |
| 93 | + int ret; | |
| 94 | + | |
| 95 | + | |
| 96 | + ret = avtextwriter_context_open(pwctx, &avtextwriter_avio); | |
| 97 | + if (ret < 0) | |
| 98 | + return ret; | |
| 99 | + | |
| 100 | + ctx = (*pwctx)->priv; | |
| 101 | + | |
| 102 | + if ((ret = avio_open(&ctx->avio_context, output_filename, AVIO_FLAG_WRITE)) < 0) { | |
| 103 | + av_log(ctx, AV_LOG_ERROR, | |
| 104 | + "Failed to open output '%s' with error: %s\n", output_filename, av_err2str(ret)); | |
| 105 | + avtextwriter_context_close(pwctx); | |
| 106 | + return ret; | |
| 107 | + } | |
| 108 | + | |
| 109 | + ctx->close_on_uninit = close_on_uninit; | |
| 110 | + | |
| 111 | + return ret; | |
| 112 | +} | |
| 113 | + | |
| 114 | + | |
| 115 | +int avtextwriter_create_avio(AVTextWriterContext **pwctx, AVIOContext *avio_ctx, int close_on_uninit) | |
| 116 | +{ | |
| 117 | + IOWriterContext *ctx; | |
| 118 | + int ret; | |
| 119 | + | |
| 120 | + ret = avtextwriter_context_open(pwctx, &avtextwriter_avio); | |
| 121 | + if (ret < 0) | |
| 122 | + return ret; | |
| 123 | + | |
| 124 | + ctx = (*pwctx)->priv; | |
| 125 | + ctx->avio_context = avio_ctx; | |
| 126 | + ctx->close_on_uninit = close_on_uninit; | |
| 127 | + | |
| 128 | + return ret; | |
| 129 | +} | |
| @@ -0,0 +1,92 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <limits.h> | |
| 22 | +#include <stdarg.h> | |
| 23 | + | |
| 24 | +#include "avtextwriters.h" | |
| 25 | +#include "libavutil/opt.h" | |
| 26 | +#include "libavutil/bprint.h" | |
| 27 | + | |
| 28 | +/* Buffer Writer */ | |
| 29 | + | |
| 30 | +# define WRITER_NAME "bufferwriter" | |
| 31 | + | |
| 32 | +typedef struct BufferWriterContext { | |
| 33 | + const AVClass *class; | |
| 34 | + AVBPrint *buffer; | |
| 35 | +} BufferWriterContext; | |
| 36 | + | |
| 37 | +static const char *bufferwriter_get_name(void *ctx) | |
| 38 | +{ | |
| 39 | + return WRITER_NAME; | |
| 40 | +} | |
| 41 | + | |
| 42 | +static const AVClass bufferwriter_class = { | |
| 43 | + .class_name = WRITER_NAME, | |
| 44 | + .item_name = bufferwriter_get_name, | |
| 45 | +}; | |
| 46 | + | |
| 47 | +static void buffer_w8(AVTextWriterContext *wctx, int b) | |
| 48 | +{ | |
| 49 | + BufferWriterContext *ctx = wctx->priv; | |
| 50 | + av_bprintf(ctx->buffer, "%c", b); | |
| 51 | +} | |
| 52 | + | |
| 53 | +static void buffer_put_str(AVTextWriterContext *wctx, const char *str) | |
| 54 | +{ | |
| 55 | + BufferWriterContext *ctx = wctx->priv; | |
| 56 | + av_bprintf(ctx->buffer, "%s", str); | |
| 57 | +} | |
| 58 | + | |
| 59 | +static void buffer_printf(AVTextWriterContext *wctx, const char *fmt, ...) | |
| 60 | +{ | |
| 61 | + BufferWriterContext *ctx = wctx->priv; | |
| 62 | + | |
| 63 | + va_list vargs; | |
| 64 | + va_start(vargs, fmt); | |
| 65 | + av_vbprintf(ctx->buffer, fmt, vargs); | |
| 66 | + va_end(vargs); | |
| 67 | +} | |
| 68 | + | |
| 69 | + | |
| 70 | +const AVTextWriter avtextwriter_buffer = { | |
| 71 | + .name = WRITER_NAME, | |
| 72 | + .priv_size = sizeof(BufferWriterContext), | |
| 73 | + .priv_class = &bufferwriter_class, | |
| 74 | + .writer_put_str = buffer_put_str, | |
| 75 | + .writer_printf = buffer_printf, | |
| 76 | + .writer_w8 = buffer_w8 | |
| 77 | +}; | |
| 78 | + | |
| 79 | +int avtextwriter_create_buffer(AVTextWriterContext **pwctx, AVBPrint *buffer) | |
| 80 | +{ | |
| 81 | + BufferWriterContext *ctx; | |
| 82 | + int ret; | |
| 83 | + | |
| 84 | + ret = avtextwriter_context_open(pwctx, &avtextwriter_buffer); | |
| 85 | + if (ret < 0) | |
| 86 | + return ret; | |
| 87 | + | |
| 88 | + ctx = (*pwctx)->priv; | |
| 89 | + ctx->buffer = buffer; | |
| 90 | + | |
| 91 | + return ret; | |
| 92 | +} | |
| @@ -0,0 +1,82 @@ | ||
| 1 | +/* | |
| 2 | + * Copyright (c) The FFmpeg developers | |
| 3 | + * | |
| 4 | + * This file is part of FFmpeg. | |
| 5 | + * | |
| 6 | + * FFmpeg is free software; you can redistribute it and/or | |
| 7 | + * modify it under the terms of the GNU Lesser General Public | |
| 8 | + * License as published by the Free Software Foundation; either | |
| 9 | + * version 2.1 of the License, or (at your option) any later version. | |
| 10 | + * | |
| 11 | + * FFmpeg is distributed in the hope that it will be useful, | |
| 12 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| 13 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| 14 | + * Lesser General Public License for more details. | |
| 15 | + * | |
| 16 | + * You should have received a copy of the GNU Lesser General Public | |
| 17 | + * License along with FFmpeg; if not, write to the Free Software | |
| 18 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| 19 | + */ | |
| 20 | + | |
| 21 | +#include <stdarg.h> | |
| 22 | +#include <stdio.h> | |
| 23 | +#include <string.h> | |
| 24 | + | |
| 25 | +#include "avtextwriters.h" | |
| 26 | +#include "libavutil/opt.h" | |
| 27 | + | |
| 28 | +/* STDOUT Writer */ | |
| 29 | + | |
| 30 | +# define WRITER_NAME "stdoutwriter" | |
| 31 | + | |
| 32 | +typedef struct StdOutWriterContext { | |
| 33 | + const AVClass *class; | |
| 34 | +} StdOutWriterContext; | |
| 35 | + | |
| 36 | +static const char *stdoutwriter_get_name(void *ctx) | |
| 37 | +{ | |
| 38 | + return WRITER_NAME; | |
| 39 | +} | |
| 40 | + | |
| 41 | +static const AVClass stdoutwriter_class = { | |
| 42 | + .class_name = WRITER_NAME, | |
| 43 | + .item_name = stdoutwriter_get_name, | |
| 44 | +}; | |
| 45 | + | |
| 46 | +static inline void stdout_w8(AVTextWriterContext *wctx, int b) | |
| 47 | +{ | |
| 48 | + printf("%c", b); | |
| 49 | +} | |
| 50 | + | |
| 51 | +static inline void stdout_put_str(AVTextWriterContext *wctx, const char *str) | |
| 52 | +{ | |
| 53 | + printf("%s", str); | |
| 54 | +} | |
| 55 | + | |
| 56 | +static inline void stdout_printf(AVTextWriterContext *wctx, const char *fmt, ...) | |
| 57 | +{ | |
| 58 | + va_list ap; | |
| 59 | + | |
| 60 | + va_start(ap, fmt); | |
| 61 | + vprintf(fmt, ap); | |
| 62 | + va_end(ap); | |
| 63 | +} | |
| 64 | + | |
| 65 | + | |
| 66 | +static const AVTextWriter avtextwriter_stdout = { | |
| 67 | + .name = WRITER_NAME, | |
| 68 | + .priv_size = sizeof(StdOutWriterContext), | |
| 69 | + .priv_class = &stdoutwriter_class, | |
| 70 | + .writer_put_str = stdout_put_str, | |
| 71 | + .writer_printf = stdout_printf, | |
| 72 | + .writer_w8 = stdout_w8 | |
| 73 | +}; | |
| 74 | + | |
| 75 | +int avtextwriter_create_stdout(AVTextWriterContext **pwctx) | |
| 76 | +{ | |
| 77 | + int ret; | |
| 78 | + | |
| 79 | + ret = avtextwriter_context_open(pwctx, &avtextwriter_stdout); | |
| 80 | + | |
| 81 | + return ret; | |
| 82 | +} | |