1/*
2Copyright (c) 2012, Broadcom Europe Ltd
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7 * Redistributions of source code must retain the above copyright
8 notice, this list of conditions and the following disclaimer.
9 * Redistributions in binary form must reproduce the above copyright
10 notice, this list of conditions and the following disclaimer in the
11 documentation and/or other materials provided with the distribution.
12 * Neither the name of the copyright holder nor the
13 names of its contributors may be used to endorse or promote products
14 derived from this software without specific prior written permission.
15
16THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
20DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27#include <stdlib.h>
28#include <stdio.h>
29#include <string.h>
30
31#define CONTAINER_IS_BIG_ENDIAN
32//#define ENABLE_CONTAINERS_LOG_FORMAT
33//#define ENABLE_CONTAINERS_LOG_FORMAT_VERBOSE
34#define CONTAINER_HELPER_LOG_INDENT(a) 0
35#include "containers/core/containers_private.h"
36#include "containers/core/containers_io_helpers.h"
37#include "containers/core/containers_utils.h"
38#include "containers/core/containers_logging.h"
39
40#include "id3_metadata_strings.h"
41
42/******************************************************************************
43Defines
44******************************************************************************/
45#define ID3_SYNC_SAFE(x) ((((x >> 24) & 0x7f) << 21) | (((x >> 16) & 0x7f) << 14) | \
46 (((x >> 8) & 0x7f) << 7) | (((x >> 0) & 0x7f) << 0))
47
48/******************************************************************************
49Type definitions
50******************************************************************************/
51
52/******************************************************************************
53Function prototypes
54******************************************************************************/
55VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T * );
56
57/******************************************************************************
58Local Functions
59******************************************************************************/
60static VC_CONTAINER_METADATA_T *id3_metadata_append( VC_CONTAINER_T *p_ctx,
61 VC_CONTAINER_METADATA_KEY_T key,
62 unsigned int size )
63{
64 VC_CONTAINER_METADATA_T *meta, **p_meta;
65 unsigned int i;
66
67 for (i = 0; i != p_ctx->meta_num; ++i)
68 {
69 if (key == p_ctx->meta[i]->key) break;
70 }
71
72 /* Avoid duplicate entries for now */
73 if (i < p_ctx->meta_num) return NULL;
74
75 /* Sanity check size, truncate if necessary */
76 size = MIN(size, 512);
77
78 /* Allocate a new metadata entry */
79 if((meta = malloc(sizeof(VC_CONTAINER_METADATA_T) + size)) == NULL)
80 return NULL;
81
82 /* We need to grow the array holding the metadata entries somehow, ideally,
83 we'd like to use a linked structure of some sort but realloc is probably
84 okay in this case */
85 if((p_meta = realloc(p_ctx->meta, sizeof(VC_CONTAINER_METADATA_T *) * (p_ctx->meta_num + 1))) == NULL)
86 {
87 free(meta);
88 return NULL;
89 }
90
91 p_ctx->meta = p_meta;
92 memset(meta, 0, sizeof(VC_CONTAINER_METADATA_T) + size);
93 p_ctx->meta[p_ctx->meta_num] = meta;
94 meta->key = key;
95 meta->value = (char *)&meta[1];
96 meta->size = size;
97 p_ctx->meta_num++;
98
99 return meta;
100}
101
102/*****************************************************************************/
103static VC_CONTAINER_METADATA_T *id3_read_metadata_entry( VC_CONTAINER_T *p_ctx,
104 VC_CONTAINER_METADATA_KEY_T key, unsigned int len )
105{
106 VC_CONTAINER_METADATA_T *meta;
107
108 if ((meta = id3_metadata_append(p_ctx, key, len + 1)) != NULL)
109 {
110 unsigned int size = meta->size - 1;
111 READ_BYTES(p_ctx, meta->value, size);
112
113 if (len > size)
114 {
115 LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size);
116 SKIP_BYTES(p_ctx, len - size);
117 }
118 }
119 else
120 {
121 SKIP_BYTES(p_ctx, len);
122 }
123
124 return meta;
125}
126
127/*****************************************************************************/
128static VC_CONTAINER_METADATA_T *id3_read_metadata_entry_ex( VC_CONTAINER_T *p_ctx,
129 VC_CONTAINER_METADATA_KEY_T key, unsigned int len, const char *encoding )
130{
131 VC_CONTAINER_METADATA_T *meta;
132
133 if ((meta = id3_metadata_append(p_ctx, key, encoding ? len + 2 : len + 1)) != NULL)
134 {
135 unsigned int size;
136
137 if (encoding)
138 {
139 size = meta->size - 2;
140 READ_STRING_UTF16(p_ctx, meta->value, size, "ID3v2 data");
141 }
142 else
143 {
144 size = meta->size - 1;
145 READ_STRING(p_ctx, meta->value, size, "ID3v2 data");
146 }
147
148 if (len > size)
149 {
150 LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size);
151 SKIP_BYTES(p_ctx, len - size);
152 }
153 }
154
155 return meta;
156}
157
158/*****************************************************************************/
159static VC_CONTAINER_METADATA_T *id3_add_metadata_entry( VC_CONTAINER_T *p_ctx,
160 VC_CONTAINER_METADATA_KEY_T key, const char *value )
161{
162 VC_CONTAINER_METADATA_T *meta;
163 unsigned int len = strlen(value);
164
165 if ((meta = id3_metadata_append(p_ctx, key, len + 1)) != NULL)
166 {
167 unsigned int size = meta->size - 1;
168
169 if (len > size)
170 {
171 LOG_DEBUG(p_ctx, "metadata value truncated (%d characters lost)", len - size);
172 }
173
174 strncpy(meta->value, value, size);
175 }
176
177 return meta;
178}
179
180/*****************************************************************************/
181static VC_CONTAINER_STATUS_T id3_read_id3v2_frame( VC_CONTAINER_T *p_ctx,
182 VC_CONTAINER_FOURCC_T frame_id, uint32_t frame_size )
183{
184 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
185 VC_CONTAINER_METADATA_KEY_T key;
186 VC_CONTAINER_METADATA_T *meta = NULL;
187 uint8_t encoding;
188 const char *charset = NULL;
189
190 if(frame_size < 1) return VC_CONTAINER_ERROR_CORRUPTED;
191
192 switch (frame_id)
193 {
194 case VC_FOURCC('T','A','L','B'): key = VC_CONTAINER_METADATA_KEY_ALBUM; break;
195 case VC_FOURCC('T','I','T','2'): key = VC_CONTAINER_METADATA_KEY_TITLE; break;
196 case VC_FOURCC('T','R','C','K'): key = VC_CONTAINER_METADATA_KEY_TRACK; break;
197 case VC_FOURCC('T','P','E','1'): key = VC_CONTAINER_METADATA_KEY_ARTIST; break;
198 case VC_FOURCC('T','C','O','N'): key = VC_CONTAINER_METADATA_KEY_GENRE; break;
199 default: key = VC_CONTAINER_METADATA_KEY_UNKNOWN; break;
200 }
201
202 if (key == VC_CONTAINER_METADATA_KEY_UNKNOWN) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
203
204 encoding = READ_U8(p_ctx, "ID3v2 text encoding byte");
205 frame_size -= 1;
206
207 switch(encoding)
208 {
209 case 0: /* ISO-8859-1 */
210 case 3: /* UTF-8 */
211 break;
212 case 1: /* UTF-16 with BOM */
213 if(frame_size < 2) return VC_CONTAINER_ERROR_CORRUPTED;
214 SKIP_U16(p_ctx, "ID3v2 text encoding BOM"); /* FIXME: Check BOM, 0xFFFE vs 0xFEFFF */
215 frame_size -= 2;
216 charset = "UTF16-LE";
217 break;
218 case 2: /* UTF-16BE */
219 charset = "UTF16-BE";
220 break;
221 default:
222 LOG_DEBUG(p_ctx, "skipping frame, text encoding %x not supported", encoding);
223 SKIP_BYTES(p_ctx, frame_size);
224 return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
225 }
226
227 if ((meta = id3_read_metadata_entry_ex(p_ctx, key, frame_size, charset)) != NULL)
228 {
229 if (charset)
230 {
231 utf8_from_charset(charset, meta->value, meta->size, meta->value, meta->size);
232 }
233
234 meta->encoding = VC_CONTAINER_CHAR_ENCODING_UTF8; /* Okay for ISO-8859-1 as well? */
235
236 status = VC_CONTAINER_SUCCESS;
237 }
238 else
239 {
240 SKIP_BYTES(p_ctx, frame_size);
241 }
242
243 return status;
244}
245
246/*****************************************************************************/
247static VC_CONTAINER_STATUS_T id3_read_id3v2_tag( VC_CONTAINER_T *p_ctx )
248{
249 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
250 uint8_t maj_version, flags;
251 uint32_t tag_size, size = 0;
252 uint8_t peek_buf[10];
253
254 SKIP_STRING(p_ctx, 3, "ID3v2 identifier");
255 maj_version = READ_U8(p_ctx, "ID3v2 version (major)");
256 SKIP_U8(p_ctx, "ID3v2 version (minor)");
257 flags = READ_U8(p_ctx, "ID3v2 flags");
258 tag_size = READ_U32(p_ctx, "ID3v2 syncsafe tag size");
259 tag_size = ID3_SYNC_SAFE(tag_size);
260 LOG_DEBUG(p_ctx, "ID3v2 tag size: %d", tag_size);
261
262 /* Check that we support this major version */
263 if (!(maj_version == 4 || maj_version == 3 || maj_version == 2))
264 return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
265
266 /* We can't currently handle unsynchronisation */
267 if ((flags >> 7) & 1)
268 {
269 LOG_DEBUG(p_ctx, "skipping unsynchronised tag, not supported");
270 return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
271 }
272
273 /* FIXME: check for version 2.2 and extract iTunes gapless playback information */
274 if (maj_version == 2) return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
275
276 if ((flags >> 6) & 1)
277 {
278 /* Skip extended header, we don't support it */
279 uint32_t ext_hdr_size;
280 LOG_DEBUG(p_ctx, "skipping ID3v2 extended header, not supported");
281 ext_hdr_size = READ_U32(p_ctx, "ID3v2 syncsafe extended header size");
282 ext_hdr_size = ID3_SYNC_SAFE(ext_hdr_size);
283 LOG_DEBUG(p_ctx, "ID3v2 extended header size: %d", ext_hdr_size);
284 SKIP_BYTES(p_ctx, MIN(tag_size, ext_hdr_size));
285 size += ext_hdr_size;
286 }
287
288 while (PEEK_BYTES(p_ctx, peek_buf, 10) == 10 && size < tag_size)
289 {
290 VC_CONTAINER_FOURCC_T frame_id;
291 uint32_t frame_size;
292 uint8_t format_flags;
293
294 frame_id = READ_FOURCC(p_ctx, "Frame ID");
295 frame_size = READ_U32(p_ctx, "Frame Size");
296
297 if (maj_version >= 4)
298 {
299 frame_size = ID3_SYNC_SAFE(frame_size);
300 LOG_DEBUG(p_ctx, "ID3v2 actual frame size: %d", frame_size);
301 }
302
303 SKIP_U8(p_ctx, "ID3v2 status message flags");
304 format_flags = READ_U8(p_ctx, "ID3v2 format description flags");
305
306 size += 10;
307
308 if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS || !frame_id)
309 break;
310
311 /* Early exit if we detect an invalid tag size */
312 if (size + frame_size > tag_size)
313 {
314 status = VC_CONTAINER_ERROR_FORMAT_INVALID;
315 break;
316 }
317
318 /* We can't currently handle unsynchronised frames */
319 if ((format_flags >> 1) & 1)
320 {
321 LOG_DEBUG(p_ctx, "skipping unsynchronised frame, not supported");
322 SKIP_BYTES(p_ctx, frame_size);
323 continue;
324 }
325
326 if ((status = id3_read_id3v2_frame(p_ctx, frame_id, frame_size)) != VC_CONTAINER_SUCCESS)
327 {
328 LOG_DEBUG(p_ctx, "skipping unsupported frame");
329 SKIP_BYTES(p_ctx, frame_size);
330 }
331
332 size += frame_size;
333 }
334
335 /* Try to skip to end of tag in case we bailed out early */
336 if (size < tag_size) SKIP_BYTES(p_ctx, tag_size - size);
337
338 return status;
339}
340
341/*****************************************************************************/
342static VC_CONTAINER_STATUS_T id3_read_id3v1_tag( VC_CONTAINER_T *p_ctx )
343{
344 VC_CONTAINER_STATUS_T status = VC_CONTAINER_SUCCESS;
345 uint8_t track, genre;
346 char track_num[4] = {0};
347
348 SKIP_STRING(p_ctx, 3, "ID3v1 identifier");
349 /* ID3v1 title */
350 id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_TITLE, 30);
351 /* ID3v1 artist */
352 id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_ARTIST, 30);
353 /* ID3v1 album */
354 id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_ALBUM, 30);
355 /* ID3v1 year */
356 id3_read_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_YEAR, 4);
357 SKIP_STRING(p_ctx, 28, "ID3v1 comment");
358 if (READ_U8(p_ctx, "ID3v1 zero-byte") == 0)
359 {
360 track = READ_U8(p_ctx, "ID3v1 track");
361 snprintf(track_num, sizeof(track_num) - 1, "%02d", track);
362 id3_add_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_TRACK, track_num);
363 }
364 else
365 {
366 SKIP_BYTES(p_ctx, 1);
367 }
368 genre = READ_U8(p_ctx, "ID3v1 genre");
369 if (genre < countof(id3_genres))
370 {
371 id3_add_metadata_entry(p_ctx, VC_CONTAINER_METADATA_KEY_GENRE, id3_genres[genre]);
372 }
373
374 status = STREAM_STATUS(p_ctx);
375
376 return status;
377}
378
379/*****************************************************************************
380Functions exported as part of the Container Module API
381 *****************************************************************************/
382
383/*****************************************************************************/
384static VC_CONTAINER_STATUS_T id3_metadata_reader_close( VC_CONTAINER_T *p_ctx )
385{
386 VC_CONTAINER_PARAM_UNUSED(p_ctx);
387 return VC_CONTAINER_SUCCESS;
388}
389
390/*****************************************************************************/
391VC_CONTAINER_STATUS_T id3_metadata_reader_open( VC_CONTAINER_T *p_ctx )
392{
393 VC_CONTAINER_STATUS_T status = VC_CONTAINER_ERROR_FORMAT_INVALID;
394 uint8_t peek_buf[10];
395 int64_t data_offset;
396
397 if (PEEK_BYTES(p_ctx, peek_buf, 10) != 10)
398 return VC_CONTAINER_ERROR_FORMAT_NOT_SUPPORTED;
399
400 /* Initial ID3v2 tag(s), variable size */
401 while ((peek_buf[0] == 'I') && (peek_buf[1] == 'D') && (peek_buf[2] == '3'))
402 {
403 if ((status = id3_read_id3v2_tag(p_ctx)) != VC_CONTAINER_SUCCESS)
404 {
405 LOG_DEBUG(p_ctx, "error reading ID3v2 tag (%i)", status);
406 }
407
408 if (PEEK_BYTES(p_ctx, peek_buf, 10) != 10) break;
409 }
410
411 data_offset = STREAM_POSITION(p_ctx);
412
413 /* ID3v1 tag, 128 bytes at the end of a file */
414 if (p_ctx->priv->io->size >= INT64_C(128) && STREAM_SEEKABLE(p_ctx))
415 {
416 SEEK(p_ctx, p_ctx->priv->io->size - INT64_C(128));
417 if (PEEK_BYTES(p_ctx, peek_buf, 3) != 3) goto end;
418
419 if ((peek_buf[0] == 'T') && (peek_buf[1] == 'A') && (peek_buf[2] == 'G'))
420 {
421 if ((status = id3_read_id3v1_tag(p_ctx)) != VC_CONTAINER_SUCCESS)
422 {
423 LOG_DEBUG(p_ctx, "error reading ID3v1 tag (%i)", status);
424 }
425 }
426 }
427
428end:
429 /* Restore position to start of data */
430 if (STREAM_POSITION(p_ctx) != data_offset)
431 SEEK(p_ctx, data_offset);
432
433 p_ctx->priv->pf_close = id3_metadata_reader_close;
434
435 if((status = STREAM_STATUS(p_ctx)) != VC_CONTAINER_SUCCESS) goto error;
436
437 return VC_CONTAINER_SUCCESS;
438
439error:
440 LOG_DEBUG(p_ctx, "error opening stream (%i)", status);
441 return status;
442}
443
444/********************************************************************************
445 Entrypoint function
446 ********************************************************************************/
447
448#if !defined(ENABLE_CONTAINERS_STANDALONE) && defined(__HIGHC__)
449# pragma weak reader_open id3_metadata_reader_open
450#endif
451