FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/fftools/textformat/tf_compact.c
Date: 2025-04-25 22:50:00
Exec Total Coverage
Lines: 86 99 86.9%
Functions: 7 10 70.0%
Branches: 66 78 84.6%

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
32
33 #define writer_w8(wctx_, b_) (wctx_)->writer->writer->writer_w8((wctx_)->writer, b_)
34 #define writer_put_str(wctx_, str_) (wctx_)->writer->writer->writer_put_str((wctx_)->writer, str_)
35 #define writer_printf(wctx_, fmt_, ...) (wctx_)->writer->writer->writer_printf((wctx_)->writer, fmt_, __VA_ARGS__)
36
37
38 #define DEFINE_FORMATTER_CLASS(name) \
39 static const char *name##_get_name(void *ctx) \
40 { \
41 return #name ; \
42 } \
43 static const AVClass name##_class = { \
44 .class_name = #name, \
45 .item_name = name##_get_name, \
46 .option = name##_options \
47 }
48
49
50 /* Compact output */
51
52 /**
53 * Apply C-language-like string escaping.
54 */
55 29890 static const char *c_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
56 {
57 const char *p;
58
59
2/2
✓ Branch 0 taken 203023 times.
✓ Branch 1 taken 29890 times.
232913 for (p = src; *p; p++) {
60
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) {
61 case '\b': av_bprintf(dst, "%s", "\\b"); break;
62 case '\f': av_bprintf(dst, "%s", "\\f"); break;
63 264 case '\n': av_bprintf(dst, "%s", "\\n"); break;
64 case '\r': av_bprintf(dst, "%s", "\\r"); break;
65 case '\\': av_bprintf(dst, "%s", "\\\\"); break;
66 202759 default:
67
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 202759 times.
202759 if (*p == sep)
68 av_bprint_chars(dst, '\\', 1);
69 202759 av_bprint_chars(dst, *p, 1);
70 }
71 }
72 29890 return dst->str;
73 }
74
75 /**
76 * Quote fields containing special characters, check RFC4180.
77 */
78 362 static const char *csv_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
79 {
80 362 char meta_chars[] = { sep, '"', '\n', '\r', '\0' };
81 362 int needs_quoting = !!src[strcspn(src, meta_chars)];
82
83
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 361 times.
362 if (needs_quoting)
84 1 av_bprint_chars(dst, '"', 1);
85
86
2/2
✓ Branch 0 taken 2326 times.
✓ Branch 1 taken 362 times.
2688 for (; *src; src++) {
87
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 2324 times.
2326 if (*src == '"')
88 2 av_bprint_chars(dst, '"', 1);
89 2326 av_bprint_chars(dst, *src, 1);
90 }
91
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 361 times.
362 if (needs_quoting)
92 1 av_bprint_chars(dst, '"', 1);
93 362 return dst->str;
94 }
95
96 static const char *none_escape_str(AVBPrint *dst, const char *src, const char sep, void *log_ctx)
97 {
98 return src;
99 }
100
101 typedef struct CompactContext {
102 const AVClass *class;
103 char *item_sep_str;
104 char item_sep;
105 int nokey;
106 int print_section;
107 char *escape_mode_str;
108 const char * (*escape_str)(AVBPrint *dst, const char *src, const char sep, void *log_ctx);
109 int nested_section[SECTION_MAX_NB_LEVELS];
110 int has_nested_elems[SECTION_MAX_NB_LEVELS];
111 int terminate_line[SECTION_MAX_NB_LEVELS];
112 } CompactContext;
113
114 #undef OFFSET
115 #define OFFSET(x) offsetof(CompactContext, x)
116
117 static const AVOption compact_options[]= {
118 {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
119 {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str="|"}, 0, 0 },
120 {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
121 {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1 },
122 {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
123 {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="c"}, 0, 0 },
124 {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
125 {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
126 {NULL},
127 };
128
129 DEFINE_FORMATTER_CLASS(compact);
130
131 40 static av_cold int compact_init(AVTextFormatContext *wctx)
132 {
133 40 CompactContext *compact = wctx->priv;
134
135
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (strlen(compact->item_sep_str) != 1) {
136 av_log(wctx, AV_LOG_ERROR, "Item separator '%s' specified, but must contain a single character\n",
137 compact->item_sep_str);
138 return AVERROR(EINVAL);
139 }
140 40 compact->item_sep = compact->item_sep_str[0];
141
142
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 40 times.
40 if (!strcmp(compact->escape_mode_str, "none")) compact->escape_str = none_escape_str;
143
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 1 times.
40 else if (!strcmp(compact->escape_mode_str, "c" )) compact->escape_str = c_escape_str;
144
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 else if (!strcmp(compact->escape_mode_str, "csv" )) compact->escape_str = csv_escape_str;
145 else {
146 av_log(wctx, AV_LOG_ERROR, "Unknown escape mode '%s'\n", compact->escape_mode_str);
147 return AVERROR(EINVAL);
148 }
149
150 40 return 0;
151 }
152
153 9997 static void compact_print_section_header(AVTextFormatContext *wctx, const void *data)
154 {
155 9997 CompactContext *compact = wctx->priv;
156 9997 const struct AVTextFormatSection *section = wctx->section[wctx->level];
157 19994 const struct AVTextFormatSection *parent_section = wctx->level ?
158
2/2
✓ Branch 0 taken 9957 times.
✓ Branch 1 taken 40 times.
9997 wctx->section[wctx->level-1] : NULL;
159 9997 compact->terminate_line[wctx->level] = 1;
160 9997 compact->has_nested_elems[wctx->level] = 0;
161
162 9997 av_bprint_clear(&wctx->section_pbuf[wctx->level]);
163
2/2
✓ Branch 0 taken 9957 times.
✓ Branch 1 taken 40 times.
9997 if (parent_section &&
164
2/2
✓ Branch 0 taken 9090 times.
✓ Branch 1 taken 867 times.
9957 (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE ||
165
2/2
✓ Branch 0 taken 8211 times.
✓ Branch 1 taken 879 times.
9090 (!(section->flags & AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY) &&
166
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))))) {
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 1362 const char *element_name = (char *)av_x_if_null(section->element_name, section->name);
171 1362 AVBPrint *section_pbuf = &wctx->section_pbuf[wctx->level];
172
173 1362 compact->nested_section[wctx->level] = 1;
174 1362 compact->has_nested_elems[wctx->level-1] = 1;
175
176 1362 av_bprintf(section_pbuf, "%s%s",
177 1362 wctx->section_pbuf[wctx->level-1].str, element_name);
178
179
2/2
✓ Branch 0 taken 867 times.
✓ Branch 1 taken 495 times.
1362 if (section->flags & AV_TEXTFORMAT_SECTION_FLAG_HAS_TYPE) {
180 // add /TYPE to prefix
181 867 av_bprint_chars(section_pbuf, '/', 1);
182
183 // normalize section type, replace special characters and lower case
184
2/2
✓ Branch 1 taken 15654 times.
✓ Branch 2 taken 867 times.
16521 for (const char *p = section->get_type(data); *p; p++) {
185 2005 char c =
186
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') ||
187
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') ||
188
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) : '_';
189 15654 av_bprint_chars(section_pbuf, c, 1);
190 }
191 }
192 1362 av_bprint_chars(section_pbuf, ':', 1);
193
194 1362 wctx->nb_item[wctx->level] = wctx->nb_item[wctx->level-1];
195 } else {
196
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)) &&
197
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])
198 801 writer_w8(wctx, compact->item_sep);
199
2/2
✓ Branch 0 taken 7470 times.
✓ Branch 1 taken 1165 times.
8635 if (compact->print_section &&
200
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)))
201 6705 writer_printf(wctx, "%s%c", section->name, compact->item_sep);
202 }
203 9997 }
204
205 9997 static void compact_print_section_footer(AVTextFormatContext *wctx)
206 {
207 9997 CompactContext *compact = wctx->priv;
208
209
2/2
✓ Branch 0 taken 8585 times.
✓ Branch 1 taken 1412 times.
9997 if (!compact->nested_section[wctx->level] &&
210
1/2
✓ Branch 0 taken 8585 times.
✗ Branch 1 not taken.
8585 compact->terminate_line[wctx->level] &&
211
2/2
✓ Branch 0 taken 7716 times.
✓ Branch 1 taken 869 times.
8585 !(wctx->section[wctx->level]->flags & (AV_TEXTFORMAT_SECTION_FLAG_IS_WRAPPER|AV_TEXTFORMAT_SECTION_FLAG_IS_ARRAY)))
212 7716 writer_w8(wctx, '\n');
213 9997 }
214
215 30252 static void compact_print_str(AVTextFormatContext *wctx, const char *key, const char *value)
216 {
217 30252 CompactContext *compact = wctx->priv;
218 AVBPrint buf;
219
220
2/2
✓ Branch 0 taken 26435 times.
✓ Branch 1 taken 3817 times.
30252 if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
221
2/2
✓ Branch 0 taken 27369 times.
✓ Branch 1 taken 2883 times.
30252 if (!compact->nokey)
222 27369 writer_printf(wctx, "%s%s=", wctx->section_pbuf[wctx->level].str, key);
223 30252 av_bprint_init(&buf, 1, AV_BPRINT_SIZE_UNLIMITED);
224 30252 writer_put_str(wctx, compact->escape_str(&buf, value, compact->item_sep, wctx));
225 30252 av_bprint_finalize(&buf, NULL);
226 30252 }
227
228 29564 static void compact_print_int(AVTextFormatContext *wctx, const char *key, int64_t value)
229 {
230 29564 CompactContext *compact = wctx->priv;
231
232
2/2
✓ Branch 0 taken 24887 times.
✓ Branch 1 taken 4677 times.
29564 if (wctx->nb_item[wctx->level]) writer_w8(wctx, compact->item_sep);
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 29564 writer_printf(wctx, "%"PRId64, value);
236 29564 }
237
238 const AVTextFormatter avtextformatter_compact = {
239 .name = "compact",
240 .priv_size = sizeof(CompactContext),
241 .init = compact_init,
242 .print_section_header = compact_print_section_header,
243 .print_section_footer = compact_print_section_footer,
244 .print_integer = compact_print_int,
245 .print_string = compact_print_str,
246 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
247 .priv_class = &compact_class,
248 };
249
250 /* CSV output */
251
252 #undef OFFSET
253 #define OFFSET(x) offsetof(CompactContext, x)
254
255 static const AVOption csv_options[] = {
256 {"item_sep", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
257 {"s", "set item separator", OFFSET(item_sep_str), AV_OPT_TYPE_STRING, {.str=","}, 0, 0 },
258 {"nokey", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
259 {"nk", "force no key printing", OFFSET(nokey), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
260 {"escape", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
261 {"e", "set escape mode", OFFSET(escape_mode_str), AV_OPT_TYPE_STRING, {.str="csv"}, 0, 0 },
262 {"print_section", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
263 {"p", "print section name", OFFSET(print_section), AV_OPT_TYPE_BOOL, {.i64=1}, 0, 1 },
264 {NULL},
265 };
266
267 DEFINE_FORMATTER_CLASS(csv);
268
269 const AVTextFormatter avtextformatter_csv = {
270 .name = "csv",
271 .priv_size = sizeof(CompactContext),
272 .init = compact_init,
273 .print_section_header = compact_print_section_header,
274 .print_section_footer = compact_print_section_footer,
275 .print_integer = compact_print_int,
276 .print_string = compact_print_str,
277 .flags = AV_TEXTFORMAT_FLAG_SUPPORTS_OPTIONAL_FIELDS,
278 .priv_class = &csv_class,
279 };
280