FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_compact.c
Date: 2026-10-08 18:39:17
Exec Total Coverage
Lines: 101 115 87.8%
Functions: 8 9 88.9%
Branches: 76 90 84.4%

Line Branch Exec Source
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 <inttypes.h>
22 #include <limits.h>
23 #include <stdarg.h>
24 #include <string.h>
25
26 #include "avtextformat.h"
27 #include "libavutil/avutil.h"
28 #include "libavutil/bprint.h"
29 #include "libavutil/error.h"
30 #include "libavutil/opt.h"
31 #include "tf_internal.h"
32
33
34 /* Compact output */
35
36 /**
37 * Apply C-language-like string escaping.
38 */
39 31672 static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
40 {
41 const char *p;
42
43
2/2
✓ Branch 0 taken 214998 times.
✓ Branch 1 taken 31672 times.
246670 for (p = src; *p; p++) {
44
2/6
✗ Branch 0 not taken.
✗ Branch 1 not taken.
✓ Branch 2 taken 264 times.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✓ Branch 5 taken 214734 times.
214998 switch (*p) {
45 ✗ case '\b': av_bprintf(dst, "%s", "\\b"); break;
46 ✗ case '\f': av_bprintf(dst, "%s", "\\f"); break;
47 264 case '\n': av_bprintf(dst, "%s", "\\n"); break;
48 ✗ case '\r': av_bprintf(dst, "%s", "\\r"); break;
49 ✗ case '\\': av_bprintf(dst, "%s", "\\\\"); break;
50 214734 default:
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 214734 times.
214734 if (*p == sep)
52 ✗ av_bprint_chars(dst, '\\', 1);
53 214734 av_bprint_chars(dst, *p, 1);
54 }
55 }
56 31672 return dst->str;
57 }
58
59 /**
60 * Quote fields containing special characters, check RFC4180.
61 */
62 578 static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
63 {
64 578 char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
65
66 578 int needs_quoting = !!src[strcspn(src, meta_chars)];
67
68
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 577 times.
578 if (needs_quoting)
69 1 av_bprint_chars(dst, '"', 1);
70
71
2/2
✓ Branch 0 taken 3962 times.
✓ Branch 1 taken 578 times.
4540 for (; *src; src++) {
72
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 3960 times.
3962 if (*src == '"')
73 2 av_bprint_chars(dst, '"', 1);
74 3962 av_bprint_chars(dst, *src, 1);
75 }
76
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 577 times.
578 if (needs_quoting)
77 1 av_bprint_chars(dst, '"', 1);
78 578 return dst->str;
79 }
80
81 ✗ static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
82 {
83 ✗ return src;
84 }
85
86 typedef struct CompactContext {
87 const AVClass *class;
88 char *item_sep_str;
89 char item_sep;
90 int nokey;
91 int print_section;
92 char *escape_mode_str;
93 const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
94 int nested_section[SECTION_MAX_NB_LEVELS];
95 int has_nested_elems[SECTION_MAX_NB_LEVELS];
96 int terminate_line[SECTION_MAX_NB_LEVELS];
97 } CompactContext;
98
99 #undef OFFSET
100 #define OFFSET(x) offsetof(CompactContext, x)
101
102 static const AVOption compact_options[] = {
103 { "item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "|" }, 0, 0 },
104 { "s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "|" }, 0, 0 },
105 { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 },
106 { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 },
107 { "escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "c" }, 0, 0 },
108 { "e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "c" }, 0, 0 },
109 { "print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
110 { "p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
111 { NULL },
112 };
113
114 DEFINE_FORMATTER_CLASS(compact);
115
116 62 static av_cold int compact_init(AVTextFormatContext *wctx)
117 {
118 62 CompactContext *compact = wctx->priv;
119
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (strlen(compact->item_sep_str) != 1) {
121 ✗ av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
122 compact->item_sep_str);
123 ✗ return AVERROR(EINVAL);
124 }
125 62 compact->item_sep = compact->item_sep_str[0];
126
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 62 times.
62 if (!strcmp(compact->escape_mode_str, "none")) {
128 ✗ compact->escape_str = none_escape_str;
129
2/2
✓ Branch 0 taken 58 times.
✓ Branch 1 taken 4 times.
62 } else if (!strcmp(compact->escape_mode_str, "c" )) {
130 58 compact->escape_str = c_escape_str;
131
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (!strcmp(compact->escape_mode_str, "csv" )) {
132 4 compact->escape_str = csv_escape_str;
133 } else {
134 ✗ av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
135 ✗ return AVERROR(EINVAL);
136 }
137
138 62 return 0;
139 }
140
141 925 static int array_has_inline_elems(const AVTextFormatContext *wctx,
142 const AVTextFormatSection *section)
143 {
144
2/2
✓ Branch 0 taken 925 times.
✓ Branch 1 taken 6 times.
931 for (int i = 0; section->children_ids[i] != -1; i++)
145
2/2
✓ Branch 0 taken 919 times.
✓ Branch 1 taken 6 times.
925 if (wctx->sections[section->children_ids[i]].flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE)
146 919 return 1;
147 6 return 0;
148 }
149
150 10883 static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
151 {
152 10883 CompactContext *compact = wctx->priv;
153 10883 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
154 10883 const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
155
156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10883 times.
10883 if (!section)
157 ✗ return;
158
159 10883 compact->terminate_line[wctx->level] = 1;
160 10883 compact->has_nested_elems[wctx->level] = 0;
161
162 10883 av_bprint_clear(&wctx->section_pbuf[wctx->level]);
163
2/2
✓ Branch 0 taken 10821 times.
✓ Branch 1 taken 62 times.
10883 if (parent_section &&
164
2/2
✓ Branch 0 taken 9839 times.
✓ Branch 1 taken 982 times.
10821 (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
165
2/2
✓ Branch 0 taken 8823 times.
✓ Branch 1 taken 1016 times.
9839 (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
166
2/2
✓ Branch 0 taken 516 times.
✓ Branch 1 taken 8307 times.
8823 !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
167
168 /* define a prefix for elements not contained in an array or
169 in a wrapper, or for array elements with a type */
170 1498 const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
171 1498 AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
172
173 1498 compact->nested_section[wctx->level] = 1;
174 1498 compact->has_nested_elems[wctx->level - 1] = 1;
175
176 1498 av_bprintf(section_pbuf, "%s%s",
177 1498 wctx->section_pbuf[wctx->level - 1].str, element_name);
178
179
2/2
✓ Branch 0 taken 982 times.
✓ Branch 1 taken 516 times.
1498 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
180 // add /TYPE to prefix
181 982 av_bprint_chars(section_pbuf, '/', 1);
182
183 // normalize section type, replace special characters and lower case
184
2/2
✓ Branch 1 taken 17467 times.
✓ Branch 2 taken 982 times.
18449 for (const char *p = section->get_type(data); *p; p++) {
185 2222 char c =
186
4/4
✓ Branch 0 taken 15381 times.
✓ Branch 1 taken 2086 times.
✓ Branch 2 taken 14979 times.
✓ Branch 3 taken 402 times.
17467 (*p >= '0' && *p <= '9') ||
187
3/4
✓ Branch 0 taken 7336 times.
✓ Branch 1 taken 9729 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7336 times.
17065 (*p >= 'a' && *p <= 'z') ||
188
4/4
✓ Branch 0 taken 7643 times.
✓ Branch 1 taken 2086 times.
✓ Branch 2 taken 7507 times.
✓ Branch 3 taken 136 times.
17467 (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
189 17467 av_bprint_chars(section_pbuf, c, 1);
190 }
191 }
192 1498 av_bprint_chars(section_pbuf, ':', 1);
193
194 1498 wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level - 1];
195 } else {
196
4/4
✓ Branch 0 taken 9323 times.
✓ Branch 1 taken 62 times.
✓ Branch 2 taken 925 times.
✓ Branch 3 taken 8398 times.
9385 if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) {
197 925 int *parent_open = &compact->terminate_line[wctx->level - 1];
198
3/4
✓ Branch 0 taken 925 times.
✗ Branch 1 not taken.
✓ Branch 3 taken 6 times.
✓ Branch 4 taken 919 times.
925 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY && !array_has_inline_elems(wctx, section)) {
199 /* Row elements end the parent's line, its footer must not do it again. */
200
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (*parent_open)
201 6 writer_w8(wctx, '\n');
202 6 *parent_open = 0;
203
3/4
✓ Branch 0 taken 919 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 916 times.
✓ Branch 3 taken 3 times.
919 } else if (wctx->level && wctx->nb_item[wctx->level - 1]) {
204 916 writer_w8(wctx, compact->item_sep);
205 916 *parent_open = 1;
206 }
207 }
208
2/2
✓ Branch 0 taken 8031 times.
✓ Branch 1 taken 1354 times.
9385 if (compact->print_section &&
209
2/2
✓ Branch 0 taken 7207 times.
✓ Branch 1 taken 824 times.
8031 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
210 7207 writer_printf(wctx, "%s%c", section->name, compact->item_sep);
211 }
212 }
213
214 10883 static void compact_print_section_footer(AVTextFormatContext *wctx)
215 {
216 10883 CompactContext *compact = wctx->priv;
217 10883 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
218
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10883 times.
10883 if (!section)
220 ✗ return;
221
222
2/2
✓ Branch 0 taken 9332 times.
✓ Branch 1 taken 1551 times.
10883 if (!compact->nested_section[wctx->level] &&
223
2/2
✓ Branch 0 taken 9326 times.
✓ Branch 1 taken 6 times.
9332 compact->terminate_line[wctx->level] &&
224
2/2
✓ Branch 0 taken 8301 times.
✓ Branch 1 taken 1025 times.
9326 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
225 8301 writer_w8(wctx, '\n');
226 }
227
228 32250 static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
229 {
230 32250 CompactContext *compact = wctx->priv;
231 AVBPrint buf;
232
233
2/2
✓ Branch 0 taken 28206 times.
✓ Branch 1 taken 4044 times.
32250 if (wctx->nb_item[wctx->level])
234 28206 writer_w8(wctx, compact->item_sep);
235
236
2/2
✓ Branch 0 taken 28479 times.
✓ Branch 1 taken 3771 times.
32250 if (!compact->nokey)
237 28479 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
238
239 32250 av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
240 32250 writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
241 32250 av_bprint_finalize(&buf, NULL);
242 32250 }
243
244 30795 static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
245 {
246 30795 CompactContext *compact = wctx->priv;
247
248
2/2
✓ Branch 0 taken 25651 times.
✓ Branch 1 taken 5144 times.
30795 if (wctx->nb_item[wctx->level])
249 25651 writer_w8(wctx, compact->item_sep);
250
251
2/2
✓ Branch 0 taken 28416 times.
✓ Branch 1 taken 2379 times.
30795 if (!compact->nokey)
252 28416 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
253
254 30795 writer_printf(wctx, "%"PRId64, value);
255 30795 }
256
257 const AVTextFormatter avtextformatter_compact = {
258 .name = "compact",
259 .priv_size = sizeof(CompactContext),
260 .init = compact_init,
261 .print_section_header = compact_print_section_header,
262 .print_section_footer = compact_print_section_footer,
263 .print_integer = compact_print_int,
264 .print_string = compact_print_str,
265 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
266 .priv_class = &compact_class,
267 };
268
269 /* CSV output */
270
271 #undef OFFSET
272 #define OFFSET(x) offsetof(CompactContext, x)
273
274 static const AVOption csv_options[] = {
275 { "item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
276 { "s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
277 { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
278 { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
279 { "escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
280 { "e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
281 { "print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
282 { "p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
283 { NULL },
284 };
285
286 DEFINE_FORMATTER_CLASS(csv);
287
288 const AVTextFormatter avtextformatter_csv = {
289 .name = "csv",
290 .priv_size = sizeof(CompactContext),
291 .init = compact_init,
292 .print_section_header = compact_print_section_header,
293 .print_section_footer = compact_print_section_footer,
294 .print_integer = compact_print_int,
295 .print_string = compact_print_str,
296 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
297 .priv_class = &csv_class,
298 };
299