| 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 |