FFmpeg coverage


Directory: ../../../ffmpeg/
File: src/libavformat/id3v2enc.c
Date: 2026-08-30 18:45:47
Exec Total Coverage
Lines: 258 284 90.8%
Functions: 19 19 100.0%
Branches: 147 194 75.8%

Line Branch Exec Source
1 /*
2 * ID3v2 header writer
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 <stdint.h>
22 #include <string.h>
23
24 #include "libavutil/avstring.h"
25 #include "libavutil/dict.h"
26 #include "libavutil/intreadwrite.h"
27 #include "libavutil/mem.h"
28 #include "avformat.h"
29 #include "avlanguage.h"
30 #include "avio.h"
31 #include "avio_internal.h"
32 #include "id3v2.h"
33 #include "mux.h"
34
35 151 static void id3v2_put_size(AVIOContext *pb, int size)
36 {
37 151 avio_w8(pb, size >> 21 & 0x7f);
38 151 avio_w8(pb, size >> 14 & 0x7f);
39 151 avio_w8(pb, size >> 7 & 0x7f);
40 151 avio_w8(pb, size & 0x7f);
41 151 }
42
43 24 static int string_is_ascii(const uint8_t *str)
44 {
45
4/4
✓ Branch 0 taken 97 times.
✓ Branch 1 taken 20 times.
✓ Branch 2 taken 93 times.
✓ Branch 3 taken 4 times.
117 while (*str && *str < 128) str++;
46 24 return !*str;
47 }
48
49 176 static void id3v2_encode_string(AVIOContext *pb, const uint8_t *str,
50 enum ID3v2Encoding enc)
51 {
52 int (*put)(AVIOContext*, const char*);
53
54
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 172 times.
176 if (enc == ID3v2_ENCODING_UTF16BOM) {
55 4 avio_wl16(pb, 0xFEFF); /* BOM */
56 4 put = avio_put_str16le;
57 } else
58 172 put = avio_put_str;
59
60 176 put(pb, str);
61 176 }
62
63 139 static int id3v2_put_frame(ID3v2EncContext *id3, AVIOContext *avioc, AVIOContext *dyn_buf,
64 const uint32_t tag, const uint8_t flags)
65 {
66 int len;
67 uint8_t *pb;
68 139 len = avio_get_dyn_buf(dyn_buf, &pb);
69
70 139 avio_wb32(avioc, tag);
71 /* ID3v2.3 frame size is not sync-safe */
72
2/2
✓ Branch 0 taken 21 times.
✓ Branch 1 taken 118 times.
139 if (id3->version == 3)
73 21 avio_wb32(avioc, len);
74 else
75 118 id3v2_put_size(avioc, len);
76 139 avio_wb16(avioc, flags);
77 139 avio_write(avioc, pb, len);
78
79 139 id3->len += len + ID3v2_HEADER_SIZE;
80
81 139 ffio_free_dyn_buf(&dyn_buf);
82 139 return 1;
83 }
84
85 /**
86 * Write a frame containing lang, descr and text such as COMM and USLT
87 * according to encoding (only UTF-8 or UTF-16+BOM supported).
88 * @return number of bytes written or a negative error code.
89 */
90 25 static int id3v2_put_lang_descr_tag(
91 ID3v2EncContext *id3, AVIOContext *avioc,
92 const uint32_t tag, const uint8_t flags,
93 const char *lang, const char *descr,
94 const char *comment, enum ID3v2Encoding enc)
95 {
96 int ret;
97 AVIOContext *dyn_buf;
98
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 25 times.
25 if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0)
99 return ret;
100
101 /* check if the strings are ASCII-only and use UTF16 only if
102 * they're not */
103
4/6
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 21 times.
✓ Branch 3 taken 4 times.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
25 if (enc == ID3v2_ENCODING_UTF16BOM && string_is_ascii(comment) &&
104 (!descr || string_is_ascii(descr)))
105 4 enc = ID3v2_ENCODING_ISO8859;
106
107 25 avio_w8(dyn_buf, enc);
108 25 avio_write(dyn_buf, lang, 3);
109 // avio_put_str can handle NULL pointer but avio_put_str16le cannot.
110
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 11 times.
25 id3v2_encode_string(dyn_buf, descr ? descr : "", enc);
111 25 id3v2_encode_string(dyn_buf, comment, enc);
112
113 25 return id3v2_put_frame(id3, avioc, dyn_buf, tag, flags);
114 }
115
116 /**
117 * Write a text frame with multiple text string according to encoding (only
118 * UTF-8 or UTF-16+BOM supported).
119 * @return number of bytes written or a negative error code.
120 */
121 100 static int id3v2_put_text_tag(
122 ID3v2EncContext *id3, AVIOContext *avioc,
123 const uint32_t tag, const uint8_t flags,
124 const char **strings, int len,
125 enum ID3v2Encoding enc)
126 {
127 int i, ret;
128 AVIOContext *dyn_buf;
129
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 100 times.
100 if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0)
130 return ret;
131
132 /* check if the strings are ASCII-only and use UTF16 only if
133 * they're not */
134
2/2
✓ Branch 0 taken 14 times.
✓ Branch 1 taken 86 times.
100 if (enc == ID3v2_ENCODING_UTF16BOM) {
135 14 enc = ID3v2_ENCODING_ISO8859;
136
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 11 times.
29 for (i = 0; i < len; i++) {
137
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 15 times.
18 if (!string_is_ascii((const uint8_t *)strings[i])) {
138 3 enc = ID3v2_ENCODING_UTF16BOM;
139 3 break;
140 }
141 }
142 }
143
144 100 avio_w8(dyn_buf, enc);
145
146
2/2
✓ Branch 0 taken 116 times.
✓ Branch 1 taken 100 times.
216 for (i = 0; i < len; i++)
147 116 id3v2_encode_string(dyn_buf, strings[i], enc);
148
149 100 return id3v2_put_frame(id3, avioc, dyn_buf, tag, flags);
150 }
151
152 /**
153 * Write a priv frame with owner and data. 'key' is the owner prepended with
154 * ID3v2_PRIV_METADATA_PREFIX. 'data' is provided as a string. Any \xXX
155 * (where 'X' is a valid hex digit) will be unescaped to the byte value.
156 */
157 19 static int id3v2_put_priv(
158 ID3v2EncContext *id3, AVIOContext *avioc,
159 const uint8_t flags, const char *key,
160 const char *data)
161 {
162 int ret;
163 AVIOContext *dyn_buf;
164
165
2/2
✓ Branch 1 taken 16 times.
✓ Branch 2 taken 3 times.
19 if (!av_strstart(key, ID3v2_PRIV_METADATA_PREFIX, &key)) {
166 16 return 0;
167 }
168
169
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
3 if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0)
170 return ret;
171
172 // owner + null byte.
173 3 avio_write(dyn_buf, key, strlen(key) + 1);
174
175
2/2
✓ Branch 0 taken 15 times.
✓ Branch 1 taken 3 times.
18 while (*data) {
176
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 11 times.
15 if (av_strstart(data, "\\x", &data)) {
177
4/8
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 4 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 4 times.
✗ Branch 7 not taken.
4 if (data[0] && data[1] && av_isxdigit(data[0]) && av_isxdigit(data[1])) {
178 4 char digits[] = {data[0], data[1], 0};
179 4 avio_w8(dyn_buf, strtol(digits, NULL, 16));
180 4 data += 2;
181 } else {
182 ffio_free_dyn_buf(&dyn_buf);
183 av_log(avioc, AV_LOG_ERROR, "Invalid escape '\\x%.2s' in metadata tag '"
184 ID3v2_PRIV_METADATA_PREFIX "%s'.\n", data, key);
185 return AVERROR(EINVAL);
186 }
187 } else {
188 11 avio_write(dyn_buf, data++, 1);
189 }
190 }
191
192 3 return id3v2_put_frame(id3, avioc, dyn_buf, MKBETAG('P', 'R', 'I', 'V'),
193 flags);
194 }
195
196 struct LangDescrTagMap {
197 const char * const key;
198 uint32_t tag;
199 };
200
201 static const struct LangDescrTagMap id3v2_lang_descr_tags[] = {
202 {.key = "comment", .tag = MKBETAG('C', 'O', 'M', 'M')},
203 {.key = "lyrics", .tag = MKBETAG('U', 'S', 'L', 'T')},
204 {.key = NULL, .tag = 0}
205 };
206
207 18 static int is_valid_lang(const char *s)
208 {
209
4/4
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 6 times.
✓ Branch 3 taken 10 times.
✓ Branch 4 taken 2 times.
18 return strlen(s) == 3 && ff_convert_lang_to(s, AV_LANG_ISO639_2_BIBL);
210 }
211
212 128 static int id3v2_check_write_lang_descr_tag(
213 ID3v2EncContext *id3, AVIOContext *pb, const AVDictionaryEntry *t,
214 enum ID3v2Encoding enc)
215 {
216 int i, key_len;
217 const char *key, *after_dash, *last_dash;
218 128 uint32_t tag, ff_tag = 0;
219 128 char lang[4] = "und";
220
221 /* Raw 4CC key (e.g. "COMM"): only exact match, no suffix modifiers. */
222
2/2
✓ Branch 0 taken 93 times.
✓ Branch 1 taken 35 times.
128 if (strlen(t->key) == 4)
223 93 ff_tag = AV_RB32(t->key);
224
225
2/2
✓ Branch 0 taken 235 times.
✓ Branch 1 taken 103 times.
338 for (i = 0; id3v2_lang_descr_tags[i].key; i++) {
226 235 tag = id3v2_lang_descr_tags[i].tag;
227 235 key = id3v2_lang_descr_tags[i].key;
228 235 key_len = strlen(key);
229
230
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 228 times.
235 if (ff_tag == tag)
231 7 return id3v2_put_lang_descr_tag(id3, pb, tag, 0,
232 7 lang, NULL, t->value, enc);
233
234 /* Generic key (e.g. "comment"): require exact match or '-' separator. */
235
2/2
✓ Branch 0 taken 18 times.
✓ Branch 1 taken 210 times.
228 if (strncmp(t->key, key, key_len) ||
236
2/4
✓ Branch 0 taken 18 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 18 times.
18 (t->key[key_len] != '\0' && t->key[key_len] != '-'))
237 210 continue;
238
239
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 18 times.
18 if (t->key[key_len] == '\0')
240 return id3v2_put_lang_descr_tag(id3, pb, tag, 0,
241 lang, NULL, t->value, enc);
242
243 /* Has suffix(es) after '-'. */
244 18 after_dash = t->key + key_len + 1;
245 18 last_dash = strrchr(after_dash, '-');
246 18 const char *suffix = NULL;
247 18 const char *middle = NULL;
248
249
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 7 times.
18 if (last_dash) {
250 11 middle = after_dash;
251 11 suffix = last_dash + 1;
252 } else {
253 7 suffix = after_dash;
254 }
255
256
2/2
✓ Branch 0 taken 7 times.
✓ Branch 1 taken 11 times.
18 if (!middle) {
257 /* <tag>-<suffix>: valid lang → lang only; otherwise →
258 * descriptor only. */
259 7 const char *descr = NULL;
260
2/2
✓ Branch 1 taken 4 times.
✓ Branch 2 taken 3 times.
7 if (is_valid_lang(suffix))
261 4 memcpy(lang, suffix, 3);
262 else
263 3 descr = suffix;
264 7 return id3v2_put_lang_descr_tag(id3, pb, tag, 0, lang,
265 7 descr, t->value, enc);
266 } else {
267 /* <tag>-<middle>-<suffix>: valid lang suffix → lang+descriptor;
268 * otherwise → full suffix after first '-' is the descriptor. */
269
4/4
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 6 times.
✓ Branch 3 taken 4 times.
✓ Branch 4 taken 1 times.
11 if (is_valid_lang(suffix) || *suffix == '\0') {
270 /* Valid lang suffix or trailing dash (explicit empty lang):
271 * descriptor = everything before the last '-'. */
272 10 size_t descr_len = last_dash - after_dash;
273 10 char *descr = av_strndup(middle, descr_len);
274 int ret;
275
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!descr)
276 return AVERROR(ENOMEM);
277
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 4 times.
10 if (*suffix)
278 6 memcpy(lang, suffix, 3);
279 10 ret = id3v2_put_lang_descr_tag(id3, pb, tag, 0,
280 10 lang, descr, t->value, enc);
281 10 av_freep(&descr);
282 10 return ret;
283 }
284 1 return id3v2_put_lang_descr_tag(id3, pb, tag, 0,
285 1 lang, after_dash, t->value, enc);
286 }
287 }
288
289 103 return 0;
290 }
291
292 134 static int id3v2_check_write_tag(ID3v2EncContext *id3, AVIOContext *pb, const AVDictionaryEntry *t,
293 const char table[][4], enum ID3v2Encoding enc)
294 {
295 uint32_t tag;
296 int i;
297
298
3/4
✓ Branch 0 taken 98 times.
✓ Branch 1 taken 36 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 98 times.
134 if (t->key[0] != 'T' || strlen(t->key) != 4)
299 36 return 0;
300
301 98 tag = AV_RB32(t->key);
302
2/2
✓ Branch 0 taken 1516 times.
✓ Branch 1 taken 14 times.
1530 for (i = 0; *table[i]; i++)
303
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 1432 times.
1516 if (tag == AV_RB32(table[i])) {
304 84 const char *strings[] = {t->value};
305 84 return id3v2_put_text_tag(id3, pb, tag, 0, strings, 1, enc);
306 }
307
308 14 return 0;
309 }
310
311 6 static void id3v2_3_metadata_split_date(AVDictionary **pm)
312 {
313 6 const AVDictionaryEntry *mtag = NULL;
314 6 AVDictionary *dst = NULL;
315 const char *key, *value;
316 6 char year[5] = {0}, day_month[5] = {0};
317 int i;
318
319
2/2
✓ Branch 1 taken 18 times.
✓ Branch 2 taken 6 times.
24 while ((mtag = av_dict_iterate(*pm, mtag))) {
320 18 key = mtag->key;
321
2/2
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 16 times.
18 if (!av_strcasecmp(key, "date")) {
322 /* split date tag using "YYYY-MM-DD" format into year and month/day segments */
323 2 value = mtag->value;
324 2 i = 0;
325
3/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 8 times.
✗ Branch 3 not taken.
10 while (value[i] >= '0' && value[i] <= '9') i++;
326
3/4
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
2 if (value[i] == '\0' || value[i] == '-') {
327 2 av_strlcpy(year, value, sizeof(year));
328 2 av_dict_set(&dst, "TYER", year, 0);
329
330
2/2
✓ Branch 0 taken 1 times.
✓ Branch 1 taken 1 times.
2 if (value[i] == '-' &&
331
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 value[i+1] >= '0' && value[i+1] <= '1' &&
332
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 value[i+2] >= '0' && value[i+2] <= '9' &&
333
1/2
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
1 value[i+3] == '-' &&
334
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 value[i+4] >= '0' && value[i+4] <= '3' &&
335
2/4
✓ Branch 0 taken 1 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
✗ Branch 3 not taken.
1 value[i+5] >= '0' && value[i+5] <= '9' &&
336
1/4
✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
1 (value[i+6] == '\0' || value[i+6] == ' ')) {
337 1 snprintf(day_month, sizeof(day_month), "%.2s%.2s", value + i + 4, value + i + 1);
338 1 av_dict_set(&dst, "TDAT", day_month, 0);
339 }
340 } else
341 av_dict_set(&dst, key, value, 0);
342 } else
343 16 av_dict_set(&dst, key, mtag->value, 0);
344 }
345 6 av_dict_free(pm);
346 6 *pm = dst;
347 6 }
348
349 33 void ff_id3v2_start(ID3v2EncContext *id3, AVIOContext *pb, int id3v2_version,
350 const char *magic)
351 {
352 33 id3->version = id3v2_version;
353
354 33 avio_wb32(pb, MKBETAG(magic[0], magic[1], magic[2], id3v2_version));
355 33 avio_w8(pb, 0);
356 33 avio_w8(pb, 0); /* flags */
357
358 /* reserve space for size */
359 33 id3->size_pos = avio_tell(pb);
360 33 avio_wb32(pb, 0);
361 33 }
362
363 38 static int write_metadata(AVIOContext *pb, AVDictionary **metadata,
364 ID3v2EncContext *id3, int enc)
365 {
366 38 const AVDictionaryEntry *t = NULL;
367 int ret;
368
369 38 ff_metadata_conv(metadata, ff_id3v2_34_metadata_conv, NULL);
370
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 32 times.
38 if (id3->version == 3)
371 6 id3v2_3_metadata_split_date(metadata);
372
1/2
✓ Branch 0 taken 32 times.
✗ Branch 1 not taken.
32 else if (id3->version == 4)
373 32 ff_metadata_conv(metadata, ff_id3v2_4_metadata_conv, NULL);
374
375
2/2
✓ Branch 1 taken 128 times.
✓ Branch 2 taken 38 times.
166 while ((t = av_dict_iterate(*metadata, t))) {
376
2/2
✓ Branch 1 taken 25 times.
✓ Branch 2 taken 103 times.
128 if ((ret = id3v2_check_write_lang_descr_tag(id3, pb, t, enc)) > 0)
377 112 continue;
378
379
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 103 times.
103 if (ret < 0)
380 return ret;
381
382
2/2
✓ Branch 1 taken 72 times.
✓ Branch 2 taken 31 times.
103 if ((ret = id3v2_check_write_tag(id3, pb, t, ff_id3v2_tags, enc)) > 0)
383 72 continue;
384
385
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 31 times.
31 if (ret < 0)
386 return ret;
387
388
4/4
✓ Branch 0 taken 8 times.
✓ Branch 1 taken 23 times.
✓ Branch 3 taken 12 times.
✓ Branch 4 taken 19 times.
31 if ((ret = id3v2_check_write_tag(id3, pb, t, id3->version == 3 ?
389 ff_id3v2_3_tags : ff_id3v2_4_tags, enc)) > 0)
390 12 continue;
391
392
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 19 times.
19 if (ret < 0)
393 return ret;
394
395
2/2
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 16 times.
19 if ((ret = id3v2_put_priv(id3, pb, 0, t->key, t->value)) > 0)
396 3 continue;
397
398
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 16 times.
16 if (ret < 0)
399 return ret;
400
401 /* unknown tag, write as TXXX frame */
402 16 const char *strings[] = {t->key, t->value};
403
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 16 times.
16 if ((ret = id3v2_put_text_tag(id3, pb, MKBETAG('T', 'X', 'X', 'X'),
404 0, strings, 2, enc)) < 0)
405 return ret;
406 }
407
408 38 return 0;
409 }
410
411 33 static int write_ctoc(AVFormatContext *s, AVIOContext *pb,
412 ID3v2EncContext *id3, int enc)
413 {
414 AVIOContext *dyn_bc;
415 char name[123];
416 int ret;
417
418
2/2
✓ Branch 0 taken 32 times.
✓ Branch 1 taken 1 times.
33 if (s->nb_chapters == 0)
419 32 return 0;
420
421
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 1 times.
1 if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0)
422 return ret;
423
424 1 avio_put_str(dyn_bc, "toc");
425 1 avio_w8(dyn_bc, 0x03);
426 1 avio_w8(dyn_bc, s->nb_chapters);
427
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 1 times.
6 for (int i = 0; i < s->nb_chapters; i++) {
428 5 snprintf(name, 122, "ch%d", i);
429 5 avio_put_str(dyn_bc, name);
430 }
431
432 1 return id3v2_put_frame(id3, pb, dyn_bc,
433 MKBETAG('C', 'T', 'O', 'C'), 0);
434 }
435
436 5 static int write_chapter(AVFormatContext *s, AVIOContext *pb,
437 ID3v2EncContext *id3, int id, int enc)
438 {
439 const AVRational time_base = {1, 1000};
440 5 AVChapter *ch = s->chapters[id];
441 uint8_t *dyn_buf;
442 AVIOContext *dyn_bc;
443 char name[123];
444 int len, start, end, ret;
445
446
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if ((ret = avio_open_dyn_buf(&dyn_bc)) < 0)
447 return ret;
448
449 5 start = av_rescale_q(ch->start, ch->time_base, time_base);
450 5 end = av_rescale_q(ch->end, ch->time_base, time_base);
451
452 5 snprintf(name, 122, "ch%d", id);
453 5 id3->len += avio_put_str(dyn_bc, name);
454 5 avio_wb32(dyn_bc, start);
455 5 avio_wb32(dyn_bc, end);
456 5 avio_wb32(dyn_bc, 0xFFFFFFFFu);
457 5 avio_wb32(dyn_bc, 0xFFFFFFFFu);
458
459
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if ((ret = write_metadata(dyn_bc, &ch->metadata, id3, enc)) < 0)
460 goto fail;
461
462 5 len = avio_get_dyn_buf(dyn_bc, &dyn_buf);
463 5 id3->len += 16 + ID3v2_HEADER_SIZE;
464
465 5 avio_wb32(pb, MKBETAG('C', 'H', 'A', 'P'));
466 5 avio_wb32(pb, len);
467 5 avio_wb16(pb, 0);
468 5 avio_write(pb, dyn_buf, len);
469
470 5 fail:
471 5 ffio_free_dyn_buf(&dyn_bc);
472
473 5 return ret;
474 }
475
476 33 int ff_id3v2_write_metadata(AVFormatContext *s, AVIOContext *pb,
477 ID3v2EncContext *id3)
478 {
479
2/2
✓ Branch 0 taken 6 times.
✓ Branch 1 taken 27 times.
33 int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM :
480 ID3v2_ENCODING_UTF8;
481 int i, ret;
482
483 33 ff_standardize_creation_time(s);
484
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 if ((ret = write_metadata(pb, &s->metadata, id3, enc)) < 0)
485 return ret;
486
487
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 33 times.
33 if ((ret = write_ctoc(s, pb, id3, enc)) < 0)
488 return ret;
489
490
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 33 times.
38 for (i = 0; i < s->nb_chapters; i++) {
491
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 5 times.
5 if ((ret = write_chapter(s, pb, id3, i, enc)) < 0)
492 return ret;
493 }
494
495 33 return 0;
496 }
497
498 10 int ff_id3v2_write_apic(AVFormatContext *s, AVIOContext *pb,
499 ID3v2EncContext *id3, AVPacket *pkt)
500 {
501 10 AVStream *st = s->streams[pkt->stream_index];
502 AVDictionaryEntry *e;
503
504 AVIOContext *dyn_buf;
505 10 const CodecMime *mime = ff_id3v2_mime_tags;
506 10 const char *mimetype = NULL, *desc = "";
507
2/2
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
10 int enc = id3->version == 3 ? ID3v2_ENCODING_UTF16BOM :
508 ID3v2_ENCODING_UTF8;
509 10 int i, type = 0, ret;
510
511 /* get the mimetype*/
512
1/2
✓ Branch 0 taken 38 times.
✗ Branch 1 not taken.
38 while (mime->id != AV_CODEC_ID_NONE) {
513
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 28 times.
38 if (mime->id == st->codecpar->codec_id) {
514 10 mimetype = mime->str;
515 10 break;
516 }
517 28 mime++;
518 }
519
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 10 times.
10 if (!mimetype) {
520 av_log(s, AV_LOG_ERROR, "No mimetype is known for stream %d, cannot "
521 "write an attached picture.\n", st->index);
522 return AVERROR(EINVAL);
523 }
524
525 /* get the picture type */
526 10 e = av_dict_get(st->metadata, "comment", NULL, 0);
527
3/4
✓ Branch 0 taken 56 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 56 times.
✗ Branch 3 not taken.
58 for (i = 0; e && i < FF_ARRAY_ELEMS(ff_id3v2_picture_types); i++) {
528
2/2
✓ Branch 1 taken 8 times.
✓ Branch 2 taken 48 times.
56 if (!av_strcasecmp(e->value, ff_id3v2_picture_types[i])) {
529 8 type = i;
530 8 break;
531 }
532 }
533
534 /* get the description */
535
2/2
✓ Branch 1 taken 5 times.
✓ Branch 2 taken 5 times.
10 if ((e = av_dict_get(st->metadata, "title", NULL, 0)))
536 5 desc = e->value;
537
538 /* use UTF16 only for non-ASCII strings */
539
4/4
✓ Branch 0 taken 2 times.
✓ Branch 1 taken 8 times.
✓ Branch 3 taken 1 times.
✓ Branch 4 taken 1 times.
10 if (enc == ID3v2_ENCODING_UTF16BOM && string_is_ascii(desc))
540 1 enc = ID3v2_ENCODING_ISO8859;
541
542 /* start writing */
543
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 10 times.
10 if ((ret = avio_open_dyn_buf(&dyn_buf)) < 0)
544 return ret;
545
546 10 avio_w8(dyn_buf, enc);
547 10 avio_put_str(dyn_buf, mimetype);
548 10 avio_w8(dyn_buf, type);
549 10 id3v2_encode_string(dyn_buf, desc, enc);
550 10 avio_write(dyn_buf, pkt->data, pkt->size);
551
552 10 return id3v2_put_frame(id3, pb, dyn_buf,
553 MKBETAG('A', 'P', 'I', 'C'), 0);
554 }
555
556 33 void ff_id3v2_finish(ID3v2EncContext *id3, AVIOContext *pb,
557 int padding_bytes)
558 {
559 int64_t cur_pos;
560
561
2/2
✓ Branch 0 taken 31 times.
✓ Branch 1 taken 2 times.
33 if (padding_bytes < 0)
562 31 padding_bytes = 10;
563
564 /* The ID3v2.3 specification states that 28 bits are used to represent the
565 * size of the whole tag. Therefore the current size of the tag needs to be
566 * subtracted from the upper limit of 2^28-1 to clip the value correctly. */
567 /* The minimum of 10 is an arbitrary amount of padding at the end of the tag
568 * to fix cover art display with some software such as iTunes, Traktor,
569 * Serato, Torq. */
570 33 padding_bytes = av_clip(padding_bytes, 10, 268435455 - id3->len);
571 33 ffio_fill(pb, 0, padding_bytes);
572 33 id3->len += padding_bytes;
573
574 33 cur_pos = avio_tell(pb);
575 33 avio_seek(pb, id3->size_pos, SEEK_SET);
576 33 id3v2_put_size(pb, id3->len);
577 33 avio_seek(pb, cur_pos, SEEK_SET);
578 33 }
579
580 2 int ff_id3v2_write_simple(struct AVFormatContext *s, int id3v2_version,
581 const char *magic)
582 {
583 2 ID3v2EncContext id3 = { 0 };
584 int ret;
585
586 2 ff_id3v2_start(&id3, s->pb, id3v2_version, magic);
587
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 2 times.
2 if ((ret = ff_id3v2_write_metadata(s, s->pb, &id3)) < 0)
588 return ret;
589 2 ff_id3v2_finish(&id3, s->pb, s->metadata_header_padding);
590
591 2 return 0;
592 }
593