From e31ebcca44d3ef85904087786cc369ab2281e8e5 Mon Sep 17 00:00:00 2001 From: markun Date: Sat, 16 Jun 2007 18:19:51 +0000 Subject: [PATCH] split up the metadata parser git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13637 a1c6a512-1295-4272-9138-f99709370657 --- apps/FILES | 1 + apps/SOURCES | 15 + apps/metadata.c | 2888 +++++--------------------------------- apps/metadata/adx.c | 115 ++ apps/metadata/aiff.c | 98 ++ apps/metadata/ape.c | 132 ++ apps/metadata/flac.c | 122 ++ apps/metadata/metadata_common.c | 273 ++++ apps/metadata/metadata_common.h | 45 + apps/metadata/metadata_parsers.h | 31 + apps/metadata/monkeys.c | 92 ++ apps/metadata/mp4.c | 669 +++++++++ apps/metadata/mpc.c | 118 ++ apps/metadata/sid.c | 88 ++ apps/metadata/spc.c | 126 ++ apps/metadata/speex.c | 212 +++ apps/metadata/vorbis.c | 330 +++++ apps/metadata/wave.c | 128 ++ 18 files changed, 2968 insertions(+), 2515 deletions(-) rewrite apps/metadata.c (85%) create mode 100644 apps/metadata/adx.c create mode 100644 apps/metadata/aiff.c create mode 100644 apps/metadata/ape.c create mode 100644 apps/metadata/flac.c create mode 100644 apps/metadata/metadata_common.c create mode 100644 apps/metadata/metadata_common.h create mode 100644 apps/metadata/metadata_parsers.h create mode 100644 apps/metadata/monkeys.c create mode 100644 apps/metadata/mp4.c create mode 100644 apps/metadata/mpc.c create mode 100644 apps/metadata/sid.c create mode 100644 apps/metadata/spc.c create mode 100644 apps/metadata/speex.c create mode 100644 apps/metadata/vorbis.c create mode 100644 apps/metadata/wave.c diff --git a/apps/FILES b/apps/FILES index 936e817fd..85d70cbe7 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 ccb9ca277..a48017c7c 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 dissimilarity index 85% index a0c2f31ee..3b2855bc2 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -1,2515 +1,373 @@ -/*************************************************************************** - * __________ __ ___. - * 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 -#include -#include -#include -#include - -#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 "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]; -}; - -struct apetag_item_header -{ - long length; - long flags; -}; - -#if CONFIG_CODEC == SWCODEC -static const unsigned short a52_bitrates[] = -{ - 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, - 192, 224, 256, 320, 384, 448, 512, 576, 640 -}; - -/* Only store frame sizes for 44.1KHz - others are simply multiples - of the bitrate */ -static const unsigned short a52_441framesizes[] = -{ - 69 * 2, 70 * 2, 87 * 2, 88 * 2, 104 * 2, 105 * 2, 121 * 2, - 122 * 2, 139 * 2, 140 * 2, 174 * 2, 175 * 2, 208 * 2, 209 * 2, - 243 * 2, 244 * 2, 278 * 2, 279 * 2, 348 * 2, 349 * 2, 417 * 2, - 418 * 2, 487 * 2, 488 * 2, 557 * 2, 558 * 2, 696 * 2, 697 * 2, - 835 * 2, 836 * 2, 975 * 2, 976 * 2, 1114 * 2, 1115 * 2, 1253 * 2, - 1254 * 2, 1393 * 2, 1394 * 2 -}; - -static const long wavpack_sample_rates [] = -{ - 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, - 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 */ - - -/* Simple file type probing by looking at the filename extension. */ -unsigned int probe_file_format(const char *filename) -{ - char *suffix; - unsigned int i; - - suffix = strrchr(filename, '.'); - - if (suffix == NULL) - { - return AFMT_UNKNOWN; - } - - /* skip '.' */ - suffix++; - - for (i = 1; i < AFMT_NUM_CODECS; i++) - { - /* search extension list for type */ - const char *ext = audio_formats[i].ext_list; - - do - { - if (strcasecmp(suffix, ext) == 0) - { - return i; - } - - ext += strlen(ext) + 1; - } - while (*ext != '\0'); - } - - return AFMT_UNKNOWN; -} - -/* Get metadata for track - return false if parsing showed problems with the - * file that would prevent playback. - */ -bool get_metadata(struct track_info* track, int fd, const char* trackname, - bool v1first) -{ -#if CONFIG_CODEC == SWCODEC - unsigned char* buf; - unsigned long totalsamples; - int i; -#endif - - /* Take our best guess at the codec type based on file extension */ - track->id3.codectype = probe_file_format(trackname); - - /* Load codec specific track tag information and confirm the codec type. */ - switch (track->id3.codectype) - { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - if (!get_mp3_metadata(fd, &track->id3, trackname, v1first)) - { - return false; - } - - break; - -#if CONFIG_CODEC == SWCODEC - case AFMT_FLAC: - if (!get_flac_metadata(fd, &(track->id3))) - { - return false; - } - - break; - - case AFMT_APE: - if (!get_monkeys_metadata(fd, &(track->id3))) - { - return false; - } - read_ape_tags(fd, &(track->id3)); - break; - - case AFMT_MPC: - if (!get_musepack_metadata(fd, &(track->id3))) - return false; - read_ape_tags(fd, &(track->id3)); - break; - - case AFMT_OGG_VORBIS: - if (!get_vorbis_metadata(fd, &(track->id3)))/*detects and handles Ogg/Speex files*/ - { - return false; - } - - break; - - case AFMT_SPEEX: - if (!get_speex_metadata(fd, &(track->id3))) - { - return false; - } - - break; - - case AFMT_PCM_WAV: - if (!get_wave_metadata(fd, &(track->id3))) - { - return false; - } - - break; - - case AFMT_WAVPACK: - /* A simple parser to read basic information from a WavPack file. This - * now works with self-extrating WavPack files. This no longer fails on - * WavPack files containing floating-point audio data because these are - * now converted to standard Rockbox format in the decoder. - */ - - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf = (unsigned char *)track->id3.path; - - for (i = 0; i < 256; ++i) { - - /* at every 256 bytes into file, try to read a WavPack header */ - - if ((lseek(fd, i * 256, SEEK_SET) < 0) || (read(fd, buf, 32) < 32)) - { - return false; - } - - /* if valid WavPack 4 header version & not floating data, break */ - - if (memcmp (buf, "wvpk", 4) == 0 && buf [9] == 4 && - (buf [8] >= 2 && buf [8] <= 0x10)) - { - break; - } - } - - if (i == 256) { - logf ("%s is not a WavPack file\n", trackname); - return false; - } - - track->id3.vbr = true; /* All WavPack files are VBR */ - track->id3.filesize = filesize (fd); - - if ((buf [20] | buf [21] | buf [22] | buf [23]) && - (buf [12] & buf [13] & buf [14] & buf [15]) != 0xff) - { - int srindx = ((buf [26] >> 7) & 1) + ((buf [27] << 1) & 14); - - if (srindx == 15) - { - track->id3.frequency = 44100; - } - else - { - track->id3.frequency = wavpack_sample_rates[srindx]; - } - - totalsamples = get_long_le(&buf[12]); - track->id3.length = totalsamples / (track->id3.frequency / 100) * 10; - track->id3.bitrate = filesize (fd) / (track->id3.length / 8); - } - - read_ape_tags(fd, &track->id3); /* use any apetag info we find */ - break; - - case AFMT_A52: - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf = (unsigned char *)track->id3.path; - - if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5)) - { - return false; - } - - if ((buf[0] != 0x0b) || (buf[1] != 0x77)) - { - logf("%s is not an A52/AC3 file\n",trackname); - return false; - } - - i = buf[4] & 0x3e; - - if (i > 36) - { - logf("A52: Invalid frmsizecod: %d\n",i); - return false; - } - - track->id3.bitrate = a52_bitrates[i >> 1]; - track->id3.vbr = false; - track->id3.filesize = filesize(fd); - - switch (buf[4] & 0xc0) - { - case 0x00: - track->id3.frequency = 48000; - track->id3.bytesperframe=track->id3.bitrate * 2 * 2; - break; - - case 0x40: - track->id3.frequency = 44100; - track->id3.bytesperframe = a52_441framesizes[i]; - break; - - case 0x80: - track->id3.frequency = 32000; - track->id3.bytesperframe = track->id3.bitrate * 3 * 2; - break; - - default: - logf("A52: Invalid samplerate code: 0x%02x\n", buf[4] & 0xc0); - return false; - break; - } - - /* One A52 frame contains 6 blocks, each containing 256 samples */ - totalsamples = track->id3.filesize / track->id3.bytesperframe * 6 * 256; - track->id3.length = totalsamples / track->id3.frequency * 1000; - break; - - case AFMT_ALAC: - case AFMT_AAC: - if (!get_mp4_metadata(fd, &(track->id3))) - { - return false; - } - - break; - - case AFMT_SHN: - track->id3.vbr = true; - track->id3.filesize = filesize(fd); - if (!skip_id3v2(fd, &(track->id3))) - { - return false; - } - /* TODO: read the id3v2 header if it exists */ - break; - - case AFMT_SID: - if (!get_sid_metadata(fd, &(track->id3))) - { - return false; - } - break; - case AFMT_SPC: - if(!get_spc_metadata(fd, &(track->id3))) - { - DEBUGF("get_spc_metadata error\n"); - } - - track->id3.filesize = filesize(fd); - track->id3.genre_string = id3_get_num_genre(36); - break; - case AFMT_ADX: - if (!get_adx_metadata(fd, &(track->id3))) - { - DEBUGF("get_adx_metadata error\n"); - return false; - } - - break; - case AFMT_NSF: - buf = (unsigned char *)track->id3.path; - if ((lseek(fd, 0, SEEK_SET) < 0) || ((read(fd, buf, 8)) < 8)) - { - DEBUGF("lseek or read failed\n"); - return false; - } - track->id3.vbr = false; - track->id3.filesize = filesize(fd); - if (memcmp(buf,"NESM",4) && memcmp(buf,"NSFE",4)) return false; - break; - - case AFMT_AIFF: - if (!get_aiff_metadata(fd, &(track->id3))) - { - return false; - } - - break; - -#endif /* CONFIG_CODEC == SWCODEC */ - - default: - /* If we don't know how to read the metadata, assume we can't play - the file */ - return false; - break; - } - - /* We have successfully read the metadata from the file */ - -#ifndef __PCTOOL__ - if (cuesheet_is_enabled() && look_for_cuesheet_file(trackname, NULL)) - { - track->id3.cuesheet_type = 1; - } -#endif - - lseek(fd, 0, SEEK_SET); - strncpy(track->id3.path, trackname, sizeof(track->id3.path)); - track->taginfo_ready = true; - - return true; -} - +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include +#include +#include + +#include "system.h" +#include "playback.h" +#include "debug.h" +#include "logf.h" +#include "cuesheet.h" + +#if CONFIG_CODEC == SWCODEC + +#include "metadata/metadata_common.h" +#include "metadata/metadata_parsers.h" + +static const unsigned short a52_bitrates[] = +{ + 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, + 192, 224, 256, 320, 384, 448, 512, 576, 640 +}; + +/* Only store frame sizes for 44.1KHz - others are simply multiples + of the bitrate */ +static const unsigned short a52_441framesizes[] = +{ + 69 * 2, 70 * 2, 87 * 2, 88 * 2, 104 * 2, 105 * 2, 121 * 2, + 122 * 2, 139 * 2, 140 * 2, 174 * 2, 175 * 2, 208 * 2, 209 * 2, + 243 * 2, 244 * 2, 278 * 2, 279 * 2, 348 * 2, 349 * 2, 417 * 2, + 418 * 2, 487 * 2, 488 * 2, 557 * 2, 558 * 2, 696 * 2, 697 * 2, + 835 * 2, 836 * 2, 975 * 2, 976 * 2, 1114 * 2, 1115 * 2, 1253 * 2, + 1254 * 2, 1393 * 2, 1394 * 2 +}; + +static const long wavpack_sample_rates [] = +{ + 6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000, + 32000, 44100, 48000, 64000, 88200, 96000, 192000 +}; + +#endif /* CONFIG_CODEC == SWCODEC */ + + +/* Simple file type probing by looking at the filename extension. */ +unsigned int probe_file_format(const char *filename) +{ + char *suffix; + unsigned int i; + + suffix = strrchr(filename, '.'); + + if (suffix == NULL) + { + return AFMT_UNKNOWN; + } + + /* skip '.' */ + suffix++; + + for (i = 1; i < AFMT_NUM_CODECS; i++) + { + /* search extension list for type */ + const char *ext = audio_formats[i].ext_list; + + do + { + if (strcasecmp(suffix, ext) == 0) + { + return i; + } + + ext += strlen(ext) + 1; + } + while (*ext != '\0'); + } + + return AFMT_UNKNOWN; +} + +/* Get metadata for track - return false if parsing showed problems with the + * file that would prevent playback. + */ +bool get_metadata(struct track_info* track, int fd, const char* trackname, + bool v1first) +{ +#if CONFIG_CODEC == SWCODEC + unsigned char* buf; + unsigned long totalsamples; + int i; +#endif + + /* Take our best guess at the codec type based on file extension */ + track->id3.codectype = probe_file_format(trackname); + + /* Load codec specific track tag information and confirm the codec type. */ + switch (track->id3.codectype) + { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + if (!get_mp3_metadata(fd, &track->id3, trackname, v1first)) + { + return false; + } + + break; + +#if CONFIG_CODEC == SWCODEC + case AFMT_FLAC: + if (!get_flac_metadata(fd, &(track->id3))) + { + return false; + } + + break; + + case AFMT_APE: + if (!get_monkeys_metadata(fd, &(track->id3))) + { + return false; + } + read_ape_tags(fd, &(track->id3)); + break; + + case AFMT_MPC: + if (!get_musepack_metadata(fd, &(track->id3))) + return false; + read_ape_tags(fd, &(track->id3)); + break; + + case AFMT_OGG_VORBIS: + if (!get_vorbis_metadata(fd, &(track->id3)))/*detects and handles Ogg/Speex files*/ + { + return false; + } + + break; + + case AFMT_SPEEX: + if (!get_speex_metadata(fd, &(track->id3))) + { + return false; + } + + break; + + case AFMT_PCM_WAV: + if (!get_wave_metadata(fd, &(track->id3))) + { + return false; + } + + break; + + case AFMT_WAVPACK: + /* A simple parser to read basic information from a WavPack file. This + * now works with self-extrating WavPack files. This no longer fails on + * WavPack files containing floating-point audio data because these are + * now converted to standard Rockbox format in the decoder. + */ + + /* Use the trackname part of the id3 structure as a temporary buffer */ + buf = (unsigned char *)track->id3.path; + + for (i = 0; i < 256; ++i) { + + /* at every 256 bytes into file, try to read a WavPack header */ + + if ((lseek(fd, i * 256, SEEK_SET) < 0) || (read(fd, buf, 32) < 32)) + { + return false; + } + + /* if valid WavPack 4 header version & not floating data, break */ + + if (memcmp (buf, "wvpk", 4) == 0 && buf [9] == 4 && + (buf [8] >= 2 && buf [8] <= 0x10)) + { + break; + } + } + + if (i == 256) { + logf ("%s is not a WavPack file\n", trackname); + return false; + } + + track->id3.vbr = true; /* All WavPack files are VBR */ + track->id3.filesize = filesize (fd); + + if ((buf [20] | buf [21] | buf [22] | buf [23]) && + (buf [12] & buf [13] & buf [14] & buf [15]) != 0xff) + { + int srindx = ((buf [26] >> 7) & 1) + ((buf [27] << 1) & 14); + + if (srindx == 15) + { + track->id3.frequency = 44100; + } + else + { + track->id3.frequency = wavpack_sample_rates[srindx]; + } + + totalsamples = get_long_le(&buf[12]); + track->id3.length = totalsamples / (track->id3.frequency / 100) * 10; + track->id3.bitrate = filesize (fd) / (track->id3.length / 8); + } + + read_ape_tags(fd, &track->id3); /* use any apetag info we find */ + break; + + case AFMT_A52: + /* Use the trackname part of the id3 structure as a temporary buffer */ + buf = (unsigned char *)track->id3.path; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5)) + { + return false; + } + + if ((buf[0] != 0x0b) || (buf[1] != 0x77)) + { + logf("%s is not an A52/AC3 file\n",trackname); + return false; + } + + i = buf[4] & 0x3e; + + if (i > 36) + { + logf("A52: Invalid frmsizecod: %d\n",i); + return false; + } + + track->id3.bitrate = a52_bitrates[i >> 1]; + track->id3.vbr = false; + track->id3.filesize = filesize(fd); + + switch (buf[4] & 0xc0) + { + case 0x00: + track->id3.frequency = 48000; + track->id3.bytesperframe=track->id3.bitrate * 2 * 2; + break; + + case 0x40: + track->id3.frequency = 44100; + track->id3.bytesperframe = a52_441framesizes[i]; + break; + + case 0x80: + track->id3.frequency = 32000; + track->id3.bytesperframe = track->id3.bitrate * 3 * 2; + break; + + default: + logf("A52: Invalid samplerate code: 0x%02x\n", buf[4] & 0xc0); + return false; + break; + } + + /* One A52 frame contains 6 blocks, each containing 256 samples */ + totalsamples = track->id3.filesize / track->id3.bytesperframe * 6 * 256; + track->id3.length = totalsamples / track->id3.frequency * 1000; + break; + + case AFMT_ALAC: + case AFMT_AAC: + if (!get_mp4_metadata(fd, &(track->id3))) + { + return false; + } + + break; + + case AFMT_SHN: + track->id3.vbr = true; + track->id3.filesize = filesize(fd); + if (!skip_id3v2(fd, &(track->id3))) + { + return false; + } + /* TODO: read the id3v2 header if it exists */ + break; + + case AFMT_SID: + if (!get_sid_metadata(fd, &(track->id3))) + { + return false; + } + break; + case AFMT_SPC: + if(!get_spc_metadata(fd, &(track->id3))) + { + DEBUGF("get_spc_metadata error\n"); + } + + track->id3.filesize = filesize(fd); + track->id3.genre_string = id3_get_num_genre(36); + break; + case AFMT_ADX: + if (!get_adx_metadata(fd, &(track->id3))) + { + DEBUGF("get_adx_metadata error\n"); + return false; + } + + break; + case AFMT_NSF: + buf = (unsigned char *)track->id3.path; + if ((lseek(fd, 0, SEEK_SET) < 0) || ((read(fd, buf, 8)) < 8)) + { + DEBUGF("lseek or read failed\n"); + return false; + } + track->id3.vbr = false; + track->id3.filesize = filesize(fd); + if (memcmp(buf,"NESM",4) && memcmp(buf,"NSFE",4)) return false; + break; + + case AFMT_AIFF: + if (!get_aiff_metadata(fd, &(track->id3))) + { + return false; + } + + break; + +#endif /* CONFIG_CODEC == SWCODEC */ + + default: + /* If we don't know how to read the metadata, assume we can't play + the file */ + return false; + break; + } + + /* We have successfully read the metadata from the file */ + +#ifndef __PCTOOL__ + if (cuesheet_is_enabled() && look_for_cuesheet_file(trackname, NULL)) + { + track->id3.cuesheet_type = 1; + } +#endif + + lseek(fd, 0, SEEK_SET); + strncpy(track->id3.path, trackname, sizeof(track->id3.path)); + track->taginfo_ready = true; + + return true; +} + diff --git a/apps/metadata/adx.c b/apps/metadata/adx.c new file mode 100644 index 000000000..44b07a14b --- /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 +#include +#include +#include +#include + +#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 000000000..3b155caa2 --- /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 +#include +#include +#include +#include + +#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 000000000..ac071be6f --- /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 +#include +#include +#include +#include + +#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 000000000..5b3644ede --- /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 +#include +#include +#include +#include + +#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 000000000..00b03841f --- /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 +#include +#include +#include +#include + +#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 000000000..70e708010 --- /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 000000000..f52ce69dd --- /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 000000000..bda3a0164 --- /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 +#include +#include +#include +#include + +#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 000000000..6523bb655 --- /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 +#include +#include +#include +#include + +#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 000000000..60e8202ff --- /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 +#include +#include +#include +#include + +#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 000000000..0dff5f626 --- /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 +#include +#include +#include +#include + +#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 000000000..8d8551871 --- /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 +#include +#include +#include +#include + +#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 000000000..e5f6a127a --- /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 +#include +#include +#include +#include + +#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 000000000..5112615e4 --- /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 +#include +#include +#include +#include + +#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 000000000..d29f9f536 --- /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 +#include +#include +#include +#include + +#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; +} -- 2.11.4.GIT