FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_compact.c
Date: 2026-10-19 08:11:58
Exec Total Coverage
Lines: 111 127 87.4%
Functions: 10 11 90.9%
Branches: 66 80 82.5%

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 32271 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 219481 times.
✓ Branch 1 taken 32271 times.
251752 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 219217 times.
219481 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 219217 default:
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 219217 times.
219217 if (*p == sep)
52 ✗ av_bprint_chars(dst, '\\', 1);
53 219217 av_bprint_chars(dst, *p, 1);
54 }
55 }
56 32271 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 /** level of the row whose line the fields at this level belong to, -1 for none */
95 int row_level[SECTION_MAX_NB_LEVELS];
96 /** number of fields written to the row at this level */
97 int nb_fields[SECTION_MAX_NB_LEVELS];
98 /** the line of the row at this level */
99 AVBPrint line[SECTION_MAX_NB_LEVELS];
100 /** the lines of the rows nested in the row at this level */
101 AVBPrint rows[SECTION_MAX_NB_LEVELS];
102 } CompactContext;
103
104 #undef OFFSET
105 #define OFFSET(x) offsetof(CompactContext, x)
106
107 static const AVOption compact_options[] = {
108 { "item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "|" }, 0, 0 },
109 { "s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "|" }, 0, 0 },
110 { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 },
111 { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1 },
112 { "escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "c" }, 0, 0 },
113 { "e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "c" }, 0, 0 },
114 { "print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
115 { "p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
116 { NULL },
117 };
118
119 DEFINE_FORMATTER_CLASS(compact);
120
121 68 static av_cold int compact_init(AVTextFormatContext *wctx)
122 {
123 68 CompactContext *compact = wctx->priv;
124
125
2/2
✓ Branch 0 taken 816 times.
✓ Branch 1 taken 68 times.
884 for (int i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
126 816 av_bprint_init(&compact->line[i], 1, AV_BPRINT_SIZE_UNLIMITED);
127 816 av_bprint_init(&compact->rows[i], 1, AV_BPRINT_SIZE_UNLIMITED);
128 }
129
130
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (strlen(compact->item_sep_str) != 1) {
131 ✗ av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
132 compact->item_sep_str);
133 ✗ return AVERROR(EINVAL);
134 }
135 68 compact->item_sep = compact->item_sep_str[0];
136
137
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 68 times.
68 if (!strcmp(compact->escape_mode_str, "none")) {
138 ✗ compact->escape_str = none_escape_str;
139
2/2
✓ Branch 0 taken 64 times.
✓ Branch 1 taken 4 times.
68 } else if (!strcmp(compact->escape_mode_str, "c" )) {
140 64 compact->escape_str = c_escape_str;
141
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 } else if (!strcmp(compact->escape_mode_str, "csv" )) {
142 4 compact->escape_str = csv_escape_str;
143 } else {
144 ✗ av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
145 ✗ return AVERROR(EINVAL);
146 }
147
148 68 return 0;
149 }
150
151 68 static av_cold int compact_uninit(AVTextFormatContext *wctx)
152 {
153 68 CompactContext *compact = wctx->priv;
154
155
2/2
✓ Branch 0 taken 816 times.
✓ Branch 1 taken 68 times.
884 for (int i = 0; i < SECTION_MAX_NB_LEVELS; i++) {
156 816 av_bprint_finalize(&compact->line[i], NULL);
157 816 av_bprint_finalize(&compact->rows[i], NULL);
158 }
159
160 68 return 0;
161 }
162
163 8425 static void bprint_append_bprint(AVBPrint *dst, const AVBPrint *src)
164 {
165 8425 av_bprint_append_data(dst, src->str, FFMIN(src->len, src->size - 1));
166 8425 }
167
168 11128 static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
169 {
170 11128 CompactContext *compact = wctx->priv;
171 11128 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
172 11128 const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
173 11128 int level = wctx->level;
174
175
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 11128 times.
11128 if (!section)
176 ✗ return;
177
178 11128 av_bprint_clear(&wctx->section_pbuf[level]);
179
2/2
✓ Branch 0 taken 11060 times.
✓ Branch 1 taken 68 times.
11128 compact->row_level[level] = level ? compact->row_level[level - 1] : -1;
180
181
2/2
✓ Branch 0 taken 11060 times.
✓ Branch 1 taken 68 times.
11128 if (parent_section &&
182
2/2
✓ Branch 0 taken 10030 times.
✓ Branch 1 taken 1030 times.
11060 (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
183
2/2
✓ Branch 0 taken 8928 times.
✓ Branch 1 taken 1102 times.
10030 (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
184
2/2
✓ Branch 0 taken 539 times.
✓ Branch 1 taken 8389 times.
8928 !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
185
186 /* define a prefix for elements not contained in an array or
187 in a wrapper, or for array elements with a type, their
188 fields continue the line of the enclosing row */
189 1569 const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
190 1569 AVBPrint *section_pbuf = &wctx->section_pbuf[level];
191
192 1569 av_bprintf(section_pbuf, "%s%s",
193 1569 wctx->section_pbuf[level - 1].str, element_name);
194
195
2/2
✓ Branch 0 taken 1030 times.
✓ Branch 1 taken 539 times.
1569 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
196 // add /TYPE to prefix
197 1030 av_bprint_chars(section_pbuf, '/', 1);
198
199 // normalize section type, replace special characters and lower case
200
2/2
✓ Branch 1 taken 18239 times.
✓ Branch 2 taken 1030 times.
19269 for (const char *p = section->get_type(data); *p; p++) {
201 2321 char c =
202
4/4
✓ Branch 0 taken 16060 times.
✓ Branch 1 taken 2179 times.
✓ Branch 2 taken 15648 times.
✓ Branch 3 taken 412 times.
18239 (*p >= '0' && *p <= '9') ||
203
3/4
✓ Branch 0 taken 7673 times.
✓ Branch 1 taken 10154 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7673 times.
17827 (*p >= 'a' && *p <= 'z') ||
204
4/4
✓ Branch 0 taken 7975 times.
✓ Branch 1 taken 2179 times.
✓ Branch 2 taken 7833 times.
✓ Branch 3 taken 142 times.
18239 (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
205 18239 av_bprint_chars(section_pbuf, c, 1);
206 }
207 }
208 1569 av_bprint_chars(section_pbuf, ':', 1);
209 1569 return;
210 }
211
212
2/2
✓ Branch 0 taken 1170 times.
✓ Branch 1 taken 8389 times.
9559 if (section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))
213 1170 return;
214
215 /* a row, printed as one line once the section ends */
216 8389 compact->row_level[level] = level;
217 8389 compact->nb_fields[level] = 0;
218 8389 av_bprint_clear(&compact->line[level]);
219 8389 av_bprint_clear(&compact->rows[level]);
220
2/2
✓ Branch 0 taken 7289 times.
✓ Branch 1 taken 1100 times.
8389 if (compact->print_section)
221 7289 av_bprintf(&compact->line[level], "%s", section->name);
222 }
223
224 11128 static void compact_print_section_footer(AVTextFormatContext *wctx)
225 {
226 11128 CompactContext *compact = wctx->priv;
227 11128 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
228 11128 int level = wctx->level;
229 int parent_row;
230
231
3/4
✓ Branch 0 taken 11128 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 2739 times.
✓ Branch 3 taken 8389 times.
11128 if (!section || compact->row_level[level] != level)
232 2739 return;
233
234 /* the row's line, followed by the lines of the rows nested in it */
235 8389 av_bprint_chars(&compact->line[level], '\n', 1);
236 8389 bprint_append_bprint(&compact->line[level], &compact->rows[level]);
237
238
1/2
✓ Branch 0 taken 8389 times.
✗ Branch 1 not taken.
8389 parent_row = level ? compact->row_level[level - 1] : -1;
239
2/2
✓ Branch 0 taken 36 times.
✓ Branch 1 taken 8353 times.
8389 if (parent_row >= 0)
240 36 bprint_append_bprint(&compact->rows[parent_row], &compact->line[level]);
241 else
242 8353 writer_put_str(wctx, compact->line[level].str);
243 }
244
245 64198 static void compact_print_field(AVTextFormatContext *wctx, const char *key, const char *value)
246 {
247 64198 CompactContext *compact = wctx->priv;
248 64198 int row = compact->row_level[wctx->level];
249 64198 AVBPrint tmp, *line = &tmp;
250
251
1/2
✓ Branch 0 taken 64198 times.
✗ Branch 1 not taken.
64198 if (row >= 0) {
252 64198 line = &compact->line[row];
253
4/4
✓ Branch 0 taken 8383 times.
✓ Branch 1 taken 55815 times.
✓ Branch 2 taken 7283 times.
✓ Branch 3 taken 1100 times.
64198 if (compact->nb_fields[row]++ || compact->print_section)
254 63098 av_bprint_chars(line, compact->item_sep, 1);
255 } else {
256 /* a field outside of any row */
257 ✗ av_bprint_init(&tmp, 1, AV_BPRINT_SIZE_UNLIMITED);
258 }
259
260
2/2
✓ Branch 0 taken 58048 times.
✓ Branch 1 taken 6150 times.
64198 if (!compact->nokey)
261 58048 av_bprintf(line, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
262 64198 av_bprintf(line, "%s", value);
263
264
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 64198 times.
64198 if (line == &tmp) {
265 ✗ writer_put_str(wctx, tmp.str);
266 ✗ av_bprint_finalize(&tmp, NULL);
267 }
268 64198 }
269
270 32849 static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
271 {
272 32849 CompactContext *compact = wctx->priv;
273 AVBPrint buf;
274
275 32849 av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
276 32849 compact_print_field(wctx, key, compact->escape_str(&buf, value, compact->item_sep, wctx));
277 32849 av_bprint_finalize(&buf, NULL);
278 32849 }
279
280 31349 static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
281 {
282 char buf[32];
283
284 31349 snprintf(buf, sizeof(buf), "%"PRId64, value);
285 31349 compact_print_field(wctx, key, buf);
286 31349 }
287
288 const AVTextFormatter avtextformatter_compact = {
289 .name = "compact",
290 .priv_size = sizeof(CompactContext),
291 .init = compact_init,
292 .uninit = compact_uninit,
293 .print_section_header = compact_print_section_header,
294 .print_section_footer = compact_print_section_footer,
295 .print_integer = compact_print_int,
296 .print_string = compact_print_str,
297 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
298 .priv_class = &compact_class,
299 };
300
301 /* CSV output */
302
303 #undef OFFSET
304 #define OFFSET(x) offsetof(CompactContext, x)
305
306 static const AVOption csv_options[] = {
307 { "item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
308 { "s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
309 { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
310 { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
311 { "escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
312 { "e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
313 { "print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
314 { "p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
315 { NULL },
316 };
317
318 DEFINE_FORMATTER_CLASS(csv);
319
320 const AVTextFormatter avtextformatter_csv = {
321 .name = "csv",
322 .priv_size = sizeof(CompactContext),
323 .init = compact_init,
324 .uninit = compact_uninit,
325 .print_section_header = compact_print_section_header,
326 .print_section_footer = compact_print_section_footer,
327 .print_integer = compact_print_int,
328 .print_string = compact_print_str,
329 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
330 .priv_class = &csv_class,
331 };
332