FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_compact.c
Date: 2026-09-12 04:54:02
Exec Total Coverage
Lines: 97 115 84.3%
Functions: 8 9 88.9%
Branches: 70 90 77.8%

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 31049 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 210712 times.
✓ Branch 1 taken 31049 times.
241761 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 210448 times.
210712 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 210448 default:
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 210448 times.
210448 if (*p == sep)
52 av_bprint_chars(dst, '\\', 1);
53 210448 av_bprint_chars(dst, *p, 1);
54 }
55 }
56 31049 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 53 static av_cold int compact_init(AVTextFormatContext *wctx)
117 {
118 53 CompactContext *compact = wctx->priv;
119
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 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 53 compact->item_sep = compact->item_sep_str[0];
126
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 53 times.
53 if (!strcmp(compact->escape_mode_str, "none")) {
128 compact->escape_str = none_escape_str;
129
2/2
✓ Branch 0 taken 49 times.
✓ Branch 1 taken 4 times.
53 } else if (!strcmp(compact->escape_mode_str, "c" )) {
130 49 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 53 return 0;
139 }
140
141 905 static int array_has_inline_elems(const AVTextFormatContext *wctx,
142 const AVTextFormatSection *section)
143 {
144
1/2
✓ Branch 0 taken 905 times.
✗ Branch 1 not taken.
905 for (int i = 0; section->children_ids[i] != -1; i++)
145
1/2
✓ Branch 0 taken 905 times.
✗ Branch 1 not taken.
905 if (wctx->sections[section->children_ids[i]].flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE)
146 905 return 1;
147 return 0;
148 }
149
150 10714 static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
151 {
152 10714 CompactContext *compact = wctx->priv;
153 10714 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
154 10714 const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
155
156
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10714 times.
10714 if (!section)
157 return;
158
159 10714 compact->terminate_line[wctx->level] = 1;
160 10714 compact->has_nested_elems[wctx->level] = 0;
161
162 10714 av_bprint_clear(&wctx->section_pbuf[wctx->level]);
163
2/2
✓ Branch 0 taken 10661 times.
✓ Branch 1 taken 53 times.
10714 if (parent_section &&
164
2/2
✓ Branch 0 taken 9690 times.
✓ Branch 1 taken 971 times.
10661 (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
165
2/2
✓ Branch 0 taken 8686 times.
✓ Branch 1 taken 1004 times.
9690 (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
166
2/2
✓ Branch 0 taken 510 times.
✓ Branch 1 taken 8176 times.
8686 !(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 1481 const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
171 1481 AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
172
173 1481 compact->nested_section[wctx->level] = 1;
174 1481 compact->has_nested_elems[wctx->level - 1] = 1;
175
176 1481 av_bprintf(section_pbuf, "%s%s",
177 1481 wctx->section_pbuf[wctx->level - 1].str, element_name);
178
179
2/2
✓ Branch 0 taken 971 times.
✓ Branch 1 taken 510 times.
1481 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
180 // add /TYPE to prefix
181 971 av_bprint_chars(section_pbuf, '/', 1);
182
183 // normalize section type, replace special characters and lower case
184
2/2
✓ Branch 1 taken 17286 times.
✓ Branch 2 taken 971 times.
18257 for (const char *p = section->get_type(data); *p; p++) {
185 2204 char c =
186
4/4
✓ Branch 0 taken 15216 times.
✓ Branch 1 taken 2070 times.
✓ Branch 2 taken 14818 times.
✓ Branch 3 taken 398 times.
17286 (*p >= '0' && *p <= '9') ||
187
3/4
✓ Branch 0 taken 7205 times.
✓ Branch 1 taken 9683 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7205 times.
16888 (*p >= 'a' && *p <= 'z') ||
188
4/4
✓ Branch 0 taken 7613 times.
✓ Branch 1 taken 2070 times.
✓ Branch 2 taken 7479 times.
✓ Branch 3 taken 134 times.
17286 (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
189 17286 av_bprint_chars(section_pbuf, c, 1);
190 }
191 }
192 1481 av_bprint_chars(section_pbuf, ':', 1);
193
194 1481 wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level - 1];
195 } else {
196
4/4
✓ Branch 0 taken 9180 times.
✓ Branch 1 taken 53 times.
✓ Branch 2 taken 905 times.
✓ Branch 3 taken 8275 times.
9233 if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))) {
197 905 int *parent_open = &compact->terminate_line[wctx->level - 1];
198
2/4
✓ Branch 0 taken 905 times.
✗ Branch 1 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 905 times.
905 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 if (*parent_open)
201 writer_w8(wctx, '\n');
202 *parent_open = 0;
203
2/4
✓ Branch 0 taken 905 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 905 times.
✗ Branch 3 not taken.
905 } else if (wctx->level && wctx->nb_item[wctx->level - 1]) {
204 905 writer_w8(wctx, compact->item_sep);
205 905 *parent_open = 1;
206 }
207 }
208
2/2
✓ Branch 0 taken 7958 times.
✓ Branch 1 taken 1275 times.
9233 if (compact->print_section &&
209
2/2
✓ Branch 0 taken 7152 times.
✓ Branch 1 taken 806 times.
7958 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
210 7152 writer_printf(wctx, "%s%c", section->name, compact->item_sep);
211 }
212 }
213
214 10714 static void compact_print_section_footer(AVTextFormatContext *wctx)
215 {
216 10714 CompactContext *compact = wctx->priv;
217 10714 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
218
219
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10714 times.
10714 if (!section)
220 return;
221
222
2/2
✓ Branch 0 taken 9181 times.
✓ Branch 1 taken 1533 times.
10714 if (!compact->nested_section[wctx->level] &&
223
1/2
✓ Branch 0 taken 9181 times.
✗ Branch 1 not taken.
9181 compact->terminate_line[wctx->level] &&
224
2/2
✓ Branch 0 taken 8176 times.
✓ Branch 1 taken 1005 times.
9181 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
225 8176 writer_w8(wctx, '\n');
226 }
227
228 31627 static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
229 {
230 31627 CompactContext *compact = wctx->priv;
231 AVBPrint buf;
232
233
2/2
✓ Branch 0 taken 27671 times.
✓ Branch 1 taken 3956 times.
31627 if (wctx->nb_item[wctx->level])
234 27671 writer_w8(wctx, compact->item_sep);
235
236
2/2
✓ Branch 0 taken 28438 times.
✓ Branch 1 taken 3189 times.
31627 if (!compact->nokey)
237 28438 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
238
239 31627 av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
240 31627 writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
241 31627 av_bprint_finalize(&buf, NULL);
242 31627 }
243
244 30351 static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
245 {
246 30351 CompactContext *compact = wctx->priv;
247
248
2/2
✓ Branch 0 taken 25247 times.
✓ Branch 1 taken 5104 times.
30351 if (wctx->nb_item[wctx->level])
249 25247 writer_w8(wctx, compact->item_sep);
250
251
2/2
✓ Branch 0 taken 28328 times.
✓ Branch 1 taken 2023 times.
30351 if (!compact->nokey)
252 28328 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
253
254 30351 writer_printf(wctx, "%"PRId64, value);
255 30351 }
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