summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcoen Hirschberg <marcoen@gmail.com>2007-06-16 18:19:51 +0000
committerMarcoen Hirschberg <marcoen@gmail.com>2007-06-16 18:19:51 +0000
commit2175d1edf65367fd3fe3cff266b8d6ea12930f2f (patch)
tree9d6f50ebfc7919b4e2f0853fc8c0cb844cabe995
parentc3206a455a455fadb282d09f9af482c66b6bdf8e (diff)
downloadrockbox-2175d1edf65367fd3fe3cff266b8d6ea12930f2f.tar.gz
rockbox-2175d1edf65367fd3fe3cff266b8d6ea12930f2f.zip
split up the metadata parser
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13637 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/FILES1
-rw-r--r--apps/SOURCES15
-rw-r--r--apps/metadata.c2154
-rw-r--r--apps/metadata/adx.c115
-rw-r--r--apps/metadata/aiff.c98
-rw-r--r--apps/metadata/ape.c132
-rw-r--r--apps/metadata/flac.c122
-rw-r--r--apps/metadata/metadata_common.c273
-rw-r--r--apps/metadata/metadata_common.h45
-rw-r--r--apps/metadata/metadata_parsers.h31
-rw-r--r--apps/metadata/monkeys.c92
-rw-r--r--apps/metadata/mp4.c669
-rw-r--r--apps/metadata/mpc.c118
-rw-r--r--apps/metadata/sid.c88
-rw-r--r--apps/metadata/spc.c126
-rw-r--r--apps/metadata/speex.c212
-rw-r--r--apps/metadata/vorbis.c330
-rw-r--r--apps/metadata/wave.c128
18 files changed, 2601 insertions, 2148 deletions
diff --git a/apps/FILES b/apps/FILES
index 936e817fd3..85d70cbe74 100644
--- a/apps/FILES
+++ b/apps/FILES
@@ -35,6 +35,7 @@ gui/*.[ch]
keymaps/*.[ch]
lang/*.lang
menus/*.[ch]
+metadata/*.[ch]
player/*.[ch]
plugins/*.[ch]
plugins/*.pl
diff --git a/apps/SOURCES b/apps/SOURCES
index ccb9ca2772..a48017c7cd 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -106,6 +106,21 @@ eq_arm.S
#endif
#endif
metadata.c
+#if CONFIG_CODEC == SWCODEC
+metadata/metadata_common.c
+metadata/aiff.c
+metadata/ape.c
+metadata/adx.c
+metadata/flac.c
+metadata/monkeys.c
+metadata/mp4.c
+metadata/mpc.c
+metadata/sid.c
+metadata/spc.c
+metadata/speex.c
+metadata/vorbis.c
+metadata/wave.c
+#endif
#ifdef HAVE_TAGCACHE
tagcache.c
#endif
diff --git a/apps/metadata.c b/apps/metadata.c
index a0c2f31eee..3b2855bc24 100644
--- a/apps/metadata.c
+++ b/apps/metadata.c
@@ -22,84 +22,17 @@
#include <ctype.h>
#include <inttypes.h>
-#include "errno.h"
-#include "metadata.h"
-#include "mp3_playback.h"
-#include "logf.h"
-#include "rbunicode.h"
-#include "atoi.h"
-#include "replaygain.h"
-#include "debug.h"
#include "system.h"
+#include "playback.h"
+#include "debug.h"
+#include "logf.h"
#include "cuesheet.h"
-#include "structec.h"
-
-enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
-
-#ifdef ROCKBOX_BIG_ENDIAN
-#define IS_BIG_ENDIAN 1
-#else
-#define IS_BIG_ENDIAN 0
-#endif
-#define APETAG_HEADER_LENGTH 32
-#define APETAG_HEADER_FORMAT "8llll8"
-#define APETAG_ITEM_HEADER_FORMAT "ll"
-#define APETAG_ITEM_TYPE_MASK 3
-
-#define TAG_NAME_LENGTH 32
-#define TAG_VALUE_LENGTH 128
-
-#define MP4_ID(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
-
-#define MP4_3gp6 MP4_ID('3', 'g', 'p', '6')
-#define MP4_alac MP4_ID('a', 'l', 'a', 'c')
-#define MP4_calb MP4_ID(0xa9, 'a', 'l', 'b')
-#define MP4_cART MP4_ID(0xa9, 'A', 'R', 'T')
-#define MP4_cnam MP4_ID(0xa9, 'n', 'a', 'm')
-#define MP4_cwrt MP4_ID(0xa9, 'w', 'r', 't')
-#define MP4_esds MP4_ID('e', 's', 'd', 's')
-#define MP4_ftyp MP4_ID('f', 't', 'y', 'p')
-#define MP4_gnre MP4_ID('g', 'n', 'r', 'e')
-#define MP4_hdlr MP4_ID('h', 'd', 'l', 'r')
-#define MP4_ilst MP4_ID('i', 'l', 's', 't')
-#define MP4_M4A MP4_ID('M', '4', 'A', ' ')
-#define MP4_M4B MP4_ID('M', '4', 'B', ' ')
-#define MP4_mdat MP4_ID('m', 'd', 'a', 't')
-#define MP4_mdia MP4_ID('m', 'd', 'i', 'a')
-#define MP4_mdir MP4_ID('m', 'd', 'i', 'r')
-#define MP4_meta MP4_ID('m', 'e', 't', 'a')
-#define MP4_minf MP4_ID('m', 'i', 'n', 'f')
-#define MP4_moov MP4_ID('m', 'o', 'o', 'v')
-#define MP4_mp4a MP4_ID('m', 'p', '4', 'a')
-#define MP4_mp42 MP4_ID('m', 'p', '4', '2')
-#define MP4_qt MP4_ID('q', 't', ' ', ' ')
-#define MP4_soun MP4_ID('s', 'o', 'u', 'n')
-#define MP4_stbl MP4_ID('s', 't', 'b', 'l')
-#define MP4_stsd MP4_ID('s', 't', 's', 'd')
-#define MP4_stts MP4_ID('s', 't', 't', 's')
-#define MP4_trak MP4_ID('t', 'r', 'a', 'k')
-#define MP4_trkn MP4_ID('t', 'r', 'k', 'n')
-#define MP4_udta MP4_ID('u', 'd', 't', 'a')
-#define MP4_extra MP4_ID('-', '-', '-', '-')
-
-struct apetag_header
-{
- char id[8];
- long version;
- long length;
- long item_count;
- long flags;
- char reserved[8];
-};
+#if CONFIG_CODEC == SWCODEC
-struct apetag_item_header
-{
- long length;
- long flags;
-};
+#include "metadata/metadata_common.h"
+#include "metadata/metadata_parsers.h"
-#if CONFIG_CODEC == SWCODEC
static const unsigned short a52_bitrates[] =
{
32, 40, 48, 56, 64, 80, 96, 112, 128, 160,
@@ -124,2081 +57,6 @@ static const long wavpack_sample_rates [] =
32000, 44100, 48000, 64000, 88200, 96000, 192000
};
-/* Read a string from the file. Read up to size bytes, or, if eos != -1,
- * until the eos character is found (eos is not stored in buf, unless it is
- * nil). Writes up to buf_size chars to buf, always terminating with a nil.
- * Returns number of chars read or -1 on read error.
- */
-static long read_string(int fd, char* buf, long buf_size, int eos, long size)
-{
- long read_bytes = 0;
- char c;
-
- while (size != 0)
- {
- if (read(fd, &c, 1) != 1)
- {
- read_bytes = -1;
- break;
- }
-
- read_bytes++;
- size--;
-
- if ((eos != -1) && (eos == (unsigned char) c))
- {
- break;
- }
-
- if (buf_size > 1)
- {
- *buf++ = c;
- buf_size--;
- }
- }
-
- *buf = 0;
- return read_bytes;
-}
-
-/* Read an unsigned 32-bit integer from a big-endian file. */
-#ifdef ROCKBOX_BIG_ENDIAN
-#define read_uint32be(fd,buf) read((fd), (buf), 4)
-#else
-static int read_uint32be(int fd, unsigned int* buf)
-{
- size_t n;
-
- n = read(fd, (char*) buf, 4);
- *buf = betoh32(*buf);
- return n;
-}
-#endif
-
-/* Read an unaligned 32-bit little endian long from buffer. */
-static unsigned long get_long_le(void* buf)
-{
- unsigned char* p = (unsigned char*) buf;
-
- return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
-}
-
-/* Read an unaligned 16-bit little endian short from buffer. */
-static unsigned short get_short_le(void* buf)
-{
- unsigned char* p = (unsigned char*) buf;
-
- return p[0] | (p[1] << 8);
-}
-
-/* Read an unaligned 32-bit big endian long from buffer. */
-static unsigned long get_long_be(void* buf)
-{
- unsigned char* p = (unsigned char*) buf;
-
- return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
-}
-
-/* Read an unaligned 32-bit little endian long from buffer. */
-static long get_slong(void* buf)
-{
- unsigned char* p = (unsigned char*) buf;
-
- return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
-}
-
-static char* skip_space(char* str)
-{
- while (isspace(*str))
- {
- str++;
- }
-
- return str;
-}
-
-static unsigned long get_itunes_int32(char* value, int count)
-{
- static const char hexdigits[] = "0123456789ABCDEF";
- const char* c;
- int r = 0;
-
- while (count-- > 0)
- {
- value = skip_space(value);
-
- while (*value && !isspace(*value))
- {
- value++;
- }
- }
-
- value = skip_space(value);
-
- while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL))
- {
- r = (r << 4) | (c - hexdigits);
- value++;
- }
-
- return r;
-}
-
-/* Parse the tag (the name-value pair) and fill id3 and buffer accordingly.
- * String values to keep are written to buf. Returns number of bytes written
- * to buf (including end nil).
- */
-static long parse_tag(const char* name, char* value, struct mp3entry* id3,
- char* buf, long buf_remaining, enum tagtype type)
-{
- long len = 0;
- char** p;
-
- if ((((strcasecmp(name, "track") == 0) && (type == TAGTYPE_APE)))
- || ((strcasecmp(name, "tracknumber") == 0) && (type == TAGTYPE_VORBIS)))
- {
- id3->tracknum = atoi(value);
- p = &(id3->track_string);
- }
- else if (((strcasecmp(name, "year") == 0) && (type == TAGTYPE_APE))
- || ((strcasecmp(name, "date") == 0) && (type == TAGTYPE_VORBIS)))
- {
- /* Date's can be in any format in Vorbis. However most of them
- * are in ISO8601 format so if we try and parse the first part
- * of the tag as a number, we should get the year. If we get crap,
- * then act like we never parsed it.
- */
- id3->year = atoi(value);
- if (id3->year < 1900)
- { /* yeah, not likely */
- id3->year = 0;
- }
- p = &(id3->year_string);
- }
- else if (strcasecmp(name, "title") == 0)
- {
- p = &(id3->title);
- }
- else if (strcasecmp(name, "artist") == 0)
- {
- p = &(id3->artist);
- }
- else if (strcasecmp(name, "album") == 0)
- {
- p = &(id3->album);
- }
- else if (strcasecmp(name, "genre") == 0)
- {
- p = &(id3->genre_string);
- }
- else if (strcasecmp(name, "composer") == 0)
- {
- p = &(id3->composer);
- }
- else if (strcasecmp(name, "comment") == 0)
- {
- p = &(id3->comment);
- }
- else if (strcasecmp(name, "albumartist") == 0)
- {
- p = &(id3->albumartist);
- }
- else if (strcasecmp(name, "album artist") == 0)
- {
- p = &(id3->albumartist);
- }
- else if (strcasecmp(name, "ensemble") == 0)
- {
- p = &(id3->albumartist);
- }
- else
- {
- len = parse_replaygain(name, value, id3, buf, buf_remaining);
- p = NULL;
- }
-
- if (p)
- {
- len = strlen(value);
- len = MIN(len, buf_remaining - 1);
-
- if (len > 0)
- {
- strncpy(buf, value, len);
- buf[len] = 0;
- *p = buf;
- len++;
- }
- else
- {
- len = 0;
- }
- }
-
- return len;
-}
-
-/* Read the items in an APEV2 tag. Only looks for a tag at the end of a
- * file. Returns true if a tag was found and fully read, false otherwise.
- */
-static bool read_ape_tags(int fd, struct mp3entry* id3)
-{
- struct apetag_header header;
-
- if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0)
- || (ecread(fd, &header, 1, APETAG_HEADER_FORMAT, IS_BIG_ENDIAN) != APETAG_HEADER_LENGTH)
- || (memcmp(header.id, "APETAGEX", sizeof(header.id))))
- {
- return false;
- }
-
- if ((header.version == 2000) && (header.item_count > 0)
- && (header.length > APETAG_HEADER_LENGTH))
- {
- char *buf = id3->id3v2buf;
- unsigned int buf_remaining = sizeof(id3->id3v2buf)
- + sizeof(id3->id3v1buf);
- unsigned int tag_remaining = header.length - APETAG_HEADER_LENGTH;
- int i;
-
- if (lseek(fd, -header.length, SEEK_END) < 0)
- {
- return false;
- }
-
- for (i = 0; i < header.item_count; i++)
- {
- struct apetag_item_header item;
- char name[TAG_NAME_LENGTH];
- char value[TAG_VALUE_LENGTH];
- long r;
-
- if (tag_remaining < sizeof(item))
- {
- break;
- }
-
- if (ecread(fd, &item, 1, APETAG_ITEM_HEADER_FORMAT, IS_BIG_ENDIAN) < (long) sizeof(item))
- {
- return false;
- }
-
- tag_remaining -= sizeof(item);
- r = read_string(fd, name, sizeof(name), 0, tag_remaining);
-
- if (r == -1)
- {
- return false;
- }
-
- tag_remaining -= r + item.length;
-
- if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0)
- {
- long len;
-
- if (read_string(fd, value, sizeof(value), -1, item.length)
- != item.length)
- {
- return false;
- }
-
- len = parse_tag(name, value, id3, buf, buf_remaining,
- TAGTYPE_APE);
- buf += len;
- buf_remaining -= len;
- }
- else
- {
- if (lseek(fd, item.length, SEEK_CUR) < 0)
- {
- return false;
- }
- }
- }
- }
-
- return true;
-}
-
-/* Read the items in a Vorbis comment packet. Returns true the items were
- * fully read, false otherwise.
- */
-static bool read_vorbis_tags(int fd, struct mp3entry *id3,
- long tag_remaining)
-{
- char *buf = id3->id3v2buf;
- int32_t comment_count;
- int32_t len;
- int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
- int i;
-
- if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
- {
- return false;
- }
-
- if ((lseek(fd, len, SEEK_CUR) < 0)
- || (ecread(fd, &comment_count, 1, "l", IS_BIG_ENDIAN)
- < (long) sizeof(comment_count)))
- {
- return false;
- }
-
- tag_remaining -= len + sizeof(len) + sizeof(comment_count);
-
- if (tag_remaining <= 0)
- {
- return true;
- }
-
- for (i = 0; i < comment_count; i++)
- {
- char name[TAG_NAME_LENGTH];
- char value[TAG_VALUE_LENGTH];
- int32_t read_len;
-
- if (tag_remaining < 4)
- {
- break;
- }
-
- if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
- {
- return false;
- }
-
- tag_remaining -= 4;
-
- /* Quit if we've passed the end of the page */
- if (tag_remaining < len)
- {
- break;
- }
-
- tag_remaining -= len;
- read_len = read_string(fd, name, sizeof(name), '=', len);
-
- if (read_len < 0)
- {
- return false;
- }
-
- len -= read_len;
-
- if (read_string(fd, value, sizeof(value), -1, len) < 0)
- {
- return false;
- }
-
- len = parse_tag(name, value, id3, buf, buf_remaining,
- TAGTYPE_VORBIS);
- buf += len;
- buf_remaining -= len;
- }
-
- /* Skip to the end of the block */
- if (tag_remaining)
- {
- if (lseek(fd, tag_remaining, SEEK_CUR) < 0)
- {
- return false;
- }
- }
-
- return true;
-}
-
-/* Skip an ID3v2 tag if it can be found. We assume the tag is located at the
- * start of the file, which should be true in all cases where we need to skip it.
- * Returns true if successfully skipped or not skipped, and false if
- * something went wrong while skipping.
- */
-static bool skip_id3v2(int fd, struct mp3entry *id3)
-{
- char buf[4];
-
- read(fd, buf, 4);
- if (memcmp(buf, "ID3", 3) == 0)
- {
- /* We have found an ID3v2 tag at the start of the file - find its
- length and then skip it. */
- if ((id3->first_frame_offset = getid3v2len(fd)) == 0)
- return false;
-
- if ((lseek(fd, id3->first_frame_offset, SEEK_SET) < 0))
- return false;
-
- return true;
- } else {
- lseek(fd, 0, SEEK_SET);
- id3->first_frame_offset = 0;
- return true;
- }
-}
-
-/* A simple parser to read vital metadata from an Ogg Speex file. Returns
- * false if metadata needed by the Speex codec couldn't be read.
- */
-
-static bool get_speex_metadata(int fd, struct mp3entry* id3)
-{
- /* An Ogg File is split into pages, each starting with the string
- * "OggS". Each page has a timestamp (in PCM samples) referred to as
- * the "granule position".
- *
- * An Ogg Speex has the following structure:
- * 1) Identification header (containing samplerate, numchannels, etc)
- Described in this page: (http://www.speex.org/manual2/node7.html)
- * 2) Comment header - containing the Vorbis Comments
- * 3) Many audio packets...
- */
-
- /* Use the path name of the id3 structure as a temporary buffer. */
- unsigned char* buf = (unsigned char*)id3->path;
- long comment_size;
- long remaining = 0;
- long last_serial = 0;
- long serial, r;
- int segments;
- int i;
- bool eof = false;
-
- if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 33))
- {
- return false;
- }
-
- if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 0))
- {
- return false;
- }
-
- /* We need to ensure the serial number from this page is the same as the
- * one from the last page (since we only support a single bitstream).
- */
- serial = get_long_le(&buf[14]);
- if ((lseek(fd, 33, SEEK_SET) < 0)||(read(fd, buf, 58) < 4))
- {
- return false;
- }
-
- id3->frequency = get_slong(&buf[31]);
- last_serial = get_long_le(&buf[27]);/*temporary, header size*/
- id3->bitrate = get_long_le(&buf[47]);
- id3->vbr = get_long_le(&buf[55]);
- id3->filesize = filesize(fd);
- /* Comments are in second Ogg page */
- if (lseek(fd, 28+last_serial/*(temporary for header size)*/, SEEK_SET) < 0)
- {
- return false;
- }
-
- /* Minimum header length for Ogg pages is 27. */
- if (read(fd, buf, 27) < 27)
- {
- return false;
- }
-
- if (memcmp(buf, "OggS", 4) !=0 )
- {
- return false;
- }
-
- segments = buf[26];
- /* read in segment table */
- if (read(fd, buf, segments) < segments)
- {
- return false;
- }
-
- /* The second packet in a vorbis stream is the comment packet. It *may*
- * extend beyond the second page, but usually does not. Here we find the
- * length of the comment packet (or the rest of the page if the comment
- * packet extends to the third page).
- */
- for (i = 0; i < segments; i++)
- {
- remaining += buf[i];
- /* The last segment of a packet is always < 255 bytes */
- if (buf[i] < 255)
- {
- break;
- }
- }
-
- comment_size = remaining;
-
- /* Failure to read the tags isn't fatal. */
- read_vorbis_tags(fd, id3, remaining);
-
- /* We now need to search for the last page in the file - identified by
- * by ('O','g','g','S',0) and retrieve totalsamples.
- */
-
- /* A page is always < 64 kB */
- if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
- {
- return false;
- }
-
- remaining = 0;
-
- while (!eof)
- {
- r = read(fd, &buf[remaining], MAX_PATH - remaining);
-
- if (r <= 0)
- {
- eof = true;
- }
- else
- {
- remaining += r;
- }
-
- /* Inefficient (but simple) search */
- i = 0;
-
- while (i < (remaining - 3))
- {
- if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
- {
- if (i < (remaining - 17))
- {
- /* Note that this only reads the low 32 bits of a
- * 64 bit value.
- */
- id3->samples = get_long_le(&buf[i + 6]);
- last_serial = get_long_le(&buf[i + 14]);
-
- /* If this page is very small the beginning of the next
- * header could be in buffer. Jump near end of this header
- * and continue */
- i += 27;
- }
- else
- {
- break;
- }
- }
- else
- {
- i++;
- }
- }
-
- if (i < remaining)
- {
- /* Move the remaining bytes to start of buffer.
- * Reuse var 'segments' as it is no longer needed */
- segments = 0;
- while (i < remaining)
- {
- buf[segments++] = buf[i++];
- }
- remaining = segments;
- }
- else
- {
- /* Discard the rest of the buffer */
- remaining = 0;
- }
- }
-
- /* This file has mutiple vorbis bitstreams (or is corrupt). */
- /* FIXME we should display an error here. */
- if (serial != last_serial)
- {
- logf("serialno mismatch");
- logf("%ld", serial);
- logf("%ld", last_serial);
- return false;
- }
-
- id3->length = (id3->samples / id3->frequency) * 1000;
- id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
- return true;
-}
-
-
-/* A simple parser to read vital metadata from an Ogg Vorbis file.
- * Calls get_speex_metadata if a speex file is identified. Returns
- * false if metadata needed by the Vorbis codec couldn't be read.
- */
-static bool get_vorbis_metadata(int fd, struct mp3entry* id3)
-{
- /* An Ogg File is split into pages, each starting with the string
- * "OggS". Each page has a timestamp (in PCM samples) referred to as
- * the "granule position".
- *
- * An Ogg Vorbis has the following structure:
- * 1) Identification header (containing samplerate, numchannels, etc)
- * 2) Comment header - containing the Vorbis Comments
- * 3) Setup header - containing codec setup information
- * 4) Many audio packets...
- */
-
- /* Use the path name of the id3 structure as a temporary buffer. */
- unsigned char* buf = (unsigned char *)id3->path;
- long comment_size;
- long remaining = 0;
- long last_serial = 0;
- long serial, r;
- int segments;
- int i;
- bool eof = false;
-
- if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 4))
- {
- return false;
- }
-
- if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[29], "vorbis", 6) != 0))
- {
- if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 0))
- {
- return false;
- }
- else
- {
- id3->codectype = AFMT_SPEEX;
- return get_speex_metadata(fd, id3);
- }
- }
-
- /* We need to ensure the serial number from this page is the same as the
- * one from the last page (since we only support a single bitstream).
- */
- serial = get_long_le(&buf[14]);
- id3->frequency = get_long_le(&buf[40]);
- id3->filesize = filesize(fd);
-
- /* Comments are in second Ogg page */
- if (lseek(fd, 58, SEEK_SET) < 0)
- {
- return false;
- }
-
- /* Minimum header length for Ogg pages is 27. */
- if (read(fd, buf, 27) < 27)
- {
- return false;
- }
-
- if (memcmp(buf, "OggS", 4) !=0 )
- {
- return false;
- }
-
- segments = buf[26];
-
- /* read in segment table */
- if (read(fd, buf, segments) < segments)
- {
- return false;
- }
-
- /* The second packet in a vorbis stream is the comment packet. It *may*
- * extend beyond the second page, but usually does not. Here we find the
- * length of the comment packet (or the rest of the page if the comment
- * packet extends to the third page).
- */
- for (i = 0; i < segments; i++)
- {
- remaining += buf[i];
-
- /* The last segment of a packet is always < 255 bytes */
- if (buf[i] < 255)
- {
- break;
- }
- }
-
- /* Now read in packet header (type and id string) */
- if (read(fd, buf, 7) < 7)
- {
- return false;
- }
-
- comment_size = remaining;
- remaining -= 7;
-
- /* The first byte of a packet is the packet type; comment packets are
- * type 3.
- */
- if ((buf[0] != 3) || (memcmp(buf + 1, "vorbis", 6) !=0))
- {
- return false;
- }
-
- /* Failure to read the tags isn't fatal. */
- read_vorbis_tags(fd, id3, remaining);
-
- /* We now need to search for the last page in the file - identified by
- * by ('O','g','g','S',0) and retrieve totalsamples.
- */
-
- /* A page is always < 64 kB */
- if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
- {
- return false;
- }
-
- remaining = 0;
-
- while (!eof)
- {
- r = read(fd, &buf[remaining], MAX_PATH - remaining);
-
- if (r <= 0)
- {
- eof = true;
- }
- else
- {
- remaining += r;
- }
-
- /* Inefficient (but simple) search */
- i = 0;
-
- while (i < (remaining - 3))
- {
- if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
- {
- if (i < (remaining - 17))
- {
- /* Note that this only reads the low 32 bits of a
- * 64 bit value.
- */
- id3->samples = get_long_le(&buf[i + 6]);
- last_serial = get_long_le(&buf[i + 14]);
-
- /* If this page is very small the beginning of the next
- * header could be in buffer. Jump near end of this header
- * and continue */
- i += 27;
- }
- else
- {
- break;
- }
- }
- else
- {
- i++;
- }
- }
-
- if (i < remaining)
- {
- /* Move the remaining bytes to start of buffer.
- * Reuse var 'segments' as it is no longer needed */
- segments = 0;
- while (i < remaining)
- {
- buf[segments++] = buf[i++];
- }
- remaining = segments;
- }
- else
- {
- /* Discard the rest of the buffer */
- remaining = 0;
- }
- }
-
- /* This file has mutiple vorbis bitstreams (or is corrupt). */
- /* FIXME we should display an error here. */
- if (serial != last_serial)
- {
- logf("serialno mismatch");
- logf("%ld", serial);
- logf("%ld", last_serial);
- return false;
- }
-
- id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
-
- if (id3->length <= 0)
- {
- logf("ogg length invalid!");
- return false;
- }
-
- id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
- id3->vbr = true;
-
- return true;
-}
-
-static bool get_flac_metadata(int fd, struct mp3entry* id3)
-{
- /* A simple parser to read vital metadata from a FLAC file - length,
- * frequency, bitrate etc. This code should either be moved to a
- * seperate file, or discarded in favour of the libFLAC code.
- * The FLAC stream specification can be found at
- * http://flac.sourceforge.net/format.html#stream
- */
-
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char* buf = (unsigned char *)id3->path;
- bool rc = false;
-
- if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4))
- {
- return rc;
- }
-
- if (memcmp(buf, "fLaC", 4) != 0)
- {
- return rc;
- }
-
- while (true)
- {
- long i;
-
- if (read(fd, buf, 4) < 0)
- {
- return rc;
- }
-
- /* The length of the block */
- i = (buf[1] << 16) | (buf[2] << 8) | buf[3];
-
- if ((buf[0] & 0x7f) == 0) /* 0 is the STREAMINFO block */
- {
- unsigned long totalsamples;
-
- /* FIXME: Don't trust the value of i */
- if (read(fd, buf, i) < 0)
- {
- return rc;
- }
-
- id3->vbr = true; /* All FLAC files are VBR */
- id3->filesize = filesize(fd);
- id3->frequency = (buf[10] << 12) | (buf[11] << 4)
- | ((buf[12] & 0xf0) >> 4);
- rc = true; /* Got vital metadata */
-
- /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */
- totalsamples = get_long_be(&buf[14]);
-
- /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
- id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
-
- if (id3->length <= 0)
- {
- logf("flac length invalid!");
- return false;
- }
-
- id3->bitrate = (id3->filesize * 8) / id3->length;
- }
- else if ((buf[0] & 0x7f) == 4) /* 4 is the VORBIS_COMMENT block */
- {
- /* The next i bytes of the file contain the VORBIS COMMENTS. */
- if (!read_vorbis_tags(fd, id3, i))
- {
- return rc;
- }
- }
- else
- {
- if (buf[0] & 0x80)
- {
- /* If we have reached the last metadata block, abort. */
- break;
- }
- else
- {
- /* Skip to next metadata block */
- if (lseek(fd, i, SEEK_CUR) < 0)
- {
- return rc;
- }
- }
- }
- }
-
- return true;
-}
-
-static bool get_monkeys_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char* buf = (unsigned char *)id3->path;
- unsigned char* header;
- bool rc = false;
- uint32_t descriptorlength;
- uint32_t totalsamples;
- uint32_t blocksperframe, finalframeblocks, totalframes;
- int fileversion;
-
- lseek(fd, 0, SEEK_SET);
-
- if (read(fd, buf, 4) < 4)
- {
- return rc;
- }
-
- if (memcmp(buf, "MAC ", 4) != 0)
- {
- return rc;
- }
-
- read(fd, buf + 4, MAX_PATH - 4);
-
- fileversion = get_short_le(buf+4);
- if (fileversion < 3970)
- {
- /* Not supported */
- return false;
- }
-
- if (fileversion >= 3980)
- {
- descriptorlength = get_long_le(buf+8);
-
- header = buf + descriptorlength;
-
- blocksperframe = get_long_le(header+4);
- finalframeblocks = get_long_le(header+8);
- totalframes = get_long_le(header+12);
- id3->frequency = get_long_le(header+20);
- }
- else
- {
- /* v3.95 and later files all have a fixed framesize */
- blocksperframe = 73728 * 4;
-
- finalframeblocks = get_long_le(buf+28);
- totalframes = get_long_le(buf+24);
- id3->frequency = get_long_le(buf+12);
- }
-
- id3->vbr = true; /* All FLAC files are VBR */
- id3->filesize = filesize(fd);
-
- totalsamples = finalframeblocks;
- if (totalframes > 1)
- totalsamples += blocksperframe * (totalframes-1);
-
- id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
- id3->bitrate = (id3->filesize * 8) / id3->length;
- return true;
-}
-
-static bool get_wave_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char* buf = (unsigned char *)id3->path;
- unsigned long totalsamples = 0;
- unsigned long channels = 0;
- unsigned long bitspersample = 0;
- unsigned long numbytes = 0;
- int read_bytes;
- int i;
-
- /* get RIFF chunk header */
- if ((lseek(fd, 0, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, 12)) < 12))
- {
- return false;
- }
-
- if ((memcmp(buf, "RIFF",4) != 0)
- || (memcmp(&buf[8], "WAVE", 4) !=0 ))
- {
- return false;
- }
-
- /* iterate over WAVE chunks until 'data' chunk */
- while (true)
- {
- /* get chunk header */
- if ((read_bytes = read(fd, buf, 8)) < 8)
- return false;
-
- /* chunkSize */
- i = get_long_le(&buf[4]);
-
- if (memcmp(buf, "fmt ", 4) == 0)
- {
- /* get rest of chunk */
- if ((read_bytes = read(fd, buf, 16)) < 16)
- return false;
-
- i -= 16;
-
- /* skipping wFormatTag */
- /* wChannels */
- channels = buf[2] | (buf[3] << 8);
- /* dwSamplesPerSec */
- id3->frequency = get_long_le(&buf[4]);
- /* dwAvgBytesPerSec */
- id3->bitrate = (get_long_le(&buf[8]) * 8) / 1000;
- /* skipping wBlockAlign */
- /* wBitsPerSample */
- bitspersample = buf[14] | (buf[15] << 8);
- }
- else if (memcmp(buf, "data", 4) == 0)
- {
- numbytes = i;
- break;
- }
- else if (memcmp(buf, "fact", 4) == 0)
- {
- /* dwSampleLength */
- if (i >= 4)
- {
- /* get rest of chunk */
- if ((read_bytes = read(fd, buf, 4)) < 4)
- return false;
-
- i -= 4;
- totalsamples = get_long_le(buf);
- }
- }
-
- /* seek to next chunk (even chunk sizes must be padded) */
- if (i & 0x01)
- i++;
-
- if(lseek(fd, i, SEEK_CUR) < 0)
- return false;
- }
-
- if ((numbytes == 0) || (channels == 0))
- {
- return false;
- }
-
- if (totalsamples == 0)
- {
- /* for PCM only */
- totalsamples = numbytes
- / ((((bitspersample - 1) / 8) + 1) * channels);
- }
-
- id3->vbr = false; /* All WAV files are CBR */
- id3->filesize = filesize(fd);
-
- /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
- id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
-
- return true;
-}
-
-/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
- * buffer.
- */
-static unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
- unsigned int buffer_left)
-{
- unsigned int bytes_read = 0;
-
- if (buffer_left == 0)
- {
- lseek(fd, size_left, SEEK_CUR); /* Skip everything */
- }
- else
- {
- /* Skip the data tag header - maybe we should parse it properly? */
- lseek(fd, 16, SEEK_CUR);
- size_left -= 16;
-
- if (size_left > buffer_left)
- {
- read(fd, buffer, buffer_left);
- lseek(fd, size_left - buffer_left, SEEK_CUR);
- bytes_read = buffer_left;
- }
- else
- {
- read(fd, buffer, size_left);
- bytes_read = size_left;
- }
- }
-
- return bytes_read;
-}
-
-/* Read a string tag from an MP4 file */
-static unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer,
- unsigned int* buffer_left, char** dest)
-{
- unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
- *buffer_left - 1);
- unsigned int length = 0;
-
- if (bytes_read)
- {
- (*buffer)[bytes_read] = 0;
- *dest = *buffer;
- length = strlen(*buffer) + 1;
- *buffer_left -= length;
- *buffer += length;
- }
- else
- {
- *dest = NULL;
- }
-
- return length;
-}
-
-static unsigned int read_mp4_atom(int fd, unsigned int* size,
- unsigned int* type, unsigned int size_left)
-{
- read_uint32be(fd, size);
- read_uint32be(fd, type);
-
- if (*size == 1)
- {
- /* FAT32 doesn't support files this big, so something seems to
- * be wrong. (64-bit sizes should only be used when required.)
- */
- errno = EFBIG;
- *type = 0;
- return 0;
- }
-
- if (*size > 0)
- {
- if (*size > size_left)
- {
- size_left = 0;
- }
- else
- {
- size_left -= *size;
- }
-
- *size -= 8;
- }
- else
- {
- *size = size_left;
- size_left = 0;
- }
-
- return size_left;
-}
-
-static unsigned int read_mp4_length(int fd, unsigned int* size)
-{
- unsigned int length = 0;
- int bytes = 0;
- unsigned char c;
-
- do
- {
- read(fd, &c, 1);
- bytes++;
- (*size)--;
- length = (length << 7) | (c & 0x7F);
- }
- while ((c & 0x80) && (bytes < 4) && (*size > 0));
-
- return length;
-}
-
-static bool read_mp4_esds(int fd, struct mp3entry* id3,
- unsigned int* size)
-{
- unsigned char buf[8];
- bool sbr = false;
-
- lseek(fd, 4, SEEK_CUR); /* Version and flags. */
- read(fd, buf, 1); /* Verify ES_DescrTag. */
- *size -= 5;
-
- if (*buf == 3)
- {
- /* read length */
- if (read_mp4_length(fd, size) < 20)
- {
- return sbr;
- }
-
- lseek(fd, 3, SEEK_CUR);
- *size -= 3;
- }
- else
- {
- lseek(fd, 2, SEEK_CUR);
- *size -= 2;
- }
-
- read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */
- *size -= 1;
-
- if (*buf != 4)
- {
- return sbr;
- }
-
- if (read_mp4_length(fd, size) < 13)
- {
- return sbr;
- }
-
- lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */
- read(fd, buf, 1);
- *size -= 14;
-
- if (*buf != 5) /* Verify DecSpecificInfoTag. */
- {
- return sbr;
- }
-
- {
- static const int sample_rates[] =
- {
- 96000, 88200, 64000, 48000, 44100, 32000,
- 24000, 22050, 16000, 12000, 11025, 8000
- };
- unsigned long bits;
- unsigned int length;
- unsigned int index;
- unsigned int type;
-
- /* Read the (leading part of the) decoder config. */
- length = read_mp4_length(fd, size);
- length = MIN(length, *size);
- length = MIN(length, sizeof(buf));
- memset(buf, 0, sizeof(buf));
- read(fd, buf, length);
- *size -= length;
-
- /* Maybe time to write a simple read_bits function... */
-
- /* Decoder config format:
- * Object type - 5 bits
- * Frequency index - 4 bits
- * Channel configuration - 4 bits
- */
- bits = get_long_be(buf);
- type = bits >> 27; /* Object type - 5 bits */
- index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */
-
- if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
- {
- id3->frequency = sample_rates[index];
- }
-
- if (type == 5)
- {
- DEBUGF("MP4: SBR\n");
- unsigned int old_index = index;
-
- sbr = true;
- index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */
-
- if (index == 15)
- {
- /* 17 bits read so far... */
- bits = get_long_be(&buf[2]);
- id3->frequency = (bits >> 7) & 0x00ffffff;
- }
- else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
- {
- id3->frequency = sample_rates[index];
- }
-
- if (old_index == index)
- {
- /* Downsampled SBR */
- id3->frequency *= 2;
- }
- }
- /* Skip 13 bits from above, plus 3 bits, then read 11 bits */
- else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7))
- {
- /* extensionAudioObjectType */
- DEBUGF("MP4: extensionAudioType\n");
- type = bits & 0x1f; /* Object type - 5 bits*/
- bits = get_long_be(&buf[4]);
-
- if (type == 5)
- {
- sbr = bits >> 31;
-
- if (sbr)
- {
- unsigned int old_index = index;
-
- /* 1 bit read so far */
- index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */
-
- if (index == 15)
- {
- /* 5 bits read so far */
- id3->frequency = (bits >> 3) & 0x00ffffff;
- }
- else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
- {
- id3->frequency = sample_rates[index];
- }
-
- if (old_index == index)
- {
- /* Downsampled SBR */
- id3->frequency *= 2;
- }
- }
- }
- }
-
- if (!sbr && (id3->frequency <= 24000) && (length <= 2))
- {
- /* Double the frequency for low-frequency files without a "long"
- * DecSpecificConfig header. The file may or may not contain SBR,
- * but here we guess it does if the header is short. This can
- * fail on some files, but it's the best we can do, short of
- * decoding (parts of) the file.
- */
- id3->frequency *= 2;
- }
- }
-
- return sbr;
-}
-
-static bool read_mp4_tags(int fd, struct mp3entry* id3,
- unsigned int size_left)
-{
- unsigned int size;
- unsigned int type;
- unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
- char* buffer = id3->id3v2buf;
- bool cwrt = false;
-
- do
- {
- size_left = read_mp4_atom(fd, &size, &type, size_left);
-
- /* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff,
- type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */
-
- switch (type)
- {
- case MP4_cnam:
- read_mp4_tag_string(fd, size, &buffer, &buffer_left,
- &id3->title);
- break;
-
- case MP4_cART:
- read_mp4_tag_string(fd, size, &buffer, &buffer_left,
- &id3->artist);
- break;
-
- case MP4_calb:
- read_mp4_tag_string(fd, size, &buffer, &buffer_left,
- &id3->album);
- break;
-
- case MP4_cwrt:
- read_mp4_tag_string(fd, size, &buffer, &buffer_left,
- &id3->composer);
- cwrt = false;
- break;
-
- case MP4_gnre:
- {
- unsigned short genre;
-
- read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
- id3->genre_string = id3_get_num_genre(betoh16(genre) - 1);
- }
- break;
-
- case MP4_trkn:
- {
- unsigned short n[2];
-
- read_mp4_tag(fd, size, (char*) &n, sizeof(n));
- id3->tracknum = betoh16(n[1]);
- }
- break;
-
- case MP4_extra:
- {
- char tag_name[TAG_NAME_LENGTH];
- unsigned int sub_size;
-
- /* "mean" atom */
- read_uint32be(fd, &sub_size);
- size -= sub_size;
- lseek(fd, sub_size - 4, SEEK_CUR);
- /* "name" atom */
- read_uint32be(fd, &sub_size);
- size -= sub_size;
- lseek(fd, 8, SEEK_CUR);
- sub_size -= 12;
-
- if (sub_size > sizeof(tag_name) - 1)
- {
- read(fd, tag_name, sizeof(tag_name) - 1);
- lseek(fd, sub_size - sizeof(tag_name) - 1, SEEK_CUR);
- tag_name[sizeof(tag_name) - 1] = 0;
- }
- else
- {
- read(fd, tag_name, sub_size);
- tag_name[sub_size] = 0;
- }
-
- if ((strcasecmp(tag_name, "composer") == 0) && !cwrt)
- {
- read_mp4_tag_string(fd, size, &buffer, &buffer_left,
- &id3->composer);
- }
- else if (strcasecmp(tag_name, "iTunSMPB") == 0)
- {
- char value[TAG_VALUE_LENGTH];
- char* value_p = value;
- char* any;
- unsigned int length = sizeof(value);
-
- read_mp4_tag_string(fd, size, &value_p, &length, &any);
- id3->lead_trim = get_itunes_int32(value, 1);
- id3->tail_trim = get_itunes_int32(value, 2);
- DEBUGF("AAC: lead_trim %d, tail_trim %d\n",
- id3->lead_trim, id3->tail_trim);
- }
- else
- {
- char* any;
- unsigned int length = read_mp4_tag_string(fd, size,
- &buffer, &buffer_left, &any);
-
- if (length > 0)
- {
- /* Re-use the read buffer as the dest buffer... */
- buffer -= length;
- buffer_left += length;
-
- if (parse_replaygain(tag_name, buffer, id3,
- buffer, buffer_left) > 0)
- {
- /* Data used, keep it. */
- buffer += length;
- buffer_left -= length;
- }
- }
- }
- }
- break;
-
- default:
- lseek(fd, size, SEEK_CUR);
- break;
- }
- }
- while ((size_left > 0) && (errno == 0));
-
- return true;
-}
-
-static bool read_mp4_container(int fd, struct mp3entry* id3,
- unsigned int size_left)
-{
- unsigned int size;
- unsigned int type;
- unsigned int handler = 0;
- bool rc = true;
-
- do
- {
- size_left = read_mp4_atom(fd, &size, &type, size_left);
-
- /* DEBUGF("Atom: '%c%c%c%c' (0x%08x, %d bytes left)\n",
- (type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff,
- type & 0xff, type, size); */
-
- switch (type)
- {
- case MP4_ftyp:
- {
- unsigned int id;
-
- read_uint32be(fd, &id);
- size -= 4;
-
- if ((id != MP4_M4A) && (id != MP4_M4B) && (id != MP4_mp42)
- && (id != MP4_qt) && (id != MP4_3gp6))
- {
- DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n",
- id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff,
- id & 0xff);
- return false;
- }
- }
- break;
-
- case MP4_meta:
- lseek(fd, 4, SEEK_CUR); /* Skip version */
- size -= 4;
- /* Fall through */
-
- case MP4_moov:
- case MP4_udta:
- case MP4_mdia:
- case MP4_stbl:
- case MP4_trak:
- rc = read_mp4_container(fd, id3, size);
- size = 0;
- break;
-
- case MP4_ilst:
- if (handler == MP4_mdir)
- {
- rc = read_mp4_tags(fd, id3, size);
- size = 0;
- }
- break;
-
- case MP4_minf:
- if (handler == MP4_soun)
- {
- rc = read_mp4_container(fd, id3, size);
- size = 0;
- }
- break;
-
- case MP4_stsd:
- lseek(fd, 8, SEEK_CUR);
- size -= 8;
- rc = read_mp4_container(fd, id3, size);
- size = 0;
- break;
-
- case MP4_hdlr:
- lseek(fd, 8, SEEK_CUR);
- read_uint32be(fd, &handler);
- size -= 12;
- /* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff,
- handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */
- break;
-
- case MP4_stts:
- {
- unsigned int entries;
- unsigned int i;
-
- lseek(fd, 4, SEEK_CUR);
- read_uint32be(fd, &entries);
- id3->samples = 0;
-
- for (i = 0; i < entries; i++)
- {
- unsigned int n;
- unsigned int l;
-
- read_uint32be(fd, &n);
- read_uint32be(fd, &l);
- id3->samples += n * l;
- }
-
- size = 0;
- }
- break;
-
- case MP4_mp4a:
- case MP4_alac:
- {
- unsigned int frequency;
-
- id3->codectype = (type == MP4_mp4a) ? AFMT_AAC : AFMT_ALAC;
- lseek(fd, 22, SEEK_CUR);
- read_uint32be(fd, &frequency);
- size -= 26;
- id3->frequency = frequency;
-
- if (type == MP4_mp4a)
- {
- unsigned int subsize;
- unsigned int subtype;
-
- /* Get frequency from the decoder info tag, if possible. */
- lseek(fd, 2, SEEK_CUR);
- /* The esds atom is a part of the mp4a atom, so ignore
- * the returned size (it's already accounted for).
- */
- read_mp4_atom(fd, &subsize, &subtype, size);
- size -= 10;
-
- if (subtype == MP4_esds)
- {
- read_mp4_esds(fd, id3, &size);
- }
- }
- }
- break;
-
- case MP4_mdat:
- id3->filesize = size;
- break;
-
- default:
- break;
- }
-
- lseek(fd, size, SEEK_CUR);
- }
- while (rc && (size_left > 0) && (errno == 0) && (id3->filesize == 0));
- /* Break on non-zero filesize, since Rockbox currently doesn't support
- * metadata after the mdat atom (which sets the filesize field).
- */
-
- return rc;
-}
-
-static bool get_mp4_metadata(int fd, struct mp3entry* id3)
-{
- id3->codectype = AFMT_UNKNOWN;
- id3->filesize = 0;
- errno = 0;
-
- if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0)
- && (id3->samples > 0) && (id3->frequency > 0)
- && (id3->filesize > 0))
- {
- if (id3->codectype == AFMT_UNKNOWN)
- {
- logf("Not an ALAC or AAC file");
- return false;
- }
-
- id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
-
- if (id3->length <= 0)
- {
- logf("mp4 length invalid!");
- return false;
- }
-
- id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
- DEBUGF("MP4 bitrate %d, frequency %ld Hz, length %ld ms\n",
- id3->bitrate, id3->frequency, id3->length);
- }
- else
- {
- logf("MP4 metadata error");
- DEBUGF("MP4 metadata error. errno %d, length %ld, frequency %ld, filesize %ld\n",
- errno, id3->length, id3->frequency, id3->filesize);
- return false;
- }
-
- return true;
-}
-
-static bool get_musepack_metadata(int fd, struct mp3entry *id3)
-{
- const int32_t sfreqs_sv7[4] = { 44100, 48000, 37800, 32000 };
- uint32_t header[8];
- uint64_t samples = 0;
- int i;
-
- if (!skip_id3v2(fd, id3))
- return false;
- if (read(fd, header, 4*8) != 4*8) return false;
- /* Musepack files are little endian, might need swapping */
- for (i = 1; i < 8; i++)
- header[i] = letoh32(header[i]);
- if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */
- unsigned int streamversion;
-
- header[0] = letoh32(header[0]);
- streamversion = (header[0] >> 24) & 15;
- if (streamversion >= 8) {
- return false; /* SV8 or higher don't exist yet, so no support */
- } else if (streamversion == 7) {
- unsigned int gapless = (header[5] >> 31) & 0x0001;
- unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff;
- int track_gain, album_gain;
- unsigned int bufused;
-
- id3->frequency = sfreqs_sv7[(header[2] >> 16) & 0x0003];
- samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */
- if (gapless)
- samples -= 1152 - last_frame_samples;
- else
- samples -= 481; /* Musepack subband synth filter delay */
-
- /* Extract ReplayGain data from header */
- track_gain = (int16_t)((header[3] >> 16) & 0xffff);
- id3->track_gain = get_replaygain_int(track_gain);
- id3->track_peak = ((uint16_t)(header[3] & 0xffff)) << 9;
-
- album_gain = (int16_t)((header[4] >> 16) & 0xffff);
- id3->album_gain = get_replaygain_int(album_gain);
- id3->album_peak = ((uint16_t)(header[4] & 0xffff)) << 9;
-
- /* Write replaygain values to strings for use in id3 screen. We use
- the XING header as buffer space since Musepack files shouldn't
- need to use it in any other way */
- id3->track_gain_string = (char *)id3->toc;
- bufused = snprintf(id3->track_gain_string, 45,
- "%d.%d dB", track_gain/100, abs(track_gain)%100);
- id3->album_gain_string = (char *)id3->toc + bufused + 1;
- bufused = snprintf(id3->album_gain_string, 45,
- "%d.%d dB", album_gain/100, abs(album_gain)%100);
- }
- } else {
- header[0] = letoh32(header[0]);
- unsigned int streamversion = (header[0] >> 11) & 0x03FF;
- if (streamversion != 4 && streamversion != 5 && streamversion != 6)
- return false;
- id3->frequency = 44100;
- id3->track_gain = 0;
- id3->track_peak = 0;
- id3->album_gain = 0;
- id3->album_peak = 0;
-
- if (streamversion >= 5)
- samples = (uint64_t)header[1]*1152; // 32 bit
- else
- samples = (uint64_t)(header[1] >> 16)*1152; // 16 bit
-
- samples -= 576;
- if (streamversion < 6)
- samples -= 1152;
- }
-
- id3->vbr = true;
- /* Estimate bitrate, we should probably subtract the various header sizes
- here for super-accurate results */
- id3->length = ((int64_t) samples * 1000) / id3->frequency;
-
- if (id3->length <= 0)
- {
- logf("mpc length invalid!");
- return false;
- }
-
- id3->filesize = filesize(fd);
- id3->bitrate = id3->filesize * 8 / id3->length;
- return true;
-}
-
-/* PSID metadata info is available here:
- http://www.unusedino.de/ec64/technical/formats/sidplay.html */
-static bool get_sid_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char* buf = (unsigned char *)id3->path;
- int read_bytes;
- char *p;
-
-
- if ((lseek(fd, 0, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, 0x80)) < 0x80))
- {
- return false;
- }
-
- if ((memcmp(buf, "PSID",4) != 0))
- {
- return false;
- }
-
- p = id3->id3v2buf;
-
- /* Copy Title (assumed max 0x1f letters + 1 zero byte) */
- id3->title = p;
- buf[0x16+0x1f] = 0;
- p = iso_decode(&buf[0x16], p, 0, strlen(&buf[0x16])+1);
-
- /* Copy Artist (assumed max 0x1f letters + 1 zero byte) */
- id3->artist = p;
- buf[0x36+0x1f] = 0;
- p = iso_decode(&buf[0x36], p, 0, strlen(&buf[0x36])+1);
-
- /* Copy Year (assumed max 4 letters + 1 zero byte) */
- buf[0x56+0x4] = 0;
- id3->year = atoi(&buf[0x56]);
-
- /* Copy Album (assumed max 0x1f-0x05 letters + 1 zero byte) */
- id3->album = p;
- buf[0x56+0x1f] = 0;
- p = iso_decode(&buf[0x5b], p, 0, strlen(&buf[0x5b])+1);
-
- id3->bitrate = 706;
- id3->frequency = 44100;
- /* New idea as posted by Marco Alanen (ravon):
- * Set the songlength in seconds to the number of subsongs
- * so every second represents a subsong.
- * Users can then skip the current subsong by seeking
- *
- * Note: the number of songs is a 16bit value at 0xE, so this code only
- * uses the lower 8 bits of the counter.
- */
- id3->length = (buf[0xf]-1)*1000;
- id3->vbr = false;
- id3->filesize = filesize(fd);
-
- return true;
-}
-
-static bool get_adx_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char * buf = (unsigned char *)id3->path;
- int chanstart, channels, read_bytes;
- int looping = 0, start_adr = 0, end_adr = 0;
-
- /* try to get the basic header */
- if ((lseek(fd, 0, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, 0x38)) < 0x38))
- {
- DEBUGF("lseek or read failed\n");
- return false;
- }
-
- /* ADX starts with 0x80 */
- if (buf[0] != 0x80) {
- DEBUGF("get_adx_metadata: wrong first byte %c\n",buf[0]);
- return false;
- }
-
- /* check for a reasonable offset */
- chanstart = ((buf[2] << 8) | buf[3]) + 4;
- if (chanstart > 4096) {
- DEBUGF("get_adx_metadata: bad chanstart %i\n", chanstart);
- return false;
- }
-
- /* check for a workable number of channels */
- channels = buf[7];
- if (channels != 1 && channels != 2) {
- DEBUGF("get_adx_metadata: bad channel count %i\n",channels);
- return false;
- }
-
- id3->frequency = get_long_be(&buf[8]);
- /* 32 samples per 18 bytes */
- id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000;
- id3->length = get_long_be(&buf[12]) / id3->frequency * 1000;
- id3->vbr = false;
- id3->filesize = filesize(fd);
-
- /* get loop info */
- if (!memcmp(buf+0x10,"\x01\xF4\x03\x00",4)) {
- /* Soul Calibur 2 style (type 03) */
- DEBUGF("get_adx_metadata: type 03 found\n");
- /* check if header is too small for loop data */
- if (chanstart-6 < 0x2c) looping=0;
- else {
- looping = get_long_be(&buf[0x18]);
- end_adr = get_long_be(&buf[0x28]);
- start_adr = get_long_be(&buf[0x1c])/32*channels*18+chanstart;
- }
- } else if (!memcmp(buf+0x10,"\x01\xF4\x04\x00",4)) {
- /* Standard (type 04) */
- DEBUGF("get_adx_metadata: type 04 found\n");
- /* check if header is too small for loop data */
- if (chanstart-6 < 0x38) looping=0;
- else {
- looping = get_long_be(&buf[0x24]);
- end_adr = get_long_be(&buf[0x34]);
- start_adr = get_long_be(&buf[0x28])/32*channels*18+chanstart;
- }
- } else {
- DEBUGF("get_adx_metadata: error, couldn't determine ADX type\n");
- return false;
- }
-
- if (looping) {
- /* 2 loops, 10 second fade */
- id3->length = (start_adr-chanstart + 2*(end_adr-start_adr))
- *8 / id3->bitrate + 10000;
- }
-
- /* try to get the channel header */
- if ((lseek(fd, chanstart-6, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, 6)) < 6))
- {
- return false;
- }
-
- /* check channel header */
- if (memcmp(buf, "(c)CRI", 6) != 0) return false;
-
- return true;
-}
-
-static bool get_spc_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char * buf = (unsigned char *)id3->path;
- int read_bytes;
- char * p;
-
- unsigned long length;
- unsigned long fade;
- bool isbinary = true;
- int i;
-
- /* try to get the ID666 tag */
- if ((lseek(fd, 0x2e, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, 0xD2)) < 0xD2))
- {
- DEBUGF("lseek or read failed\n");
- return false;
- }
-
- p = id3->id3v2buf;
-
- id3->title = p;
- buf[31] = 0;
- p = iso_decode(buf, p, 0, 32);
- buf += 32;
-
- id3->album = p;
- buf[31] = 0;
- p = iso_decode(buf, p, 0, 32);
- buf += 48;
-
- id3->comment = p;
- buf[31] = 0;
- p = iso_decode(buf, p, 0, 32);
- buf += 32;
-
- /* Date check */
- if(buf[2] == '/' && buf[5] == '/')
- isbinary = false;
-
- /* Reserved bytes check */
- if(buf[0xD2 - 0x2E - 112] >= '0' &&
- buf[0xD2 - 0x2E - 112] <= '9' &&
- buf[0xD3 - 0x2E - 112] == 0x00)
- isbinary = false;
-
- /* is length & fade only digits? */
- for (i=0;i<8 && (
- (buf[0xA9 - 0x2E - 112+i]>='0'&&buf[0xA9 - 0x2E - 112+i]<='9') ||
- buf[0xA9 - 0x2E - 112+i]=='\0');
- i++);
- if (i==8) isbinary = false;
-
- if(isbinary) {
- id3->year = buf[0] | (buf[1]<<8);
- buf += 11;
-
- length = (buf[0] | (buf[1]<<8) | (buf[2]<<16)) * 1000;
- buf += 3;
-
- fade = (buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24));
- buf += 4;
- } else {
- char tbuf[6];
-
- buf += 6;
- buf[4] = 0;
- id3->year = atoi(buf);
- buf += 5;
-
- memcpy(tbuf, buf, 3);
- tbuf[3] = 0;
- length = atoi(tbuf) * 1000;
- buf += 3;
-
- memcpy(tbuf, buf, 5);
- tbuf[5] = 0;
- fade = atoi(tbuf);
- buf += 5;
- }
-
- id3->artist = p;
- buf[31] = 0;
- p = iso_decode(buf, p, 0, 32);
-
- if (length==0) {
- length=3*60*1000; /* 3 minutes */
- fade=5*1000; /* 5 seconds */
- }
-
- id3->length = length+fade;
-
- return true;
-}
-
-static bool get_aiff_metadata(int fd, struct mp3entry* id3)
-{
- /* Use the trackname part of the id3 structure as a temporary buffer */
- unsigned char* buf = (unsigned char *)id3->path;
- unsigned long numChannels = 0;
- unsigned long numSampleFrames = 0;
- unsigned long sampleSize = 0;
- unsigned long sampleRate = 0;
- unsigned long numbytes = 0;
- int read_bytes;
- int i;
-
- if ((lseek(fd, 0, SEEK_SET) < 0)
- || ((read_bytes = read(fd, buf, sizeof(id3->path))) < 54))
- {
- return false;
- }
-
- if ((memcmp(buf, "FORM",4) != 0)
- || (memcmp(&buf[8], "AIFF", 4) !=0 ))
- {
- return false;
- }
-
- buf += 12;
- read_bytes -= 12;
-
- while ((numbytes == 0) && (read_bytes >= 8))
- {
- /* chunkSize */
- i = get_long_be(&buf[4]);
-
- if (memcmp(buf, "COMM", 4) == 0)
- {
- /* numChannels */
- numChannels = ((buf[8]<<8)|buf[9]);
- /* numSampleFrames */
- numSampleFrames = get_long_be(&buf[10]);
- /* sampleSize */
- sampleSize = ((buf[14]<<8)|buf[15]);
- /* sampleRate */
- sampleRate = get_long_be(&buf[18]);
- sampleRate = sampleRate >> (16+14-buf[17]);
- /* save format infos */
- id3->bitrate = (sampleSize * numChannels * sampleRate) / 1000;
- id3->frequency = sampleRate;
- id3->length = ((int64_t) numSampleFrames * 1000) / id3->frequency;
-
- id3->vbr = false; /* AIFF files are CBR */
- id3->filesize = filesize(fd);
- }
- else if (memcmp(buf, "SSND", 4) == 0)
- {
- numbytes = i - 8;
- }
-
- if (i & 0x01)
- {
- i++; /* odd chunk sizes must be padded */
- }
- buf += i + 8;
- read_bytes -= i + 8;
- }
-
- if ((numbytes == 0) || (numChannels == 0))
- {
- return false;
- }
- return true;
-}
#endif /* CONFIG_CODEC == SWCODEC */
diff --git a/apps/metadata/adx.c b/apps/metadata/adx.c
new file mode 100644
index 0000000000..44b07a14b8
--- /dev/null
+++ b/apps/metadata/adx.c
@@ -0,0 +1,115 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "debug.h"
+
+bool get_adx_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char * buf = (unsigned char *)id3->path;
+ int chanstart, channels, read_bytes;
+ int looping = 0, start_adr = 0, end_adr = 0;
+
+ /* try to get the basic header */
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 0x38)) < 0x38))
+ {
+ DEBUGF("lseek or read failed\n");
+ return false;
+ }
+
+ /* ADX starts with 0x80 */
+ if (buf[0] != 0x80) {
+ DEBUGF("get_adx_metadata: wrong first byte %c\n",buf[0]);
+ return false;
+ }
+
+ /* check for a reasonable offset */
+ chanstart = ((buf[2] << 8) | buf[3]) + 4;
+ if (chanstart > 4096) {
+ DEBUGF("get_adx_metadata: bad chanstart %i\n", chanstart);
+ return false;
+ }
+
+ /* check for a workable number of channels */
+ channels = buf[7];
+ if (channels != 1 && channels != 2) {
+ DEBUGF("get_adx_metadata: bad channel count %i\n",channels);
+ return false;
+ }
+
+ id3->frequency = get_long_be(&buf[8]);
+ /* 32 samples per 18 bytes */
+ id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000;
+ id3->length = get_long_be(&buf[12]) / id3->frequency * 1000;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ /* get loop info */
+ if (!memcmp(buf+0x10,"\x01\xF4\x03\x00",4)) {
+ /* Soul Calibur 2 style (type 03) */
+ DEBUGF("get_adx_metadata: type 03 found\n");
+ /* check if header is too small for loop data */
+ if (chanstart-6 < 0x2c) looping=0;
+ else {
+ looping = get_long_be(&buf[0x18]);
+ end_adr = get_long_be(&buf[0x28]);
+ start_adr = get_long_be(&buf[0x1c])/32*channels*18+chanstart;
+ }
+ } else if (!memcmp(buf+0x10,"\x01\xF4\x04\x00",4)) {
+ /* Standard (type 04) */
+ DEBUGF("get_adx_metadata: type 04 found\n");
+ /* check if header is too small for loop data */
+ if (chanstart-6 < 0x38) looping=0;
+ else {
+ looping = get_long_be(&buf[0x24]);
+ end_adr = get_long_be(&buf[0x34]);
+ start_adr = get_long_be(&buf[0x28])/32*channels*18+chanstart;
+ }
+ } else {
+ DEBUGF("get_adx_metadata: error, couldn't determine ADX type\n");
+ return false;
+ }
+
+ if (looping) {
+ /* 2 loops, 10 second fade */
+ id3->length = (start_adr-chanstart + 2*(end_adr-start_adr))
+ *8 / id3->bitrate + 10000;
+ }
+
+ /* try to get the channel header */
+ if ((lseek(fd, chanstart-6, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 6)) < 6))
+ {
+ return false;
+ }
+
+ /* check channel header */
+ if (memcmp(buf, "(c)CRI", 6) != 0) return false;
+
+ return true;
+}
diff --git a/apps/metadata/aiff.c b/apps/metadata/aiff.c
new file mode 100644
index 0000000000..3b155caa2b
--- /dev/null
+++ b/apps/metadata/aiff.c
@@ -0,0 +1,98 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+
+bool get_aiff_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned long numChannels = 0;
+ unsigned long numSampleFrames = 0;
+ unsigned long sampleSize = 0;
+ unsigned long sampleRate = 0;
+ unsigned long numbytes = 0;
+ int read_bytes;
+ int i;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, sizeof(id3->path))) < 54))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "FORM",4) != 0)
+ || (memcmp(&buf[8], "AIFF", 4) !=0 ))
+ {
+ return false;
+ }
+
+ buf += 12;
+ read_bytes -= 12;
+
+ while ((numbytes == 0) && (read_bytes >= 8))
+ {
+ /* chunkSize */
+ i = get_long_be(&buf[4]);
+
+ if (memcmp(buf, "COMM", 4) == 0)
+ {
+ /* numChannels */
+ numChannels = ((buf[8]<<8)|buf[9]);
+ /* numSampleFrames */
+ numSampleFrames = get_long_be(&buf[10]);
+ /* sampleSize */
+ sampleSize = ((buf[14]<<8)|buf[15]);
+ /* sampleRate */
+ sampleRate = get_long_be(&buf[18]);
+ sampleRate = sampleRate >> (16+14-buf[17]);
+ /* save format infos */
+ id3->bitrate = (sampleSize * numChannels * sampleRate) / 1000;
+ id3->frequency = sampleRate;
+ id3->length = ((int64_t) numSampleFrames * 1000) / id3->frequency;
+
+ id3->vbr = false; /* AIFF files are CBR */
+ id3->filesize = filesize(fd);
+ }
+ else if (memcmp(buf, "SSND", 4) == 0)
+ {
+ numbytes = i - 8;
+ }
+
+ if (i & 0x01)
+ {
+ i++; /* odd chunk sizes must be padded */
+ }
+ buf += i + 8;
+ read_bytes -= i + 8;
+ }
+
+ if ((numbytes == 0) || (numChannels == 0))
+ {
+ return false;
+ }
+ return true;
+}
diff --git a/apps/metadata/ape.c b/apps/metadata/ape.c
new file mode 100644
index 0000000000..ac071be6f7
--- /dev/null
+++ b/apps/metadata/ape.c
@@ -0,0 +1,132 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "structec.h"
+
+#define APETAG_HEADER_LENGTH 32
+#define APETAG_HEADER_FORMAT "8llll8"
+#define APETAG_ITEM_HEADER_FORMAT "ll"
+#define APETAG_ITEM_TYPE_MASK 3
+
+struct apetag_header
+{
+ char id[8];
+ long version;
+ long length;
+ long item_count;
+ long flags;
+ char reserved[8];
+};
+
+struct apetag_item_header
+{
+ long length;
+ long flags;
+};
+
+/* Read the items in an APEV2 tag. Only looks for a tag at the end of a
+ * file. Returns true if a tag was found and fully read, false otherwise.
+ */
+bool read_ape_tags(int fd, struct mp3entry* id3)
+{
+ struct apetag_header header;
+
+ if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0)
+ || (ecread(fd, &header, 1, APETAG_HEADER_FORMAT, IS_BIG_ENDIAN) != APETAG_HEADER_LENGTH)
+ || (memcmp(header.id, "APETAGEX", sizeof(header.id))))
+ {
+ return false;
+ }
+
+ if ((header.version == 2000) && (header.item_count > 0)
+ && (header.length > APETAG_HEADER_LENGTH))
+ {
+ char *buf = id3->id3v2buf;
+ unsigned int buf_remaining = sizeof(id3->id3v2buf)
+ + sizeof(id3->id3v1buf);
+ unsigned int tag_remaining = header.length - APETAG_HEADER_LENGTH;
+ int i;
+
+ if (lseek(fd, -header.length, SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ for (i = 0; i < header.item_count; i++)
+ {
+ struct apetag_item_header item;
+ char name[TAG_NAME_LENGTH];
+ char value[TAG_VALUE_LENGTH];
+ long r;
+
+ if (tag_remaining < sizeof(item))
+ {
+ break;
+ }
+
+ if (ecread(fd, &item, 1, APETAG_ITEM_HEADER_FORMAT, IS_BIG_ENDIAN) < (long) sizeof(item))
+ {
+ return false;
+ }
+
+ tag_remaining -= sizeof(item);
+ r = read_string(fd, name, sizeof(name), 0, tag_remaining);
+
+ if (r == -1)
+ {
+ return false;
+ }
+
+ tag_remaining -= r + item.length;
+
+ if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0)
+ {
+ long len;
+
+ if (read_string(fd, value, sizeof(value), -1, item.length)
+ != item.length)
+ {
+ return false;
+ }
+
+ len = parse_tag(name, value, id3, buf, buf_remaining,
+ TAGTYPE_APE);
+ buf += len;
+ buf_remaining -= len;
+ }
+ else
+ {
+ if (lseek(fd, item.length, SEEK_CUR) < 0)
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/apps/metadata/flac.c b/apps/metadata/flac.c
new file mode 100644
index 0000000000..5b3644ede2
--- /dev/null
+++ b/apps/metadata/flac.c
@@ -0,0 +1,122 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "logf.h"
+
+bool get_flac_metadata(int fd, struct mp3entry* id3)
+{
+ /* A simple parser to read vital metadata from a FLAC file - length,
+ * frequency, bitrate etc. This code should either be moved to a
+ * seperate file, or discarded in favour of the libFLAC code.
+ * The FLAC stream specification can be found at
+ * http://flac.sourceforge.net/format.html#stream
+ */
+
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ bool rc = false;
+
+ if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4))
+ {
+ return rc;
+ }
+
+ if (memcmp(buf, "fLaC", 4) != 0)
+ {
+ return rc;
+ }
+
+ while (true)
+ {
+ long i;
+
+ if (read(fd, buf, 4) < 0)
+ {
+ return rc;
+ }
+
+ /* The length of the block */
+ i = (buf[1] << 16) | (buf[2] << 8) | buf[3];
+
+ if ((buf[0] & 0x7f) == 0) /* 0 is the STREAMINFO block */
+ {
+ unsigned long totalsamples;
+
+ /* FIXME: Don't trust the value of i */
+ if (read(fd, buf, i) < 0)
+ {
+ return rc;
+ }
+
+ id3->vbr = true; /* All FLAC files are VBR */
+ id3->filesize = filesize(fd);
+ id3->frequency = (buf[10] << 12) | (buf[11] << 4)
+ | ((buf[12] & 0xf0) >> 4);
+ rc = true; /* Got vital metadata */
+
+ /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */
+ totalsamples = get_long_be(&buf[14]);
+
+ /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+
+ if (id3->length <= 0)
+ {
+ logf("flac length invalid!");
+ return false;
+ }
+
+ id3->bitrate = (id3->filesize * 8) / id3->length;
+ }
+ else if ((buf[0] & 0x7f) == 4) /* 4 is the VORBIS_COMMENT block */
+ {
+ /* The next i bytes of the file contain the VORBIS COMMENTS. */
+ if (!read_vorbis_tags(fd, id3, i))
+ {
+ return rc;
+ }
+ }
+ else
+ {
+ if (buf[0] & 0x80)
+ {
+ /* If we have reached the last metadata block, abort. */
+ break;
+ }
+ else
+ {
+ /* Skip to next metadata block */
+ if (lseek(fd, i, SEEK_CUR) < 0)
+ {
+ return rc;
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/apps/metadata/metadata_common.c b/apps/metadata/metadata_common.c
new file mode 100644
index 0000000000..00b03841f9
--- /dev/null
+++ b/apps/metadata/metadata_common.c
@@ -0,0 +1,273 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "replaygain.h"
+#include "atoi.h"
+
+/* Skip an ID3v2 tag if it can be found. We assume the tag is located at the
+ * start of the file, which should be true in all cases where we need to skip it.
+ * Returns true if successfully skipped or not skipped, and false if
+ * something went wrong while skipping.
+ */
+bool skip_id3v2(int fd, struct mp3entry *id3)
+{
+ char buf[4];
+
+ read(fd, buf, 4);
+ if (memcmp(buf, "ID3", 3) == 0)
+ {
+ /* We have found an ID3v2 tag at the start of the file - find its
+ length and then skip it. */
+ if ((id3->first_frame_offset = getid3v2len(fd)) == 0)
+ return false;
+
+ if ((lseek(fd, id3->first_frame_offset, SEEK_SET) < 0))
+ return false;
+
+ return true;
+ } else {
+ lseek(fd, 0, SEEK_SET);
+ id3->first_frame_offset = 0;
+ return true;
+ }
+}
+
+
+/* Read a string from the file. Read up to size bytes, or, if eos != -1,
+ * until the eos character is found (eos is not stored in buf, unless it is
+ * nil). Writes up to buf_size chars to buf, always terminating with a nil.
+ * Returns number of chars read or -1 on read error.
+ */
+long read_string(int fd, char* buf, long buf_size, int eos, long size)
+{
+ long read_bytes = 0;
+ char c;
+
+ while (size != 0)
+ {
+ if (read(fd, &c, 1) != 1)
+ {
+ read_bytes = -1;
+ break;
+ }
+
+ read_bytes++;
+ size--;
+
+ if ((eos != -1) && (eos == (unsigned char) c))
+ {
+ break;
+ }
+
+ if (buf_size > 1)
+ {
+ *buf++ = c;
+ buf_size--;
+ }
+ }
+
+ *buf = 0;
+ return read_bytes;
+}
+
+/* Read an unsigned 32-bit integer from a big-endian file. */
+#ifdef ROCKBOX_BIG_ENDIAN
+#define read_uint32be(fd,buf) read((fd), (buf), 4)
+#else
+int read_uint32be(int fd, unsigned int* buf)
+{
+ size_t n;
+
+ n = read(fd, (char*) buf, 4);
+ *buf = betoh32(*buf);
+ return n;
+}
+#endif
+
+/* Read an unaligned 32-bit little endian long from buffer. */
+unsigned long get_long_le(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+/* Read an unaligned 16-bit little endian short from buffer. */
+unsigned short get_short_le(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8);
+}
+
+/* Read an unaligned 32-bit big endian long from buffer. */
+unsigned long get_long_be(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+}
+
+/* Read an unaligned 32-bit little endian long from buffer. */
+long get_slong(void* buf)
+{
+ unsigned char* p = (unsigned char*) buf;
+
+ return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
+}
+
+static char* skip_space(char* str)
+{
+ while (isspace(*str))
+ {
+ str++;
+ }
+
+ return str;
+}
+
+unsigned long get_itunes_int32(char* value, int count)
+{
+ static const char hexdigits[] = "0123456789ABCDEF";
+ const char* c;
+ int r = 0;
+
+ while (count-- > 0)
+ {
+ value = skip_space(value);
+
+ while (*value && !isspace(*value))
+ {
+ value++;
+ }
+ }
+
+ value = skip_space(value);
+
+ while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL))
+ {
+ r = (r << 4) | (c - hexdigits);
+ value++;
+ }
+
+ return r;
+}
+
+/* Parse the tag (the name-value pair) and fill id3 and buffer accordingly.
+ * String values to keep are written to buf. Returns number of bytes written
+ * to buf (including end nil).
+ */
+long parse_tag(const char* name, char* value, struct mp3entry* id3,
+ char* buf, long buf_remaining, enum tagtype type)
+{
+ long len = 0;
+ char** p;
+
+ if ((((strcasecmp(name, "track") == 0) && (type == TAGTYPE_APE)))
+ || ((strcasecmp(name, "tracknumber") == 0) && (type == TAGTYPE_VORBIS)))
+ {
+ id3->tracknum = atoi(value);
+ p = &(id3->track_string);
+ }
+ else if (((strcasecmp(name, "year") == 0) && (type == TAGTYPE_APE))
+ || ((strcasecmp(name, "date") == 0) && (type == TAGTYPE_VORBIS)))
+ {
+ /* Date's can be in any format in Vorbis. However most of them
+ * are in ISO8601 format so if we try and parse the first part
+ * of the tag as a number, we should get the year. If we get crap,
+ * then act like we never parsed it.
+ */
+ id3->year = atoi(value);
+ if (id3->year < 1900)
+ { /* yeah, not likely */
+ id3->year = 0;
+ }
+ p = &(id3->year_string);
+ }
+ else if (strcasecmp(name, "title") == 0)
+ {
+ p = &(id3->title);
+ }
+ else if (strcasecmp(name, "artist") == 0)
+ {
+ p = &(id3->artist);
+ }
+ else if (strcasecmp(name, "album") == 0)
+ {
+ p = &(id3->album);
+ }
+ else if (strcasecmp(name, "genre") == 0)
+ {
+ p = &(id3->genre_string);
+ }
+ else if (strcasecmp(name, "composer") == 0)
+ {
+ p = &(id3->composer);
+ }
+ else if (strcasecmp(name, "comment") == 0)
+ {
+ p = &(id3->comment);
+ }
+ else if (strcasecmp(name, "albumartist") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else if (strcasecmp(name, "album artist") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else if (strcasecmp(name, "ensemble") == 0)
+ {
+ p = &(id3->albumartist);
+ }
+ else
+ {
+ len = parse_replaygain(name, value, id3, buf, buf_remaining);
+ p = NULL;
+ }
+
+ if (p)
+ {
+ len = strlen(value);
+ len = MIN(len, buf_remaining - 1);
+
+ if (len > 0)
+ {
+ strncpy(buf, value, len);
+ buf[len] = 0;
+ *p = buf;
+ len++;
+ }
+ else
+ {
+ len = 0;
+ }
+ }
+
+ return len;
+}
+
diff --git a/apps/metadata/metadata_common.h b/apps/metadata/metadata_common.h
new file mode 100644
index 0000000000..70e708010d
--- /dev/null
+++ b/apps/metadata/metadata_common.h
@@ -0,0 +1,45 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "id3.h"
+
+#ifdef ROCKBOX_BIG_ENDIAN
+#define IS_BIG_ENDIAN 1
+#else
+#define IS_BIG_ENDIAN 0
+#endif
+
+#define TAG_NAME_LENGTH 32
+#define TAG_VALUE_LENGTH 128
+
+enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
+
+bool read_ape_tags(int fd, struct mp3entry* id3);
+bool read_vorbis_tags(int fd, struct mp3entry *id3,
+ long tag_remaining);
+
+bool skip_id3v2(int fd, struct mp3entry *id3);
+long read_string(int fd, char* buf, long buf_size, int eos, long size);
+int read_uint32be(int fd, unsigned int* buf);
+unsigned long get_long_le(void* buf);
+unsigned short get_short_le(void* buf);
+unsigned long get_long_be(void* buf);
+long get_slong(void* buf);
+unsigned long get_itunes_int32(char* value, int count);
+long parse_tag(const char* name, char* value, struct mp3entry* id3,
+ char* buf, long buf_remaining, enum tagtype type);
diff --git a/apps/metadata/metadata_parsers.h b/apps/metadata/metadata_parsers.h
new file mode 100644
index 0000000000..f52ce69dd2
--- /dev/null
+++ b/apps/metadata/metadata_parsers.h
@@ -0,0 +1,31 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include "id3.h"
+
+bool get_adx_metadata(int fd, struct mp3entry* id3);
+bool get_aiff_metadata(int fd, struct mp3entry* id3);
+bool get_flac_metadata(int fd, struct mp3entry* id3);
+bool get_mp4_metadata(int fd, struct mp3entry* id3);
+bool get_monkeys_metadata(int fd, struct mp3entry* id3);
+bool get_musepack_metadata(int fd, struct mp3entry *id3);
+bool get_sid_metadata(int fd, struct mp3entry* id3);
+bool get_spc_metadata(int fd, struct mp3entry* id3);
+bool get_speex_metadata(int fd, struct mp3entry* id3);
+bool get_vorbis_metadata(int fd, struct mp3entry* id3);
+bool get_wave_metadata(int fd, struct mp3entry* id3);
diff --git a/apps/metadata/monkeys.c b/apps/metadata/monkeys.c
new file mode 100644
index 0000000000..bda3a01643
--- /dev/null
+++ b/apps/metadata/monkeys.c
@@ -0,0 +1,92 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+
+bool get_monkeys_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned char* header;
+ bool rc = false;
+ uint32_t descriptorlength;
+ uint32_t totalsamples;
+ uint32_t blocksperframe, finalframeblocks, totalframes;
+ int fileversion;
+
+ lseek(fd, 0, SEEK_SET);
+
+ if (read(fd, buf, 4) < 4)
+ {
+ return rc;
+ }
+
+ if (memcmp(buf, "MAC ", 4) != 0)
+ {
+ return rc;
+ }
+
+ read(fd, buf + 4, MAX_PATH - 4);
+
+ fileversion = get_short_le(buf+4);
+ if (fileversion < 3970)
+ {
+ /* Not supported */
+ return false;
+ }
+
+ if (fileversion >= 3980)
+ {
+ descriptorlength = get_long_le(buf+8);
+
+ header = buf + descriptorlength;
+
+ blocksperframe = get_long_le(header+4);
+ finalframeblocks = get_long_le(header+8);
+ totalframes = get_long_le(header+12);
+ id3->frequency = get_long_le(header+20);
+ }
+ else
+ {
+ /* v3.95 and later files all have a fixed framesize */
+ blocksperframe = 73728 * 4;
+
+ finalframeblocks = get_long_le(buf+28);
+ totalframes = get_long_le(buf+24);
+ id3->frequency = get_long_le(buf+12);
+ }
+
+ id3->vbr = true; /* All FLAC files are VBR */
+ id3->filesize = filesize(fd);
+
+ totalsamples = finalframeblocks;
+ if (totalframes > 1)
+ totalsamples += blocksperframe * (totalframes-1);
+
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+ id3->bitrate = (id3->filesize * 8) / id3->length;
+ return true;
+}
diff --git a/apps/metadata/mp4.c b/apps/metadata/mp4.c
new file mode 100644
index 0000000000..6523bb6551
--- /dev/null
+++ b/apps/metadata/mp4.c
@@ -0,0 +1,669 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "errno.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "logf.h"
+#include "debug.h"
+#include "replaygain.h"
+
+#define MP4_ID(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
+
+#define MP4_3gp6 MP4_ID('3', 'g', 'p', '6')
+#define MP4_alac MP4_ID('a', 'l', 'a', 'c')
+#define MP4_calb MP4_ID(0xa9, 'a', 'l', 'b')
+#define MP4_cART MP4_ID(0xa9, 'A', 'R', 'T')
+#define MP4_cnam MP4_ID(0xa9, 'n', 'a', 'm')
+#define MP4_cwrt MP4_ID(0xa9, 'w', 'r', 't')
+#define MP4_esds MP4_ID('e', 's', 'd', 's')
+#define MP4_ftyp MP4_ID('f', 't', 'y', 'p')
+#define MP4_gnre MP4_ID('g', 'n', 'r', 'e')
+#define MP4_hdlr MP4_ID('h', 'd', 'l', 'r')
+#define MP4_ilst MP4_ID('i', 'l', 's', 't')
+#define MP4_M4A MP4_ID('M', '4', 'A', ' ')
+#define MP4_M4B MP4_ID('M', '4', 'B', ' ')
+#define MP4_mdat MP4_ID('m', 'd', 'a', 't')
+#define MP4_mdia MP4_ID('m', 'd', 'i', 'a')
+#define MP4_mdir MP4_ID('m', 'd', 'i', 'r')
+#define MP4_meta MP4_ID('m', 'e', 't', 'a')
+#define MP4_minf MP4_ID('m', 'i', 'n', 'f')
+#define MP4_moov MP4_ID('m', 'o', 'o', 'v')
+#define MP4_mp4a MP4_ID('m', 'p', '4', 'a')
+#define MP4_mp42 MP4_ID('m', 'p', '4', '2')
+#define MP4_qt MP4_ID('q', 't', ' ', ' ')
+#define MP4_soun MP4_ID('s', 'o', 'u', 'n')
+#define MP4_stbl MP4_ID('s', 't', 'b', 'l')
+#define MP4_stsd MP4_ID('s', 't', 's', 'd')
+#define MP4_stts MP4_ID('s', 't', 't', 's')
+#define MP4_trak MP4_ID('t', 'r', 'a', 'k')
+#define MP4_trkn MP4_ID('t', 'r', 'k', 'n')
+#define MP4_udta MP4_ID('u', 'd', 't', 'a')
+#define MP4_extra MP4_ID('-', '-', '-', '-')
+
+/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
+ * buffer.
+ */
+static unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
+ unsigned int buffer_left)
+{
+ unsigned int bytes_read = 0;
+
+ if (buffer_left == 0)
+ {
+ lseek(fd, size_left, SEEK_CUR); /* Skip everything */
+ }
+ else
+ {
+ /* Skip the data tag header - maybe we should parse it properly? */
+ lseek(fd, 16, SEEK_CUR);
+ size_left -= 16;
+
+ if (size_left > buffer_left)
+ {
+ read(fd, buffer, buffer_left);
+ lseek(fd, size_left - buffer_left, SEEK_CUR);
+ bytes_read = buffer_left;
+ }
+ else
+ {
+ read(fd, buffer, size_left);
+ bytes_read = size_left;
+ }
+ }
+
+ return bytes_read;
+}
+
+/* Read a string tag from an MP4 file */
+static unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer,
+ unsigned int* buffer_left, char** dest)
+{
+ unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
+ *buffer_left - 1);
+ unsigned int length = 0;
+
+ if (bytes_read)
+ {
+ (*buffer)[bytes_read] = 0;
+ *dest = *buffer;
+ length = strlen(*buffer) + 1;
+ *buffer_left -= length;
+ *buffer += length;
+ }
+ else
+ {
+ *dest = NULL;
+ }
+
+ return length;
+}
+
+static unsigned int read_mp4_atom(int fd, unsigned int* size,
+ unsigned int* type, unsigned int size_left)
+{
+ read_uint32be(fd, size);
+ read_uint32be(fd, type);
+
+ if (*size == 1)
+ {
+ /* FAT32 doesn't support files this big, so something seems to
+ * be wrong. (64-bit sizes should only be used when required.)
+ */
+ errno = EFBIG;
+ *type = 0;
+ return 0;
+ }
+
+ if (*size > 0)
+ {
+ if (*size > size_left)
+ {
+ size_left = 0;
+ }
+ else
+ {
+ size_left -= *size;
+ }
+
+ *size -= 8;
+ }
+ else
+ {
+ *size = size_left;
+ size_left = 0;
+ }
+
+ return size_left;
+}
+
+static unsigned int read_mp4_length(int fd, unsigned int* size)
+{
+ unsigned int length = 0;
+ int bytes = 0;
+ unsigned char c;
+
+ do
+ {
+ read(fd, &c, 1);
+ bytes++;
+ (*size)--;
+ length = (length << 7) | (c & 0x7F);
+ }
+ while ((c & 0x80) && (bytes < 4) && (*size > 0));
+
+ return length;
+}
+
+static bool read_mp4_esds(int fd, struct mp3entry* id3,
+ unsigned int* size)
+{
+ unsigned char buf[8];
+ bool sbr = false;
+
+ lseek(fd, 4, SEEK_CUR); /* Version and flags. */
+ read(fd, buf, 1); /* Verify ES_DescrTag. */
+ *size -= 5;
+
+ if (*buf == 3)
+ {
+ /* read length */
+ if (read_mp4_length(fd, size) < 20)
+ {
+ return sbr;
+ }
+
+ lseek(fd, 3, SEEK_CUR);
+ *size -= 3;
+ }
+ else
+ {
+ lseek(fd, 2, SEEK_CUR);
+ *size -= 2;
+ }
+
+ read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */
+ *size -= 1;
+
+ if (*buf != 4)
+ {
+ return sbr;
+ }
+
+ if (read_mp4_length(fd, size) < 13)
+ {
+ return sbr;
+ }
+
+ lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */
+ read(fd, buf, 1);
+ *size -= 14;
+
+ if (*buf != 5) /* Verify DecSpecificInfoTag. */
+ {
+ return sbr;
+ }
+
+ {
+ static const int sample_rates[] =
+ {
+ 96000, 88200, 64000, 48000, 44100, 32000,
+ 24000, 22050, 16000, 12000, 11025, 8000
+ };
+ unsigned long bits;
+ unsigned int length;
+ unsigned int index;
+ unsigned int type;
+
+ /* Read the (leading part of the) decoder config. */
+ length = read_mp4_length(fd, size);
+ length = MIN(length, *size);
+ length = MIN(length, sizeof(buf));
+ memset(buf, 0, sizeof(buf));
+ read(fd, buf, length);
+ *size -= length;
+
+ /* Maybe time to write a simple read_bits function... */
+
+ /* Decoder config format:
+ * Object type - 5 bits
+ * Frequency index - 4 bits
+ * Channel configuration - 4 bits
+ */
+ bits = get_long_be(buf);
+ type = bits >> 27; /* Object type - 5 bits */
+ index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */
+
+ if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (type == 5)
+ {
+ DEBUGF("MP4: SBR\n");
+ unsigned int old_index = index;
+
+ sbr = true;
+ index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */
+
+ if (index == 15)
+ {
+ /* 17 bits read so far... */
+ bits = get_long_be(&buf[2]);
+ id3->frequency = (bits >> 7) & 0x00ffffff;
+ }
+ else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (old_index == index)
+ {
+ /* Downsampled SBR */
+ id3->frequency *= 2;
+ }
+ }
+ /* Skip 13 bits from above, plus 3 bits, then read 11 bits */
+ else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7))
+ {
+ /* extensionAudioObjectType */
+ DEBUGF("MP4: extensionAudioType\n");
+ type = bits & 0x1f; /* Object type - 5 bits*/
+ bits = get_long_be(&buf[4]);
+
+ if (type == 5)
+ {
+ sbr = bits >> 31;
+
+ if (sbr)
+ {
+ unsigned int old_index = index;
+
+ /* 1 bit read so far */
+ index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */
+
+ if (index == 15)
+ {
+ /* 5 bits read so far */
+ id3->frequency = (bits >> 3) & 0x00ffffff;
+ }
+ else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
+ {
+ id3->frequency = sample_rates[index];
+ }
+
+ if (old_index == index)
+ {
+ /* Downsampled SBR */
+ id3->frequency *= 2;
+ }
+ }
+ }
+ }
+
+ if (!sbr && (id3->frequency <= 24000) && (length <= 2))
+ {
+ /* Double the frequency for low-frequency files without a "long"
+ * DecSpecificConfig header. The file may or may not contain SBR,
+ * but here we guess it does if the header is short. This can
+ * fail on some files, but it's the best we can do, short of
+ * decoding (parts of) the file.
+ */
+ id3->frequency *= 2;
+ }
+ }
+
+ return sbr;
+}
+
+static bool read_mp4_tags(int fd, struct mp3entry* id3,
+ unsigned int size_left)
+{
+ unsigned int size;
+ unsigned int type;
+ unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ char* buffer = id3->id3v2buf;
+ bool cwrt = false;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff,
+ type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */
+
+ switch (type)
+ {
+ case MP4_cnam:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->title);
+ break;
+
+ case MP4_cART:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->artist);
+ break;
+
+ case MP4_calb:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->album);
+ break;
+
+ case MP4_cwrt:
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ cwrt = false;
+ break;
+
+ case MP4_gnre:
+ {
+ unsigned short genre;
+
+ read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
+ id3->genre_string = id3_get_num_genre(betoh16(genre) - 1);
+ }
+ break;
+
+ case MP4_trkn:
+ {
+ unsigned short n[2];
+
+ read_mp4_tag(fd, size, (char*) &n, sizeof(n));
+ id3->tracknum = betoh16(n[1]);
+ }
+ break;
+
+ case MP4_extra:
+ {
+ char tag_name[TAG_NAME_LENGTH];
+ unsigned int sub_size;
+
+ /* "mean" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, sub_size - 4, SEEK_CUR);
+ /* "name" atom */
+ read_uint32be(fd, &sub_size);
+ size -= sub_size;
+ lseek(fd, 8, SEEK_CUR);
+ sub_size -= 12;
+
+ if (sub_size > sizeof(tag_name) - 1)
+ {
+ read(fd, tag_name, sizeof(tag_name) - 1);
+ lseek(fd, sub_size - sizeof(tag_name) - 1, SEEK_CUR);
+ tag_name[sizeof(tag_name) - 1] = 0;
+ }
+ else
+ {
+ read(fd, tag_name, sub_size);
+ tag_name[sub_size] = 0;
+ }
+
+ if ((strcasecmp(tag_name, "composer") == 0) && !cwrt)
+ {
+ read_mp4_tag_string(fd, size, &buffer, &buffer_left,
+ &id3->composer);
+ }
+ else if (strcasecmp(tag_name, "iTunSMPB") == 0)
+ {
+ char value[TAG_VALUE_LENGTH];
+ char* value_p = value;
+ char* any;
+ unsigned int length = sizeof(value);
+
+ read_mp4_tag_string(fd, size, &value_p, &length, &any);
+ id3->lead_trim = get_itunes_int32(value, 1);
+ id3->tail_trim = get_itunes_int32(value, 2);
+ DEBUGF("AAC: lead_trim %d, tail_trim %d\n",
+ id3->lead_trim, id3->tail_trim);
+ }
+ else
+ {
+ char* any;
+ unsigned int length = read_mp4_tag_string(fd, size,
+ &buffer, &buffer_left, &any);
+
+ if (length > 0)
+ {
+ /* Re-use the read buffer as the dest buffer... */
+ buffer -= length;
+ buffer_left += length;
+
+ if (parse_replaygain(tag_name, buffer, id3,
+ buffer, buffer_left) > 0)
+ {
+ /* Data used, keep it. */
+ buffer += length;
+ buffer_left -= length;
+ }
+ }
+ }
+ }
+ break;
+
+ default:
+ lseek(fd, size, SEEK_CUR);
+ break;
+ }
+ }
+ while ((size_left > 0) && (errno == 0));
+
+ return true;
+}
+
+static bool read_mp4_container(int fd, struct mp3entry* id3,
+ unsigned int size_left)
+{
+ unsigned int size;
+ unsigned int type;
+ unsigned int handler = 0;
+ bool rc = true;
+
+ do
+ {
+ size_left = read_mp4_atom(fd, &size, &type, size_left);
+
+ /* DEBUGF("Atom: '%c%c%c%c' (0x%08x, %d bytes left)\n",
+ (type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff,
+ type & 0xff, type, size); */
+
+ switch (type)
+ {
+ case MP4_ftyp:
+ {
+ unsigned int id;
+
+ read_uint32be(fd, &id);
+ size -= 4;
+
+ if ((id != MP4_M4A) && (id != MP4_M4B) && (id != MP4_mp42)
+ && (id != MP4_qt) && (id != MP4_3gp6))
+ {
+ DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n",
+ id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff,
+ id & 0xff);
+ return false;
+ }
+ }
+ break;
+
+ case MP4_meta:
+ lseek(fd, 4, SEEK_CUR); /* Skip version */
+ size -= 4;
+ /* Fall through */
+
+ case MP4_moov:
+ case MP4_udta:
+ case MP4_mdia:
+ case MP4_stbl:
+ case MP4_trak:
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_ilst:
+ if (handler == MP4_mdir)
+ {
+ rc = read_mp4_tags(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_minf:
+ if (handler == MP4_soun)
+ {
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ }
+ break;
+
+ case MP4_stsd:
+ lseek(fd, 8, SEEK_CUR);
+ size -= 8;
+ rc = read_mp4_container(fd, id3, size);
+ size = 0;
+ break;
+
+ case MP4_hdlr:
+ lseek(fd, 8, SEEK_CUR);
+ read_uint32be(fd, &handler);
+ size -= 12;
+ /* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff,
+ handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */
+ break;
+
+ case MP4_stts:
+ {
+ unsigned int entries;
+ unsigned int i;
+
+ lseek(fd, 4, SEEK_CUR);
+ read_uint32be(fd, &entries);
+ id3->samples = 0;
+
+ for (i = 0; i < entries; i++)
+ {
+ unsigned int n;
+ unsigned int l;
+
+ read_uint32be(fd, &n);
+ read_uint32be(fd, &l);
+ id3->samples += n * l;
+ }
+
+ size = 0;
+ }
+ break;
+
+ case MP4_mp4a:
+ case MP4_alac:
+ {
+ unsigned int frequency;
+
+ id3->codectype = (type == MP4_mp4a) ? AFMT_AAC : AFMT_ALAC;
+ lseek(fd, 22, SEEK_CUR);
+ read_uint32be(fd, &frequency);
+ size -= 26;
+ id3->frequency = frequency;
+
+ if (type == MP4_mp4a)
+ {
+ unsigned int subsize;
+ unsigned int subtype;
+
+ /* Get frequency from the decoder info tag, if possible. */
+ lseek(fd, 2, SEEK_CUR);
+ /* The esds atom is a part of the mp4a atom, so ignore
+ * the returned size (it's already accounted for).
+ */
+ read_mp4_atom(fd, &subsize, &subtype, size);
+ size -= 10;
+
+ if (subtype == MP4_esds)
+ {
+ read_mp4_esds(fd, id3, &size);
+ }
+ }
+ }
+ break;
+
+ case MP4_mdat:
+ id3->filesize = size;
+ break;
+
+ default:
+ break;
+ }
+
+ lseek(fd, size, SEEK_CUR);
+ }
+ while (rc && (size_left > 0) && (errno == 0) && (id3->filesize == 0));
+ /* Break on non-zero filesize, since Rockbox currently doesn't support
+ * metadata after the mdat atom (which sets the filesize field).
+ */
+
+ return rc;
+}
+
+bool get_mp4_metadata(int fd, struct mp3entry* id3)
+{
+ id3->codectype = AFMT_UNKNOWN;
+ id3->filesize = 0;
+ errno = 0;
+
+ if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0)
+ && (id3->samples > 0) && (id3->frequency > 0)
+ && (id3->filesize > 0))
+ {
+ if (id3->codectype == AFMT_UNKNOWN)
+ {
+ logf("Not an ALAC or AAC file");
+ return false;
+ }
+
+ id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
+
+ if (id3->length <= 0)
+ {
+ logf("mp4 length invalid!");
+ return false;
+ }
+
+ id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
+ DEBUGF("MP4 bitrate %d, frequency %ld Hz, length %ld ms\n",
+ id3->bitrate, id3->frequency, id3->length);
+ }
+ else
+ {
+ logf("MP4 metadata error");
+ DEBUGF("MP4 metadata error. errno %d, length %ld, frequency %ld, filesize %ld\n",
+ errno, id3->length, id3->frequency, id3->filesize);
+ return false;
+ }
+
+ return true;
+}
diff --git a/apps/metadata/mpc.c b/apps/metadata/mpc.c
new file mode 100644
index 0000000000..60e8202ff5
--- /dev/null
+++ b/apps/metadata/mpc.c
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "logf.h"
+#include "replaygain.h"
+
+bool get_musepack_metadata(int fd, struct mp3entry *id3)
+{
+ const int32_t sfreqs_sv7[4] = { 44100, 48000, 37800, 32000 };
+ uint32_t header[8];
+ uint64_t samples = 0;
+ int i;
+
+ if (!skip_id3v2(fd, id3))
+ return false;
+ if (read(fd, header, 4*8) != 4*8) return false;
+ /* Musepack files are little endian, might need swapping */
+ for (i = 1; i < 8; i++)
+ header[i] = letoh32(header[i]);
+ if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */
+ unsigned int streamversion;
+
+ header[0] = letoh32(header[0]);
+ streamversion = (header[0] >> 24) & 15;
+ if (streamversion >= 8) {
+ return false; /* SV8 or higher don't exist yet, so no support */
+ } else if (streamversion == 7) {
+ unsigned int gapless = (header[5] >> 31) & 0x0001;
+ unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff;
+ int track_gain, album_gain;
+ unsigned int bufused;
+
+ id3->frequency = sfreqs_sv7[(header[2] >> 16) & 0x0003];
+ samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */
+ if (gapless)
+ samples -= 1152 - last_frame_samples;
+ else
+ samples -= 481; /* Musepack subband synth filter delay */
+
+ /* Extract ReplayGain data from header */
+ track_gain = (int16_t)((header[3] >> 16) & 0xffff);
+ id3->track_gain = get_replaygain_int(track_gain);
+ id3->track_peak = ((uint16_t)(header[3] & 0xffff)) << 9;
+
+ album_gain = (int16_t)((header[4] >> 16) & 0xffff);
+ id3->album_gain = get_replaygain_int(album_gain);
+ id3->album_peak = ((uint16_t)(header[4] & 0xffff)) << 9;
+
+ /* Write replaygain values to strings for use in id3 screen. We use
+ the XING header as buffer space since Musepack files shouldn't
+ need to use it in any other way */
+ id3->track_gain_string = (char *)id3->toc;
+ bufused = snprintf(id3->track_gain_string, 45,
+ "%d.%d dB", track_gain/100, abs(track_gain)%100);
+ id3->album_gain_string = (char *)id3->toc + bufused + 1;
+ bufused = snprintf(id3->album_gain_string, 45,
+ "%d.%d dB", album_gain/100, abs(album_gain)%100);
+ }
+ } else {
+ header[0] = letoh32(header[0]);
+ unsigned int streamversion = (header[0] >> 11) & 0x03FF;
+ if (streamversion != 4 && streamversion != 5 && streamversion != 6)
+ return false;
+ id3->frequency = 44100;
+ id3->track_gain = 0;
+ id3->track_peak = 0;
+ id3->album_gain = 0;
+ id3->album_peak = 0;
+
+ if (streamversion >= 5)
+ samples = (uint64_t)header[1]*1152; // 32 bit
+ else
+ samples = (uint64_t)(header[1] >> 16)*1152; // 16 bit
+
+ samples -= 576;
+ if (streamversion < 6)
+ samples -= 1152;
+ }
+
+ id3->vbr = true;
+ /* Estimate bitrate, we should probably subtract the various header sizes
+ here for super-accurate results */
+ id3->length = ((int64_t) samples * 1000) / id3->frequency;
+
+ if (id3->length <= 0)
+ {
+ logf("mpc length invalid!");
+ return false;
+ }
+
+ id3->filesize = filesize(fd);
+ id3->bitrate = id3->filesize * 8 / id3->length;
+ return true;
+}
diff --git a/apps/metadata/sid.c b/apps/metadata/sid.c
new file mode 100644
index 0000000000..0dff5f6268
--- /dev/null
+++ b/apps/metadata/sid.c
@@ -0,0 +1,88 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "atoi.h"
+#include "rbunicode.h"
+
+/* PSID metadata info is available here:
+ http://www.unusedino.de/ec64/technical/formats/sidplay.html */
+bool get_sid_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ int read_bytes;
+ char *p;
+
+
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 0x80)) < 0x80))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "PSID",4) != 0))
+ {
+ return false;
+ }
+
+ p = id3->id3v2buf;
+
+ /* Copy Title (assumed max 0x1f letters + 1 zero byte) */
+ id3->title = p;
+ buf[0x16+0x1f] = 0;
+ p = iso_decode(&buf[0x16], p, 0, strlen(&buf[0x16])+1);
+
+ /* Copy Artist (assumed max 0x1f letters + 1 zero byte) */
+ id3->artist = p;
+ buf[0x36+0x1f] = 0;
+ p = iso_decode(&buf[0x36], p, 0, strlen(&buf[0x36])+1);
+
+ /* Copy Year (assumed max 4 letters + 1 zero byte) */
+ buf[0x56+0x4] = 0;
+ id3->year = atoi(&buf[0x56]);
+
+ /* Copy Album (assumed max 0x1f-0x05 letters + 1 zero byte) */
+ id3->album = p;
+ buf[0x56+0x1f] = 0;
+ p = iso_decode(&buf[0x5b], p, 0, strlen(&buf[0x5b])+1);
+
+ id3->bitrate = 706;
+ id3->frequency = 44100;
+ /* New idea as posted by Marco Alanen (ravon):
+ * Set the songlength in seconds to the number of subsongs
+ * so every second represents a subsong.
+ * Users can then skip the current subsong by seeking
+ *
+ * Note: the number of songs is a 16bit value at 0xE, so this code only
+ * uses the lower 8 bits of the counter.
+ */
+ id3->length = (buf[0xf]-1)*1000;
+ id3->vbr = false;
+ id3->filesize = filesize(fd);
+
+ return true;
+}
diff --git a/apps/metadata/spc.c b/apps/metadata/spc.c
new file mode 100644
index 0000000000..8d85518714
--- /dev/null
+++ b/apps/metadata/spc.c
@@ -0,0 +1,126 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "debug.h"
+#include "atoi.h"
+#include "rbunicode.h"
+
+bool get_spc_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char * buf = (unsigned char *)id3->path;
+ int read_bytes;
+ char * p;
+
+ unsigned long length;
+ unsigned long fade;
+ bool isbinary = true;
+ int i;
+
+ /* try to get the ID666 tag */
+ if ((lseek(fd, 0x2e, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 0xD2)) < 0xD2))
+ {
+ DEBUGF("lseek or read failed\n");
+ return false;
+ }
+
+ p = id3->id3v2buf;
+
+ id3->title = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 32;
+
+ id3->album = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 48;
+
+ id3->comment = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+ buf += 32;
+
+ /* Date check */
+ if(buf[2] == '/' && buf[5] == '/')
+ isbinary = false;
+
+ /* Reserved bytes check */
+ if(buf[0xD2 - 0x2E - 112] >= '0' &&
+ buf[0xD2 - 0x2E - 112] <= '9' &&
+ buf[0xD3 - 0x2E - 112] == 0x00)
+ isbinary = false;
+
+ /* is length & fade only digits? */
+ for (i=0;i<8 && (
+ (buf[0xA9 - 0x2E - 112+i]>='0'&&buf[0xA9 - 0x2E - 112+i]<='9') ||
+ buf[0xA9 - 0x2E - 112+i]=='\0');
+ i++);
+ if (i==8) isbinary = false;
+
+ if(isbinary) {
+ id3->year = buf[0] | (buf[1]<<8);
+ buf += 11;
+
+ length = (buf[0] | (buf[1]<<8) | (buf[2]<<16)) * 1000;
+ buf += 3;
+
+ fade = (buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24));
+ buf += 4;
+ } else {
+ char tbuf[6];
+
+ buf += 6;
+ buf[4] = 0;
+ id3->year = atoi(buf);
+ buf += 5;
+
+ memcpy(tbuf, buf, 3);
+ tbuf[3] = 0;
+ length = atoi(tbuf) * 1000;
+ buf += 3;
+
+ memcpy(tbuf, buf, 5);
+ tbuf[5] = 0;
+ fade = atoi(tbuf);
+ buf += 5;
+ }
+
+ id3->artist = p;
+ buf[31] = 0;
+ p = iso_decode(buf, p, 0, 32);
+
+ if (length==0) {
+ length=3*60*1000; /* 3 minutes */
+ fade=5*1000; /* 5 seconds */
+ }
+
+ id3->length = length+fade;
+
+ return true;
+}
diff --git a/apps/metadata/speex.c b/apps/metadata/speex.c
new file mode 100644
index 0000000000..e5f6a127ae
--- /dev/null
+++ b/apps/metadata/speex.c
@@ -0,0 +1,212 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "logf.h"
+
+/* A simple parser to read vital metadata from an Ogg Speex file. Returns
+ * false if metadata needed by the Speex codec couldn't be read.
+ */
+
+bool get_speex_metadata(int fd, struct mp3entry* id3)
+{
+ /* An Ogg File is split into pages, each starting with the string
+ * "OggS". Each page has a timestamp (in PCM samples) referred to as
+ * the "granule position".
+ *
+ * An Ogg Speex has the following structure:
+ * 1) Identification header (containing samplerate, numchannels, etc)
+ Described in this page: (http://www.speex.org/manual2/node7.html)
+ * 2) Comment header - containing the Vorbis Comments
+ * 3) Many audio packets...
+ */
+
+ /* Use the path name of the id3 structure as a temporary buffer. */
+ unsigned char* buf = (unsigned char*)id3->path;
+ long comment_size;
+ long remaining = 0;
+ long last_serial = 0;
+ long serial, r;
+ int segments;
+ int i;
+ bool eof = false;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 33))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 0))
+ {
+ return false;
+ }
+
+ /* We need to ensure the serial number from this page is the same as the
+ * one from the last page (since we only support a single bitstream).
+ */
+ serial = get_long_le(&buf[14]);
+ if ((lseek(fd, 33, SEEK_SET) < 0)||(read(fd, buf, 58) < 4))
+ {
+ return false;
+ }
+
+ id3->frequency = get_slong(&buf[31]);
+ last_serial = get_long_le(&buf[27]);/*temporary, header size*/
+ id3->bitrate = get_long_le(&buf[47]);
+ id3->vbr = get_long_le(&buf[55]);
+ id3->filesize = filesize(fd);
+ /* Comments are in second Ogg page */
+ if (lseek(fd, 28+last_serial/*(temporary for header size)*/, SEEK_SET) < 0)
+ {
+ return false;
+ }
+
+ /* Minimum header length for Ogg pages is 27. */
+ if (read(fd, buf, 27) < 27)
+ {
+ return false;
+ }
+
+ if (memcmp(buf, "OggS", 4) !=0 )
+ {
+ return false;
+ }
+
+ segments = buf[26];
+ /* read in segment table */
+ if (read(fd, buf, segments) < segments)
+ {
+ return false;
+ }
+
+ /* The second packet in a vorbis stream is the comment packet. It *may*
+ * extend beyond the second page, but usually does not. Here we find the
+ * length of the comment packet (or the rest of the page if the comment
+ * packet extends to the third page).
+ */
+ for (i = 0; i < segments; i++)
+ {
+ remaining += buf[i];
+ /* The last segment of a packet is always < 255 bytes */
+ if (buf[i] < 255)
+ {
+ break;
+ }
+ }
+
+ comment_size = remaining;
+
+ /* Failure to read the tags isn't fatal. */
+ read_vorbis_tags(fd, id3, remaining);
+
+ /* We now need to search for the last page in the file - identified by
+ * by ('O','g','g','S',0) and retrieve totalsamples.
+ */
+
+ /* A page is always < 64 kB */
+ if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ remaining = 0;
+
+ while (!eof)
+ {
+ r = read(fd, &buf[remaining], MAX_PATH - remaining);
+
+ if (r <= 0)
+ {
+ eof = true;
+ }
+ else
+ {
+ remaining += r;
+ }
+
+ /* Inefficient (but simple) search */
+ i = 0;
+
+ while (i < (remaining - 3))
+ {
+ if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
+ {
+ if (i < (remaining - 17))
+ {
+ /* Note that this only reads the low 32 bits of a
+ * 64 bit value.
+ */
+ id3->samples = get_long_le(&buf[i + 6]);
+ last_serial = get_long_le(&buf[i + 14]);
+
+ /* If this page is very small the beginning of the next
+ * header could be in buffer. Jump near end of this header
+ * and continue */
+ i += 27;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ if (i < remaining)
+ {
+ /* Move the remaining bytes to start of buffer.
+ * Reuse var 'segments' as it is no longer needed */
+ segments = 0;
+ while (i < remaining)
+ {
+ buf[segments++] = buf[i++];
+ }
+ remaining = segments;
+ }
+ else
+ {
+ /* Discard the rest of the buffer */
+ remaining = 0;
+ }
+ }
+
+ /* This file has mutiple vorbis bitstreams (or is corrupt). */
+ /* FIXME we should display an error here. */
+ if (serial != last_serial)
+ {
+ logf("serialno mismatch");
+ logf("%ld", serial);
+ logf("%ld", last_serial);
+ return false;
+ }
+
+ id3->length = (id3->samples / id3->frequency) * 1000;
+ id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
+ return true;
+}
diff --git a/apps/metadata/vorbis.c b/apps/metadata/vorbis.c
new file mode 100644
index 0000000000..5112615e47
--- /dev/null
+++ b/apps/metadata/vorbis.c
@@ -0,0 +1,330 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+#include "metadata_parsers.h"
+#include "structec.h"
+#include "logf.h"
+
+/* Read the items in a Vorbis comment packet. Returns true the items were
+ * fully read, false otherwise.
+ */
+bool read_vorbis_tags(int fd, struct mp3entry *id3,
+ long tag_remaining)
+{
+ char *buf = id3->id3v2buf;
+ int32_t comment_count;
+ int32_t len;
+ int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
+ int i;
+
+ if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
+ {
+ return false;
+ }
+
+ if ((lseek(fd, len, SEEK_CUR) < 0)
+ || (ecread(fd, &comment_count, 1, "l", IS_BIG_ENDIAN)
+ < (long) sizeof(comment_count)))
+ {
+ return false;
+ }
+
+ tag_remaining -= len + sizeof(len) + sizeof(comment_count);
+
+ if (tag_remaining <= 0)
+ {
+ return true;
+ }
+
+ for (i = 0; i < comment_count; i++)
+ {
+ char name[TAG_NAME_LENGTH];
+ char value[TAG_VALUE_LENGTH];
+ int32_t read_len;
+
+ if (tag_remaining < 4)
+ {
+ break;
+ }
+
+ if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
+ {
+ return false;
+ }
+
+ tag_remaining -= 4;
+
+ /* Quit if we've passed the end of the page */
+ if (tag_remaining < len)
+ {
+ break;
+ }
+
+ tag_remaining -= len;
+ read_len = read_string(fd, name, sizeof(name), '=', len);
+
+ if (read_len < 0)
+ {
+ return false;
+ }
+
+ len -= read_len;
+
+ if (read_string(fd, value, sizeof(value), -1, len) < 0)
+ {
+ return false;
+ }
+
+ len = parse_tag(name, value, id3, buf, buf_remaining,
+ TAGTYPE_VORBIS);
+ buf += len;
+ buf_remaining -= len;
+ }
+
+ /* Skip to the end of the block */
+ if (tag_remaining)
+ {
+ if (lseek(fd, tag_remaining, SEEK_CUR) < 0)
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/* A simple parser to read vital metadata from an Ogg Vorbis file.
+ * Calls get_speex_metadata if a speex file is identified. Returns
+ * false if metadata needed by the Vorbis codec couldn't be read.
+ */
+bool get_vorbis_metadata(int fd, struct mp3entry* id3)
+{
+ /* An Ogg File is split into pages, each starting with the string
+ * "OggS". Each page has a timestamp (in PCM samples) referred to as
+ * the "granule position".
+ *
+ * An Ogg Vorbis has the following structure:
+ * 1) Identification header (containing samplerate, numchannels, etc)
+ * 2) Comment header - containing the Vorbis Comments
+ * 3) Setup header - containing codec setup information
+ * 4) Many audio packets...
+ */
+
+ /* Use the path name of the id3 structure as a temporary buffer. */
+ unsigned char* buf = (unsigned char *)id3->path;
+ long comment_size;
+ long remaining = 0;
+ long last_serial = 0;
+ long serial, r;
+ int segments;
+ int i;
+ bool eof = false;
+
+ if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 4))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[29], "vorbis", 6) != 0))
+ {
+ if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 0))
+ {
+ return false;
+ }
+ else
+ {
+ id3->codectype = AFMT_SPEEX;
+ return get_speex_metadata(fd, id3);
+ }
+ }
+
+ /* We need to ensure the serial number from this page is the same as the
+ * one from the last page (since we only support a single bitstream).
+ */
+ serial = get_long_le(&buf[14]);
+ id3->frequency = get_long_le(&buf[40]);
+ id3->filesize = filesize(fd);
+
+ /* Comments are in second Ogg page */
+ if (lseek(fd, 58, SEEK_SET) < 0)
+ {
+ return false;
+ }
+
+ /* Minimum header length for Ogg pages is 27. */
+ if (read(fd, buf, 27) < 27)
+ {
+ return false;
+ }
+
+ if (memcmp(buf, "OggS", 4) !=0 )
+ {
+ return false;
+ }
+
+ segments = buf[26];
+
+ /* read in segment table */
+ if (read(fd, buf, segments) < segments)
+ {
+ return false;
+ }
+
+ /* The second packet in a vorbis stream is the comment packet. It *may*
+ * extend beyond the second page, but usually does not. Here we find the
+ * length of the comment packet (or the rest of the page if the comment
+ * packet extends to the third page).
+ */
+ for (i = 0; i < segments; i++)
+ {
+ remaining += buf[i];
+
+ /* The last segment of a packet is always < 255 bytes */
+ if (buf[i] < 255)
+ {
+ break;
+ }
+ }
+
+ /* Now read in packet header (type and id string) */
+ if (read(fd, buf, 7) < 7)
+ {
+ return false;
+ }
+
+ comment_size = remaining;
+ remaining -= 7;
+
+ /* The first byte of a packet is the packet type; comment packets are
+ * type 3.
+ */
+ if ((buf[0] != 3) || (memcmp(buf + 1, "vorbis", 6) !=0))
+ {
+ return false;
+ }
+
+ /* Failure to read the tags isn't fatal. */
+ read_vorbis_tags(fd, id3, remaining);
+
+ /* We now need to search for the last page in the file - identified by
+ * by ('O','g','g','S',0) and retrieve totalsamples.
+ */
+
+ /* A page is always < 64 kB */
+ if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
+ {
+ return false;
+ }
+
+ remaining = 0;
+
+ while (!eof)
+ {
+ r = read(fd, &buf[remaining], MAX_PATH - remaining);
+
+ if (r <= 0)
+ {
+ eof = true;
+ }
+ else
+ {
+ remaining += r;
+ }
+
+ /* Inefficient (but simple) search */
+ i = 0;
+
+ while (i < (remaining - 3))
+ {
+ if ((buf[i] == 'O') && (memcmp(&buf[i], "OggS", 4) == 0))
+ {
+ if (i < (remaining - 17))
+ {
+ /* Note that this only reads the low 32 bits of a
+ * 64 bit value.
+ */
+ id3->samples = get_long_le(&buf[i + 6]);
+ last_serial = get_long_le(&buf[i + 14]);
+
+ /* If this page is very small the beginning of the next
+ * header could be in buffer. Jump near end of this header
+ * and continue */
+ i += 27;
+ }
+ else
+ {
+ break;
+ }
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ if (i < remaining)
+ {
+ /* Move the remaining bytes to start of buffer.
+ * Reuse var 'segments' as it is no longer needed */
+ segments = 0;
+ while (i < remaining)
+ {
+ buf[segments++] = buf[i++];
+ }
+ remaining = segments;
+ }
+ else
+ {
+ /* Discard the rest of the buffer */
+ remaining = 0;
+ }
+ }
+
+ /* This file has mutiple vorbis bitstreams (or is corrupt). */
+ /* FIXME we should display an error here. */
+ if (serial != last_serial)
+ {
+ logf("serialno mismatch");
+ logf("%ld", serial);
+ logf("%ld", last_serial);
+ return false;
+ }
+
+ id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
+
+ if (id3->length <= 0)
+ {
+ logf("ogg length invalid!");
+ return false;
+ }
+
+ id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
+ id3->vbr = true;
+
+ return true;
+}
+
diff --git a/apps/metadata/wave.c b/apps/metadata/wave.c
new file mode 100644
index 0000000000..d29f9f5363
--- /dev/null
+++ b/apps/metadata/wave.c
@@ -0,0 +1,128 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2005 Dave Chapman
+ *
+ * All files in this archive are subject to the GNU General Public License.
+ * See the file COPYING in the source tree root for full license agreement.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ****************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <inttypes.h>
+
+#include "system.h"
+#include "id3.h"
+#include "metadata_common.h"
+
+bool get_wave_metadata(int fd, struct mp3entry* id3)
+{
+ /* Use the trackname part of the id3 structure as a temporary buffer */
+ unsigned char* buf = (unsigned char *)id3->path;
+ unsigned long totalsamples = 0;
+ unsigned long channels = 0;
+ unsigned long bitspersample = 0;
+ unsigned long numbytes = 0;
+ int read_bytes;
+ int i;
+
+ /* get RIFF chunk header */
+ if ((lseek(fd, 0, SEEK_SET) < 0)
+ || ((read_bytes = read(fd, buf, 12)) < 12))
+ {
+ return false;
+ }
+
+ if ((memcmp(buf, "RIFF",4) != 0)
+ || (memcmp(&buf[8], "WAVE", 4) !=0 ))
+ {
+ return false;
+ }
+
+ /* iterate over WAVE chunks until 'data' chunk */
+ while (true)
+ {
+ /* get chunk header */
+ if ((read_bytes = read(fd, buf, 8)) < 8)
+ return false;
+
+ /* chunkSize */
+ i = get_long_le(&buf[4]);
+
+ if (memcmp(buf, "fmt ", 4) == 0)
+ {
+ /* get rest of chunk */
+ if ((read_bytes = read(fd, buf, 16)) < 16)
+ return false;
+
+ i -= 16;
+
+ /* skipping wFormatTag */
+ /* wChannels */
+ channels = buf[2] | (buf[3] << 8);
+ /* dwSamplesPerSec */
+ id3->frequency = get_long_le(&buf[4]);
+ /* dwAvgBytesPerSec */
+ id3->bitrate = (get_long_le(&buf[8]) * 8) / 1000;
+ /* skipping wBlockAlign */
+ /* wBitsPerSample */
+ bitspersample = buf[14] | (buf[15] << 8);
+ }
+ else if (memcmp(buf, "data", 4) == 0)
+ {
+ numbytes = i;
+ break;
+ }
+ else if (memcmp(buf, "fact", 4) == 0)
+ {
+ /* dwSampleLength */
+ if (i >= 4)
+ {
+ /* get rest of chunk */
+ if ((read_bytes = read(fd, buf, 4)) < 4)
+ return false;
+
+ i -= 4;
+ totalsamples = get_long_le(buf);
+ }
+ }
+
+ /* seek to next chunk (even chunk sizes must be padded) */
+ if (i & 0x01)
+ i++;
+
+ if(lseek(fd, i, SEEK_CUR) < 0)
+ return false;
+ }
+
+ if ((numbytes == 0) || (channels == 0))
+ {
+ return false;
+ }
+
+ if (totalsamples == 0)
+ {
+ /* for PCM only */
+ totalsamples = numbytes
+ / ((((bitspersample - 1) / 8) + 1) * channels);
+ }
+
+ id3->vbr = false; /* All WAV files are CBR */
+ id3->filesize = filesize(fd);
+
+ /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
+ id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
+
+ return true;
+}