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"
23#include "id3v1.h"
24#include "libavutil/avstring.h"
25
26int ff_id3v2_match(const uint8_t *buf)
27{
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 &&
36           (buf[9] & 0x80) ==    0;
37}
38
39int ff_id3v2_tag_len(const uint8_t * buf)
40{
41    int len = ((buf[6] & 0x7f) << 21) +
42              ((buf[7] & 0x7f) << 14) +
43              ((buf[8] & 0x7f) << 7) +
44               (buf[9] & 0x7f) +
45              ID3v2_HEADER_SIZE;
46    if (buf[5] & 0x10)
47        len += ID3v2_HEADER_SIZE;
48    return len;
49}
50
51void 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
71static unsigned int get_size(ByteIOContext *s, int len)
72{
73    int v = 0;
74    while (len--)
75        v = (v << 7) + (get_byte(s) & 0x7F);
76    return v;
77}
78
79static void read_ttag(AVFormatContext *s, int taglen, const char *key)
80{
81    char *q, dst[512];
82    const char *val = NULL;
83    int len, dstlen = sizeof(dst) - 1;
84    unsigned genre;
85    unsigned int (*get)(ByteIOContext*) = get_be16;
86
87    dst[0] = 0;
88    if (taglen < 1)
89        return;
90
91    taglen--; /* account for encoding type byte */
92
93    switch (get_byte(s->pb)) { /* encoding type */
94
95    case 0:  /* ISO-8859-1 (0 - 255 maps directly into unicode) */
96        q = dst;
97        while (taglen-- && q - dst < dstlen - 7) {
98            uint8_t tmp;
99            PUT_UTF8(get_byte(s->pb), tmp, *q++ = tmp;)
100        }
101        *q = 0;
102        break;
103
104    case 1:  /* UTF-16 with BOM */
105        taglen -= 2;
106        switch (get_be16(s->pb)) {
107        case 0xfffe:
108            get = get_le16;
109        case 0xfeff:
110            break;
111        default:
112            av_log(s, AV_LOG_ERROR, "Incorrect BOM value in tag %s.\n", key);
113            return;
114        }
115        // fall-through
116
117    case 2:  /* UTF-16BE without BOM */
118        q = dst;
119        while (taglen > 1 && q - dst < dstlen - 7) {
120            uint32_t ch;
121            uint8_t tmp;
122
123            GET_UTF16(ch, ((taglen -= 2) >= 0 ? get(s->pb) : 0), break;)
124            PUT_UTF8(ch, tmp, *q++ = tmp;)
125        }
126        *q = 0;
127        break;
128
129    case 3:  /* UTF-8 */
130        len = FFMIN(taglen, dstlen);
131        get_buffer(s->pb, dst, len);
132        dst[len] = 0;
133        break;
134    default:
135        av_log(s, AV_LOG_WARNING, "Unknown encoding in tag %s\n.", key);
136    }
137
138    if (!(strcmp(key, "TCON") && strcmp(key, "TCO"))
139        && (sscanf(dst, "(%d)", &genre) == 1 || sscanf(dst, "%d", &genre) == 1)
140        && genre <= ID3v1_GENRE_MAX)
141        val = ff_id3v1_genre_str[genre];
142    else if (!(strcmp(key, "TXXX") && strcmp(key, "TXX"))) {
143        /* dst now contains two 0-terminated strings */
144        dst[dstlen] = 0;
145        len = strlen(dst);
146        key = dst;
147        val = dst + FFMIN(len + 1, dstlen);
148    }
149    else if (*dst)
150        val = dst;
151
152    if (val)
153        av_metadata_set2(&s->metadata, key, val, 0);
154}
155
156void ff_id3v2_parse(AVFormatContext *s, int len, uint8_t version, uint8_t flags)
157{
158    int isv34, tlen;
159    char tag[5];
160    int64_t next;
161    int taghdrlen;
162    const char *reason;
163
164    switch (version) {
165    case 2:
166        if (flags & 0x40) {
167            reason = "compression";
168            goto error;
169        }
170        isv34 = 0;
171        taghdrlen = 6;
172        break;
173
174    case 3:
175    case 4:
176        isv34 = 1;
177        taghdrlen = 10;
178        break;
179
180    default:
181        reason = "version";
182        goto error;
183    }
184
185    if (flags & 0x80) {
186        reason = "unsynchronization";
187        goto error;
188    }
189
190    if (isv34 && flags & 0x40) /* Extended header present, just skip over it */
191        url_fskip(s->pb, get_size(s->pb, 4));
192
193    while (len >= taghdrlen) {
194        if (isv34) {
195            get_buffer(s->pb, tag, 4);
196            tag[4] = 0;
197            if(version==3){
198                tlen = get_be32(s->pb);
199            }else
200                tlen = get_size(s->pb, 4);
201            get_be16(s->pb); /* flags */
202        } else {
203            get_buffer(s->pb, tag, 3);
204            tag[3] = 0;
205            tlen = get_be24(s->pb);
206        }
207        len -= taghdrlen + tlen;
208
209        if (len < 0)
210            break;
211
212        next = url_ftell(s->pb) + tlen;
213
214        if (tag[0] == 'T')
215            read_ttag(s, tlen, tag);
216        else if (!tag[0]) {
217            if (tag[1])
218                av_log(s, AV_LOG_WARNING, "invalid frame id, assuming padding");
219            url_fskip(s->pb, len);
220            break;
221        }
222        /* Skip to end of tag */
223        url_fseek(s->pb, next, SEEK_SET);
224    }
225
226    if (version == 4 && flags & 0x10) /* Footer preset, always 10 bytes, skip over it */
227        url_fskip(s->pb, 10);
228    return;
229
230  error:
231    av_log(s, AV_LOG_INFO, "ID3v2.%d tag skipped, cannot handle %s\n", version, reason);
232    url_fskip(s->pb, len);
233}
234
235const AVMetadataConv ff_id3v2_metadata_conv[] = {
236    { "TALB", "album"},
237    { "TAL",  "album"},
238    { "TCOM", "composer"},
239    { "TCON", "genre"},
240    { "TCO",  "genre"},
241    { "TCOP", "copyright"},
242    { "TDRL", "date"},
243    { "TDRC", "date"},
244    { "TENC", "encoded_by"},
245    { "TEN",  "encoded_by"},
246    { "TIT2", "title"},
247    { "TT2",  "title"},
248    { "TLAN", "language"},
249    { "TPE1", "artist"},
250    { "TP1",  "artist"},
251    { "TPE2", "album_artist"},
252    { "TP2",  "album_artist"},
253    { "TPE3", "performer"},
254    { "TP3",  "performer"},
255    { "TPOS", "disc"},
256    { "TPUB", "publisher"},
257    { "TRCK", "track"},
258    { "TRK",  "track"},
259    { "TSOA", "album-sort"},
260    { "TSOP", "artist-sort"},
261    { "TSOT", "title-sort"},
262    { "TSSE", "encoder"},
263    { 0 }
264};
265
266const char ff_id3v2_tags[][4] = {
267   "TALB", "TBPM", "TCOM", "TCON", "TCOP", "TDEN", "TDLY", "TDOR", "TDRC",
268   "TDRL", "TDTG", "TENC", "TEXT", "TFLT", "TIPL", "TIT1", "TIT2", "TIT3",
269   "TKEY", "TLAN", "TLEN", "TMCL", "TMED", "TMOO", "TOAL", "TOFN", "TOLY",
270   "TOPE", "TOWN", "TPE1", "TPE2", "TPE3", "TPE4", "TPOS", "TPRO", "TPUB",
271   "TRCK", "TRSN", "TRSO", "TSOA", "TSOP", "TSOT", "TSRC", "TSSE", "TSST",
272   { 0 },
273};
274