Commit | Line | Data |
---|---|---|
2ea512a6 AC |
1 | /* |
2 | * ID3v2 header parser | |
3 | * Copyright (c) 2003 Fabrice Bellard | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
15 | * Lesser General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU Lesser General Public | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | #include "id3v2.h" | |
75411182 PD |
23 | #include "id3v1.h" |
24 | #include "libavutil/avstring.h" | |
2ea512a6 AC |
25 | |
26 | int ff_id3v2_match(const uint8_t *buf) | |
27 | { | |
7d7b8c32 DB |
28 | return buf[0] == 'I' && |
29 | buf[1] == 'D' && | |
30 | buf[2] == '3' && | |
31 | buf[3] != 0xff && | |
32 | buf[4] != 0xff && | |
33 | (buf[6] & 0x80) == 0 && | |
34 | (buf[7] & 0x80) == 0 && | |
35 | (buf[8] & 0x80) == 0 && | |
1d4b1bf2 | 36 | (buf[9] & 0x80) == 0; |
2ea512a6 | 37 | } |
ac3ef4a4 AC |
38 | |
39 | int ff_id3v2_tag_len(const uint8_t * buf) | |
40 | { | |
41 | int len = ((buf[6] & 0x7f) << 21) + | |
7d7b8c32 DB |
42 | ((buf[7] & 0x7f) << 14) + |
43 | ((buf[8] & 0x7f) << 7) + | |
44 | (buf[9] & 0x7f) + | |
45 | ID3v2_HEADER_SIZE; | |
ac3ef4a4 AC |
46 | if (buf[5] & 0x10) |
47 | len += ID3v2_HEADER_SIZE; | |
48 | return len; | |
49 | } | |
75411182 | 50 | |
50fcd5be PD |
51 | void ff_id3v2_read(AVFormatContext *s) |
52 | { | |
53 | int len, ret; | |
54 | uint8_t buf[ID3v2_HEADER_SIZE]; | |
55 | ||
56 | ret = get_buffer(s->pb, buf, ID3v2_HEADER_SIZE); | |
57 | if (ret != ID3v2_HEADER_SIZE) | |
58 | return; | |
59 | if (ff_id3v2_match(buf)) { | |
60 | /* parse ID3v2 header */ | |
61 | len = ((buf[6] & 0x7f) << 21) | | |
62 | ((buf[7] & 0x7f) << 14) | | |
63 | ((buf[8] & 0x7f) << 7) | | |
64 | (buf[9] & 0x7f); | |
65 | ff_id3v2_parse(s, len, buf[3], buf[5]); | |
66 | } else { | |
67 | url_fseek(s->pb, 0, SEEK_SET); | |
68 | } | |
69 | } | |
70 | ||
75411182 PD |
71 | static unsigned int get_size(ByteIOContext *s, int len) |
72 | { | |
7d7b8c32 DB |
73 | int v = 0; |
74 | while (len--) | |
75 | v = (v << 7) + (get_byte(s) & 0x7F); | |
75411182 PD |
76 | return v; |
77 | } | |
78 | ||
79 | static void read_ttag(AVFormatContext *s, int taglen, const char *key) | |
80 | { | |
81 | char *q, dst[512]; | |
82 | int len, dstlen = sizeof(dst) - 1; | |
83 | unsigned genre; | |
20c68378 | 84 | unsigned int (*get)(ByteIOContext*) = get_be16; |
75411182 | 85 | |
7d7b8c32 DB |
86 | dst[0] = 0; |
87 | if (taglen < 1) | |
75411182 PD |
88 | return; |
89 | ||
90 | taglen--; /* account for encoding type byte */ | |
91 | ||
7d7b8c32 | 92 | switch (get_byte(s->pb)) { /* encoding type */ |
75411182 PD |
93 | |
94 | case 0: /* ISO-8859-1 (0 - 255 maps directly into unicode) */ | |
95 | q = dst; | |
787f8fad | 96 | while (taglen-- && q - dst < dstlen - 7) { |
75411182 | 97 | uint8_t tmp; |
787f8fad | 98 | PUT_UTF8(get_byte(s->pb), tmp, *q++ = tmp;) |
75411182 | 99 | } |
9aa1bcce | 100 | *q = 0; |
75411182 PD |
101 | break; |
102 | ||
20c68378 AK |
103 | case 1: /* UTF-16 with BOM */ |
104 | taglen -= 2; | |
105 | switch (get_be16(s->pb)) { | |
106 | case 0xfffe: | |
107 | get = get_le16; | |
108 | case 0xfeff: | |
109 | break; | |
110 | default: | |
111 | av_log(s, AV_LOG_ERROR, "Incorrect BOM value in tag %s.\n", key); | |
112 | return; | |
113 | } | |
114 | // fall-through | |
115 | ||
116 | case 2: /* UTF-16BE without BOM */ | |
117 | q = dst; | |
118 | while (taglen > 1 && q - dst < dstlen - 7) { | |
119 | uint32_t ch; | |
120 | uint8_t tmp; | |
121 | ||
122 | GET_UTF16(ch, ((taglen -= 2) >= 0 ? get(s->pb) : 0), break;) | |
123 | PUT_UTF8(ch, tmp, *q++ = tmp;) | |
124 | } | |
125 | *q = 0; | |
126 | break; | |
127 | ||
75411182 | 128 | case 3: /* UTF-8 */ |
7d7b8c32 | 129 | len = FFMIN(taglen, dstlen - 1); |
75411182 PD |
130 | get_buffer(s->pb, dst, len); |
131 | dst[len] = 0; | |
132 | break; | |
20c68378 AK |
133 | default: |
134 | av_log(s, AV_LOG_WARNING, "Unknown encoding in tag %s\n.", key); | |
75411182 PD |
135 | } |
136 | ||
137 | if (!strcmp(key, "genre") | |
138 | && (sscanf(dst, "(%d)", &genre) == 1 || sscanf(dst, "%d", &genre) == 1) | |
139 | && genre <= ID3v1_GENRE_MAX) | |
140 | av_strlcpy(dst, ff_id3v1_genre_str[genre], sizeof(dst)); | |
141 | ||
142 | if (*dst) | |
143 | av_metadata_set(&s->metadata, key, dst); | |
144 | } | |
145 | ||
146 | void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags) | |
147 | { | |
148 | int isv34, tlen; | |
149 | uint32_t tag; | |
150 | int64_t next; | |
151 | int taghdrlen; | |
152 | const char *reason; | |
153 | ||
7d7b8c32 | 154 | switch (version) { |
75411182 | 155 | case 2: |
7d7b8c32 | 156 | if (flags & 0x40) { |
75411182 PD |
157 | reason = "compression"; |
158 | goto error; | |
159 | } | |
160 | isv34 = 0; | |
161 | taghdrlen = 6; | |
162 | break; | |
163 | ||
164 | case 3: | |
165 | case 4: | |
166 | isv34 = 1; | |
167 | taghdrlen = 10; | |
168 | break; | |
169 | ||
170 | default: | |
171 | reason = "version"; | |
172 | goto error; | |
173 | } | |
174 | ||
7d7b8c32 | 175 | if (flags & 0x80) { |
75411182 PD |
176 | reason = "unsynchronization"; |
177 | goto error; | |
178 | } | |
179 | ||
7d7b8c32 | 180 | if (isv34 && flags & 0x40) /* Extended header present, just skip over it */ |
75411182 PD |
181 | url_fskip(s->pb, get_size(s->pb, 4)); |
182 | ||
7d7b8c32 DB |
183 | while (len >= taghdrlen) { |
184 | if (isv34) { | |
75411182 | 185 | tag = get_be32(s->pb); |
3fd5a75b | 186 | if(version==3){ |
d004179e | 187 | tlen = get_be32(s->pb); |
3fd5a75b MN |
188 | }else |
189 | tlen = get_size(s->pb, 4); | |
75411182 PD |
190 | get_be16(s->pb); /* flags */ |
191 | } else { | |
192 | tag = get_be24(s->pb); | |
1cd44221 | 193 | tlen = get_be24(s->pb); |
75411182 PD |
194 | } |
195 | len -= taghdrlen + tlen; | |
196 | ||
7d7b8c32 | 197 | if (len < 0) |
75411182 PD |
198 | break; |
199 | ||
200 | next = url_ftell(s->pb) + tlen; | |
201 | ||
7d7b8c32 | 202 | switch (tag) { |
75411182 PD |
203 | case MKBETAG('T', 'I', 'T', '2'): |
204 | case MKBETAG(0, 'T', 'T', '2'): | |
205 | read_ttag(s, tlen, "title"); | |
206 | break; | |
207 | case MKBETAG('T', 'P', 'E', '1'): | |
208 | case MKBETAG(0, 'T', 'P', '1'): | |
209 | read_ttag(s, tlen, "author"); | |
210 | break; | |
211 | case MKBETAG('T', 'A', 'L', 'B'): | |
212 | case MKBETAG(0, 'T', 'A', 'L'): | |
213 | read_ttag(s, tlen, "album"); | |
214 | break; | |
215 | case MKBETAG('T', 'C', 'O', 'N'): | |
216 | case MKBETAG(0, 'T', 'C', 'O'): | |
217 | read_ttag(s, tlen, "genre"); | |
218 | break; | |
219 | case MKBETAG('T', 'C', 'O', 'P'): | |
220 | case MKBETAG(0, 'T', 'C', 'R'): | |
221 | read_ttag(s, tlen, "copyright"); | |
222 | break; | |
223 | case MKBETAG('T', 'R', 'C', 'K'): | |
224 | case MKBETAG(0, 'T', 'R', 'K'): | |
225 | read_ttag(s, tlen, "track"); | |
226 | break; | |
227 | case 0: | |
228 | /* padding, skip to end */ | |
229 | url_fskip(s->pb, len); | |
230 | len = 0; | |
231 | continue; | |
232 | } | |
233 | /* Skip to end of tag */ | |
234 | url_fseek(s->pb, next, SEEK_SET); | |
235 | } | |
236 | ||
7d7b8c32 | 237 | if (version == 4 && flags & 0x10) /* Footer preset, always 10 bytes, skip over it */ |
75411182 PD |
238 | url_fskip(s->pb, 10); |
239 | return; | |
240 | ||
241 | error: | |
242 | av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", version, reason); | |
243 | url_fskip(s->pb, len); | |
244 | } |