FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_compact.c
Date: 2025-06-01 09:29:47
Exec Total Coverage
Lines: 90 104 86.5%
Functions: 7 8 87.5%
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 <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/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 29890 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 203023 times.
✓ Branch 1 taken 29890 times.
232913 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 202759 times.
203023 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 202759 default:
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 202759 times.
202759 if (*p == sep)
52 av_bprint_chars(dst, '\\', 1);
53 202759 av_bprint_chars(dst, *p, 1);
54 }
55 }
56 29890 return dst->str;
57 }
58
59 /**
60 * Quote fields containing special characters, check RFC4180.
61 */
62 362 static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
63 {
64 362 char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
65
66 362 int needs_quoting = !!src[strcspn(src, meta_chars)];
67
68
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 361 times.
362 if (needs_quoting)
69 1 av_bprint_chars(dst, '"', 1);
70
71
2/2
✓ Branch 0 taken 2326 times.
✓ Branch 1 taken 362 times.
2688 for (; *src; src++) {
72
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2324 times.
2326 if (*src == '"')
73 2 av_bprint_chars(dst, '"', 1);
74 2326 av_bprint_chars(dst, *src, 1);
75 }
76
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 361 times.
362 if (needs_quoting)
77 1 av_bprint_chars(dst, '"', 1);
78 362 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 40 static av_cold int compact_init(AVTextFormatContext *wctx)
117 {
118 40 CompactContext *compact = wctx->priv;
119
120
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 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 40 compact->item_sep = compact->item_sep_str[0];
126
127
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (!strcmp(compact->escape_mode_str, "none")) {
128 compact->escape_str = none_escape_str;
129
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 1 times.
40 } else if (!strcmp(compact->escape_mode_str, "c" )) {
130 39 compact->escape_str = c_escape_str;
131
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 } else if (!strcmp(compact->escape_mode_str, "csv" )) {
132 1 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 40 return 0;
139 }
140
141 9997 static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
142 {
143 9997 CompactContext *compact = wctx->priv;
144 9997 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
145 9997 const AVTextFormatSection *parent_section = tf_get_parent_section(wctx, wctx->level);
146
147
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9997 times.
9997 if (!section)
148 return;
149
150 9997 compact->terminate_line[wctx->level] = 1;
151 9997 compact->has_nested_elems[wctx->level] = 0;
152
153 9997 av_bprint_clear(&wctx->section_pbuf[wctx->level]);
154
2/2
✓ Branch 0 taken 9957 times.
✓ Branch 1 taken 40 times.
9997 if (parent_section &&
155
2/2
✓ Branch 0 taken 9090 times.
✓ Branch 1 taken 867 times.
9957 (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
156
2/2
✓ Branch 0 taken 8211 times.
✓ Branch 1 taken 879 times.
9090 (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
157
2/2
✓ Branch 0 taken 495 times.
✓ Branch 1 taken 7716 times.
8211 !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY))))) {
158
159 /* define a prefix for elements not contained in an array or
160 in a wrapper, or for array elements with a type */
161 1362 const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
162 1362 AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
163
164 1362 compact->nested_section[wctx->level] = 1;
165 1362 compact->has_nested_elems[wctx->level - 1] = 1;
166
167 1362 av_bprintf(section_pbuf, "%s%s",
168 1362 wctx->section_pbuf[wctx->level - 1].str, element_name);
169
170
2/2
✓ Branch 0 taken 867 times.
✓ Branch 1 taken 495 times.
1362 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
171 // add /TYPE to prefix
172 867 av_bprint_chars(section_pbuf, '/', 1);
173
174 // normalize section type, replace special characters and lower case
175
2/2
✓ Branch 1 taken 15654 times.
✓ Branch 2 taken 867 times.
16521 for (const char *p = section->get_type(data); *p; p++) {
176 2005 char c =
177
4/4
✓ Branch 0 taken 13783 times.
✓ Branch 1 taken 1871 times.
✓ Branch 2 taken 13385 times.
✓ Branch 3 taken 398 times.
15654 (*p >= '0' && *p <= '9') ||
178
3/4
✓ Branch 0 taken 6643 times.
✓ Branch 1 taken 8613 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 6643 times.
15256 (*p >= 'a' && *p <= 'z') ||
179
4/4
✓ Branch 0 taken 6742 times.
✓ Branch 1 taken 1871 times.
✓ Branch 2 taken 6608 times.
✓ Branch 3 taken 134 times.
15654 (*p >= 'A' && *p <= 'Z') ? av_tolower(*p) : '_';
180 15654 av_bprint_chars(section_pbuf, c, 1);
181 }
182 }
183 1362 av_bprint_chars(section_pbuf, ':', 1);
184
185 1362 wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level - 1];
186 } else {
187
4/4
✓ Branch 0 taken 8595 times.
✓ Branch 1 taken 40 times.
✓ Branch 2 taken 801 times.
✓ Branch 3 taken 7794 times.
8635 if (parent_section && !(parent_section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)) &&
188
2/4
✓ Branch 0 taken 801 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 801 times.
✗ Branch 3 not taken.
801 wctx->level && wctx->nb_item[wctx->level - 1])
189 801 writer_w8(wctx, compact->item_sep);
190
2/2
✓ Branch 0 taken 7470 times.
✓ Branch 1 taken 1165 times.
8635 if (compact->print_section &&
191
2/2
✓ Branch 0 taken 6705 times.
✓ Branch 1 taken 765 times.
7470 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
192 6705 writer_printf(wctx, "%s%c", section->name, compact->item_sep);
193 }
194 }
195
196 9997 static void compact_print_section_footer(AVTextFormatContext *wctx)
197 {
198 9997 CompactContext *compact = wctx->priv;
199 9997 const AVTextFormatSection *section = tf_get_section(wctx, wctx->level);
200
201
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 9997 times.
9997 if (!section)
202 return;
203
204
2/2
✓ Branch 0 taken 8585 times.
✓ Branch 1 taken 1412 times.
9997 if (!compact->nested_section[wctx->level] &&
205
1/2
✓ Branch 0 taken 8585 times.
✗ Branch 1 not taken.
8585 compact->terminate_line[wctx->level] &&
206
2/2
✓ Branch 0 taken 7716 times.
✓ Branch 1 taken 869 times.
8585 !(section->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER | AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
207 7716 writer_w8(wctx, '\n');
208 }
209
210 30252 static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
211 {
212 30252 CompactContext *compact = wctx->priv;
213 AVBPrint buf;
214
215
2/2
✓ Branch 0 taken 26435 times.
✓ Branch 1 taken 3817 times.
30252 if (wctx->nb_item[wctx->level])
216 26435 writer_w8(wctx, compact->item_sep);
217
218
2/2
✓ Branch 0 taken 27369 times.
✓ Branch 1 taken 2883 times.
30252 if (!compact->nokey)
219 27369 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
220
221 30252 av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
222 30252 writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
223 30252 av_bprint_finalize(&buf, NULL);
224 30252 }
225
226 29564 static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
227 {
228 29564 CompactContext *compact = wctx->priv;
229
230
2/2
✓ Branch 0 taken 24887 times.
✓ Branch 1 taken 4677 times.
29564 if (wctx->nb_item[wctx->level])
231 24887 writer_w8(wctx, compact->item_sep);
232
233
2/2
✓ Branch 0 taken 27732 times.
✓ Branch 1 taken 1832 times.
29564 if (!compact->nokey)
234 27732 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
235
236 29564 writer_printf(wctx, "%"PRId64, value);
237 29564 }
238
239 const AVTextFormatter avtextformatter_compact = {
240 .name = "compact",
241 .priv_size = sizeof(CompactContext),
242 .init = compact_init,
243 .print_section_header = compact_print_section_header,
244 .print_section_footer = compact_print_section_footer,
245 .print_integer = compact_print_int,
246 .print_string = compact_print_str,
247 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
248 .priv_class = &compact_class,
249 };
250
251 /* CSV output */
252
253 #undef OFFSET
254 #define OFFSET(x) offsetof(CompactContext, x)
255
256 static const AVOption csv_options[] = {
257 { "item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
258 { "s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, { .str = "," }, 0, 0 },
259 { "nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
260 { "nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
261 { "escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
262 { "e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, { .str = "csv" }, 0, 0 },
263 { "print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
264 { "p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1 },
265 { NULL },
266 };
267
268 DEFINE_FORMATTER_CLASS(csv);
269
270 const AVTextFormatter avtextformatter_csv = {
271 .name = "csv",
272 .priv_size = sizeof(CompactContext),
273 .init = compact_init,
274 .print_section_header = compact_print_section_header,
275 .print_section_footer = compact_print_section_footer,
276 .print_integer = compact_print_int,
277 .print_string = compact_print_str,
278 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
279 .priv_class = &csv_class,
280 };
281