From: Nicolas George <george@nsup.org> To: ffmpeg-devel@ffmpeg.org Subject: [FFmpeg-devel] [PATCH 5/8] lavu: add a JSON writer API (WIP) Date: Fri, 28 Apr 2023 11:55:05 +0200 Message-ID: <20230428095508.221826-5-george@nsup.org> (raw) In-Reply-To: <20230428095508.221826-1-george@nsup.org> Signed-off-by: Nicolas George <george@nsup.org> --- libavutil/Makefile | 1 + libavutil/json.c | 368 +++++++++++++++++++++++++++++++++++ libavutil/json.h | 470 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 839 insertions(+) create mode 100644 libavutil/json.c create mode 100644 libavutil/json.h diff --git a/libavutil/Makefile b/libavutil/Makefile index 4526ec80ca..a8a9700778 100644 --- a/libavutil/Makefile +++ b/libavutil/Makefile @@ -140,6 +140,7 @@ OBJS = adler32.o \ imgutils.o \ integer.o \ intmath.o \ + json.o \ lfg.o \ lls.o \ log.o \ diff --git a/libavutil/json.c b/libavutil/json.c new file mode 100644 index 0000000000..7ee68aaa4a --- /dev/null +++ b/libavutil/json.c @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2021 Nicolas George + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "avassert.h" +#include "common.h" +#include "json.h" +#include "opt.h" + +#define FIELDOK(st, f) ((char *)(&(st)->f + 1) <= (char *)(st) + (st)->self_size) + +#define json_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) +#define json_escape_writer_assert_abi(jwr) av_assert1(FIELDOK(jwr, padding)) + +AVClass *av_json_get_class(void) +{ + return NULL; +} + +int av_json_alloc(AVJson **jc, unsigned max_depth) +{ + return AVERROR_BUG; +} + +void av_json_free(AVJson **jc) +{ +} + +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr) +{ + json_assert_abi(jc); + json_escape_writer_assert_abi(jwr); + jc->av_class = av_json_get_class(); + jc->escape_writer = jwr; + av_opt_set_defaults(jc); + return jc; +} + +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options) +{ + jc->out = wr; + jc->flags = flags; + jc->in_string = 0; + jc->in_object = 0; + jc->first_element = 1; + jc->depth = 0; + jc->stack = NULL; +} + +static inline int check_value_must_be_string(AVJson *jc, int string) +{ + return !jc->object_key || string; +} + +static inline int check_stack_clean(AVJsonStack *stack) +{ + return !stack->prev; +} + +static inline int check_begin_end_balanced(AVJson *jc, int obj) +{ + return jc->in_object == obj && jc->stack; +} + +static inline int flag_pretty_print(AVJson *jc) +{ + return !!(jc->flags & AV_JSON_FLAG_PRETTY_PRINT); +} + +static void end_value(AVJson *jc) +{ + if (!jc->stack && flag_pretty_print(jc)) + av_writer_print(jc->out, "\n"); +} + +static void auto_end_string(AVJson *jc) +{ + if (jc->in_string) { + av_writer_print(jc->out, "\""); + jc->in_string = 0; + end_value(jc); + } +} + +static void auto_add_separator(AVJson *jc) +{ + int indent = flag_pretty_print(jc); + + auto_end_string(jc); + if (!jc->first_element) { + if (jc->object_key) { + av_writer_print(jc->out, flag_pretty_print(jc) ? " : " : ":"); + indent = jc->object_key = 0; + } else { + av_writer_print(jc->out, ","); + jc->object_key = jc->in_object; + } + } + if (indent) { + if (jc->stack) + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->first_element = 0; +} + +static void begin_value(AVJson *jc, int string) +{ + auto_add_separator(jc); + av_assert1(check_value_must_be_string(jc, string)); +} + +static void begin_compound(AVJson *jc, AVJsonStack *stack, unsigned obj) +{ + av_assert1(FIELDOK(stack, in_object)); + av_assert1(check_stack_clean(stack)); + stack->prev = jc->stack; + stack->in_object = jc->in_object; + jc->stack = stack; + jc->first_element = 1; + jc->in_object = jc->object_key = obj; + jc->depth++; +} + +static void end_compound(AVJson *jc, unsigned obj) +{ + AVJsonStack *stack = jc->stack; + + av_assert1(check_begin_end_balanced(jc, obj)); + auto_end_string(jc); + jc->depth--; + if (!jc->first_element && flag_pretty_print(jc)) { + av_writer_print(jc->out, "\n"); + av_writer_add_chars(jc->out, ' ', 3 * jc->depth); + } + jc->in_object = stack->in_object; + jc->object_key = 0; + jc->first_element = 0; + jc->stack = stack->prev; + stack->prev = NULL; +} + +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 1); + av_writer_print(jc->out, "{"); +} + +void av_json_end_object(AVJson *jc) +{ + end_compound(jc, 1); + av_writer_print(jc->out, "}"); + end_value(jc); +} + +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack) +{ + begin_value(jc, 0); + begin_compound(jc, stack, 0); + av_writer_print(jc->out, "["); +} + +void av_json_end_array(AVJson *jc) +{ + end_compound(jc, 0); + av_writer_print(jc->out, "]"); + end_value(jc); +} + +AVWriter av_json_begin_string(AVJson *jc) +{ + begin_value(jc, 1); + jc->in_string = 1; + av_writer_print(jc->out, "\""); + av_json_escape_writer_init(jc->escape_writer, jc->out, jc->flags); + return av_json_escape_writer_wrap(jc->escape_writer); +} + +void av_json_end_string(AVJson *jc) +{ + av_assert1(jc->in_string); + auto_end_string(jc); +} + +void av_json_add_string(AVJson *jc, const char *str) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_print(wr, str); +} + +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + AVWriter wr = av_json_begin_string(jc); + av_writer_vprintf(wr, fmt, va); + av_json_end_string(jc); +} + +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_string_vprintf(jc, fmt, va); + va_end(va); +} + +void av_json_add_int(AVJson *jc, intmax_t val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%jd", val); + end_value(jc); +} + +void av_json_add_double(AVJson *jc, double val) +{ + begin_value(jc, 0); + av_writer_printf(jc->out, "%.15g", val); + end_value(jc); +} + +void av_json_add_bool(AVJson *jc, int val) +{ + begin_value(jc, 0); + av_writer_print(jc->out, val ? "true" : "false"); + end_value(jc); +} + +void av_json_add_null(AVJson *jc) +{ + begin_value(jc, 0); + av_writer_print(jc->out, "null"); + end_value(jc); +} + +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va) +{ + begin_value(jc, 1); + av_writer_vprintf(jc->out, fmt, va); + end_value(jc); +} + +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) +{ + va_list va; + va_start(va, fmt); + av_json_add_raw_vprintf(jc, fmt, va); + va_end(va); +} + +/*************************************************************************** + * AVJsonEscapeWriter - escpae JSON strings + ***************************************************************************/ + +static void json_escape_writer_write(AVWriter wr, const char *data, size_t size) +{ + AVJsonEscapeWriter *jwr = wr.obj; + const uint8_t *end = data + size; + const uint8_t *cur = data; + const uint8_t *written = data; + const uint8_t *raw = data; + unsigned char buf[13]; + const char *escape; + int32_t c; + int ret; + + av_assert1(av_json_escape_writer_check(wr)); + json_escape_writer_assert_abi(jwr); + if (jwr->error) + return; + while (cur < end) { + raw = cur; + ret = av_utf8_decode(&c, &cur, end, 0); + if (ret < 0) { + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_EXPLODE)) { + av_log(NULL, AV_LOG_PANIC, "Bad UTF-8 in JSON\n"); + abort(); + } + if ((jwr->flags & AV_JSON_FLAG_BAD_ENCODING_REPLACE)) { + c = 0xFFFD; + } else { + jwr->error = ret; + return; + } + } + av_assert1(c >= 0 && c <= 0x10FFFF); + if ((unsigned)(c - ' ') < 127 - ' ' && c != '"' && c != '\\') + continue; + if ((unsigned)(c - 0x0080) <= (0xFFFF - 0x0080)) /* TODO flag */ + continue; + if (c == '"') { + escape = "\\\""; + } else if (c == '\\') { + escape = "\\\\"; + } else if (c == '\n') { + escape = "\\n"; + } else if (c == '\r') { + escape = "\\r"; + } else if (c == '\t') { + escape = "\\t"; + } else if (c == '\f') { + escape = "\\f"; + } else if (c == '\b') { + escape = "\\b"; + } else { + if (c < 0x10000) + snprintf(buf, sizeof(buf), "\\u%04X", c); + else /* JSON sucks: UTF-16 */ + snprintf(buf, sizeof(buf), "\\u%04X\\u%04X", + 0xD800 + (((c - 0x10000) >> 10) & 0x3FF), + 0xDC00 + (((c - 0x10000) >> 0) & 0x3FF)); + escape = buf; + } + if (raw > written) + av_writer_write(jwr->owr, written, raw - written); + av_writer_print(jwr->owr, escape); + written = raw = cur; + } + if (cur > written) + av_writer_write(jwr->owr, written, cur - written); +} + +static int json_escape_writer_get_error(AVWriter wr, int self_only) +{ + AVJsonEscapeWriter *jwr = wr.obj; + + av_assert1(av_json_escape_writer_check(wr)); + return jwr->error ? jwr->error : + self_only ? 0 : av_writer_get_error(jwr->owr, 0); +} + +AV_WRITER_DEFINE_METHODS(/*public*/, AVJsonEscapeWriter, av_json_escape_writer) { + .self_size = sizeof(AVWriterMethods), + .name = "AVJsonEscapeWriter", + .write = json_escape_writer_write, + .get_error = json_escape_writer_get_error, +}; + +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags) +{ + json_escape_writer_assert_abi(jwr); + jwr->owr = owr; + jwr->flags = flags; + return jwr; +} + +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr) +{ + AVWriter r = { av_json_escape_writer_get_methods(), jwr }; + json_escape_writer_assert_abi(jwr); + return r; +} + diff --git a/libavutil/json.h b/libavutil/json.h new file mode 100644 index 0000000000..6cdcdc1348 --- /dev/null +++ b/libavutil/json.h @@ -0,0 +1,470 @@ +/* + * Copyright (c) 2023 The FFmpeg project + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVUTIL_JSON_H +#define AVUTIL_JSON_H + +#include <stddef.h> +#include <stdint.h> +#include "dict.h" +#include "extendable.h" +#include "log.h" +#include "writer.h" + +/** + * @defgroup av_json AVJson + * + * API to serialize data to JSON. + * + * API summary and quick HOWTO: + * + * AVWriter out = av_dynbuf_writer(); + * AVJson *jc = AV_JSON_DEFINE(); + * av_json_init(jc, out, AV_JSON_FLAG_PRETTY_PRINT, NULL); + * av_json_begin_object(jc); + * av_json_add_string("user"); + * av_json_add_string(user_name); + * av_json_add_string("score"); + * av_json_add_int("%d", score); + * av_json_end_object(jc); + * av_json_finish(jc); + * ret = av_writer_get_error(out, 0); + * if (ret < 0) ... + * data = av_dynbuf_writer_get_data(wr, &size); + * + * @{ + */ + +typedef struct AVJson AVJson; +typedef struct AVJsonStack AVJsonStack; +typedef struct AVJsonEscapeWriter AVJsonEscapeWriter; +typedef struct AVJsonValue AVJsonValue; +typedef enum AVJsonValueType AVJsonValueType; + +/** + * Produce ASCII output by escaping non-ASCII characters + */ +#define AV_JSON_FLAG_ASCII 0x0001 + +/** + * Abort if the input is not valid UTF-8. + */ +#define AV_JSON_FLAG_BAD_ENCODING_EXPLODE 0x0002 + +/** + * If the input is not valid UTF-8, replace the offending partis with a + * replacement character. + */ +#define AV_JSON_FLAG_BAD_ENCODING_REPLACE 0x0004 + +/** + * Pretty-print the output with spaces and indentation. + * XXX + */ +#define AV_JSON_FLAG_PRETTY_PRINT 0x0008 + +/** + * Consider strings to be pseudo-binary instead of UTF-8. + * + * Warning: without AV_JSON_FLAG_ASCII, it will probably produce + * non-standard JSON. + * XXX + */ +#define AV_JSON_FLAG_PSEUDO_BINARY 0x0010 + +/** + * Define a JSON context by allocating it as compound literals + * (hidden local variables). + * The context will be valid in the current scope and no further. + */ +#define AV_JSON_DEFINE(max_depth) \ + av_json_preinit(&FF_NEW_SZ(AVJson), &FF_NEW_SZ(AVJsonEscapeWriter)) + +/** + * Get the AVClass for a JSON context. + */ +AVClass *av_json_get_class(void); + +/** + * Pre-initialize a JSON context with its escape writer. + * @return jc itself + */ +AVJson *av_json_preinit(AVJson *jc, AVJsonEscapeWriter *jwr); + +/** + * Allocate a new JSON context. + * Only use this if AV_JSON_DEFINE() is not suitable. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_alloc(AVJson **jc, unsigned max_depth); + +/** + * Free a JSON context allocated with av_json_alloc(). + */ +void av_json_free(AVJson **jc); + +/** + * Initialize a JSON context with output, flags an options. + * This function can be called several times on the same context to reuse + * it. + */ +void av_json_init(AVJson *jc, AVWriter wr, unsigned flags, AVDictionary *options); + +/** + * Begin an object, i.e. a { key : value... } dictionary. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_object() for a more convenient version. + */ +void av_json_begin_object_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an object. + */ +void av_json_end_object(AVJson *jc); + +/** + * Begin an array, i.e. a [ value, value... ] list. + * The stack object must be allocated and inited, and valid until the + * corresponding av_json_end_object(). + * See av_json_begin_array() for a more convenient version. + */ +void av_json_begin_array_with_stack(AVJson *jc, AVJsonStack *stack); + +/** + * End an array. + */ +void av_json_end_array(AVJson *jc); + +/** + * Begin a string and a return an AVWriter to write its contents. + */ +AVWriter av_json_begin_string(AVJson *jc); + +/** + * End a string. Optional. + */ +void av_json_end_string(AVJson *jc); + +/** + * Add a string all at once. + */ +void av_json_add_string(AVJson *jc, const char *str); + +/** + * Add a string all at once from a format string and a va_list. + */ +void av_json_add_string_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add a sring all at once from a format string and arguments. + */ +void av_json_add_string_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Add an integer number. + */ +void av_json_add_int(AVJson *jc, intmax_t val); + +/** + * Add a floating-point number. + */ +void av_json_add_double(AVJson *jc, double val); + +/** + * Add a boolean value (true/false). + */ +void av_json_add_bool(AVJson *jc, int val); + +/** + * Add a null value. + */ +void av_json_add_null(AVJson *jc); + +/** + * Add an arbitrary value from a format string and a va_list. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_vprintf(AVJson *jc, const char *fmt, va_list va); + +/** + * Add an arbitrary value from a format string and arguments. + * Useful for adding a floating point number and controlling the format. + * Warning: the validity of the output cannot be guaranteed. + */ +void av_json_add_raw_printf(AVJson *jc, const char *fmt, ...) av_printf_format(2, 3); + +/** + * Define and init a stack element as a compound literal + * (hidden local variable). + * Using this directly is not recommended. + */ +#define AV_JSON_STACK() (&FF_NEW_SZ(AVJsonStack)) + +/** + * Allocate a stack element. + * Only use this if av_json_begin_object() / av_json_begin_array() cannot be + * used. + * @return 0 or an AVERROR code, including AVERROR(ENOMEM). + */ +int av_json_stack_alloc(AVJsonStack **stack); + +/** + * Free a stack element allocated with av_json_stack_alloc(). + */ +void av_json_stack_free(AVJsonStack **stack); + +/** + * Begin an object with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_object() must be in the same scope. + * After this, every other value must be a string. + * The behavior is undefined if a key is not a string. + */ +#define av_json_begin_object(jc) av_json_begin_object_with_stack((jc), AV_JSON_STACK()) + +/** + * Begin an array with a stack element as a compound literal + * (hidden local variable). + * The corresponding av_json_end_array() must be in the same scope. + */ +#define av_json_begin_array(jc) av_json_begin_array_with_stack((jc), AV_JSON_STACK()) + +/** + * An AVWriter object to escape JSON strings. + * + * Can be allocated on the stack. + * + * Should be inited with one of the utility functions. + */ +struct AVJsonEscapeWriter { + size_t self_size; /**< Size of the structure itself */ + AVWriter owr; /**< AVWriter to send the output */ + unsigned flags; /**< Escaping flags, see AV_JSON_FLAG_* */ + int error; /**< Error status */ + unsigned replacement_char; /**< Replacement character for bad UTF-8 */ + FFReservedPadding padding[2]; +}; + +/** + * Get the methods for a JSON escape writer. + * Probably not useful to use directly. + */ +const AVWriterMethods *av_json_escape_writer_get_methods(void); + +/** + * Check if a writer is a JSON escape writer. + */ +int av_json_escape_writer_check(AVWriter wr); + +/** + * Initialize an AVJsonEscapeWriter to an already-allocated memory buffer. + * + * @return jwr itself + * jwr->self_size must be set. + */ +AVJsonEscapeWriter *av_json_escape_writer_init(AVJsonEscapeWriter *jwr, AVWriter owr, unsigned flags); + +/** + * Create an AVWriter from an AVJsonEscapeWriter structure. + */ +AVWriter av_json_escape_writer_wrap(AVJsonEscapeWriter *jwr); + +/** + * Create an AVWriter to escape JSON strings. + * + * Note: as it relies on a compound statement, the AVJsonEscapeWriter object has + * a scope limited to the block where this macro is called. + */ +#define av_json_escape_writer(owr, flags) \ + av_json_escape_writer_wrap(av_json_escape_writer_init(&FF_NEW_SZ(AVJsonEscapeWriter), (owr), (flags))) + +/** + * JSON encoding context. + * + * This structure must be allocated with AV_JSON_DEFINE() or equivalent + * code that will set av_class, self_size and escape_writer. + * + * It then must be initialized using av_json_init(). + */ +struct AVJson { + + /** + * Class; must be initialized to av_json_get_class(). + */ + const AVClass *av_class; + + /** + * Size of the structure, must be initialized at allocation. + */ + size_t self_size; + + /** + * Encoding flags, see AV_JSON_FLAG_*. + */ + unsigned flags; + + /** + * Indentation shift. + */ + unsigned indentation; + + /** + * Output writer. + */ + AVWriter out; + + /**************************************************************** + * The fields below this limit are private. + ****************************************************************/ + + /** + * Stack of states (object/array) + */ + AVJsonStack *stack; + + /** + * Pre-allocated writer for escaping strings. + * + * Must be allocated before init. + */ + AVJsonEscapeWriter *escape_writer; + + /** + * Depth of nested structures, for indentation. + */ + unsigned depth; + + /** + * True if a string is being constructed. + */ + unsigned in_string : 1; + + /** + * True if we currently are directly in an object. + */ + unsigned in_object : 1; + + /** + * True if we are about to write the first element of a structure. + */ + unsigned first_element : 1; + + /** + * True if we are about to write the key in an object. + */ + unsigned object_key : 1; + + FFReservedPadding padding[8]; +}; + +/** + * Stack element for the JSON context. + */ +struct AVJsonStack { + size_t self_size; + AVJsonStack *prev; + unsigned short in_object; + FFReservedPadding padding; +}; + +/** + * Type of a JSON value. + * + * Note that JSON does not distinguish int and float values. + */ +enum AVJsonValueType { + AV_JSON_TYPE_NULL, + AV_JSON_TYPE_BOOL, + AV_JSON_TYPE_STRING, + AV_JSON_TYPE_INT, + AV_JSON_TYPE_DOUBLE, + AV_JSON_TYPE_OBJECT, + AV_JSON_TYPE_ARRAY, +}; + +/** + * Typed atomic JSON values. + * Objects and arrays cannot be represented. + * This structure is meant to be passed by value. + */ +struct AVJsonValue { + AVJsonValueType type; + union AVJsonValueValue { + intmax_t i; + double d; + const char *s; + } val; +}; + +/** + * Build an AVJsonValue for null. + */ +static inline AVJsonValue av_json_value_null(void) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_NULL }; + return ret; +} + +/** + * Build an AVJsonValue for a boolean. + */ +static inline AVJsonValue av_json_value_bool(int val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_BOOL, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for a string. + * The pointer must stay valid while the value is used. + */ +static inline AVJsonValue av_json_value_string(const char *val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_STRING, .val.s = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_int(intmax_t val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.i = val }; + return ret; +} + +/** + * Build an AVJsonValue for an integer. + */ +static inline AVJsonValue av_json_value_double(double val) +{ + AVJsonValue ret = { .type = AV_JSON_TYPE_INT, .val.d = val }; + return ret; +} + +/** + * @} + */ + +#endif /* AVUTIL_JSON_H */ -- 2.39.2 _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe".
next prev parent reply other threads:[~2023-04-28 9:56 UTC|newest] Thread overview: 38+ messages / expand[flat|nested] mbox.gz Atom feed top 2023-04-28 9:55 [FFmpeg-devel] [PATCH 1/8] lavu: add macros to help making future-proof structures Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 2/8] lavu: new AVWriter API Nicolas George 2023-04-28 10:37 ` Rodney Baker 2023-04-28 11:20 ` Nicolas George 2023-05-02 15:53 ` Rémi Denis-Courmont 2023-05-02 16:53 ` Nicolas George 2023-05-02 18:29 ` Rémi Denis-Courmont 2023-05-02 18:36 ` Nicolas George 2023-05-02 18:46 ` Rémi Denis-Courmont 2023-05-02 18:47 ` Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 3/8] lavu/writer: add test Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 4/8] lavf/dump: use a writer Nicolas George 2023-04-28 9:55 ` Nicolas George [this message] 2023-04-29 9:11 ` [FFmpeg-devel] [PATCH 5/8] lavu: add a JSON writer API (WIP) Nicolas George 2023-04-29 9:41 ` Anton Khirnov 2023-04-29 14:06 ` James Almer 2023-04-29 17:17 ` Nicolas George 2023-04-29 15:06 ` Derek Buitenhuis 2023-04-30 0:29 ` Kieran Kunhya 2023-05-01 6:20 ` Vittorio Giovara 2023-04-29 17:11 ` Nicolas George 2023-04-29 18:27 ` Anton Khirnov 2023-04-29 18:33 ` Nicolas George 2023-05-01 6:57 ` Leo Izen 2023-05-01 9:51 ` Nicolas George 2023-05-01 10:18 ` Jean-Baptiste Kempf 2023-04-30 15:06 ` Michael Niedermayer 2023-04-30 21:51 ` Kieran Kunhya 2023-05-01 9:46 ` Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 6/8] lavu: add JSON writer test (WIP) Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 7/8] lavf/options: add av_disposition_write() Nicolas George 2023-04-28 9:55 ` [FFmpeg-devel] [PATCH 8/8] lavf/dump: use av_disposition_write() Nicolas George 2023-04-29 8:17 ` [FFmpeg-devel] [PATCH 1/8] lavu: add macros to help making future-proof structures Anton Khirnov 2023-04-29 15:11 ` Derek Buitenhuis 2023-05-02 15:36 ` Rémi Denis-Courmont 2023-05-02 16:42 ` Nicolas George 2023-05-02 18:31 ` Rémi Denis-Courmont 2023-05-02 18:38 ` Nicolas George
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20230428095508.221826-5-george@nsup.org \ --to=george@nsup.org \ --cc=ffmpeg-devel@ffmpeg.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git