diff options
| author | Magnus Holmgren <magnushol@gmail.com> | 2005-09-22 16:58:03 +0000 |
|---|---|---|
| committer | Magnus Holmgren <magnushol@gmail.com> | 2005-09-22 16:58:03 +0000 |
| commit | ccdae5dbe44385f580e16a6a0754041bf075f557 (patch) | |
| tree | cfc790d8e3f9f577abdb436a184fe41ef0dee382 /apps/metadata.c | |
| parent | aafb343d10c9190d76e5983d55056a8564fee01a (diff) | |
| download | rockbox-ccdae5dbe44385f580e16a6a0754041bf075f557.zip rockbox-ccdae5dbe44385f580e16a6a0754041bf075f557.tar.gz rockbox-ccdae5dbe44385f580e16a6a0754041bf075f557.tar.bz2 rockbox-ccdae5dbe44385f580e16a6a0754041bf075f557.tar.xz | |
iriver: Metadata code cleanup: 1) Remove 4k static buffer. 2) Generalized tag parsing, so APE and Vorbis tags can share some code. 3) Code size reduction (800+ bytes). 4) Better UTF-8 parser (I hope...). 5) More consistent return value on errors.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@7539 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'apps/metadata.c')
| -rw-r--r-- | apps/metadata.c | 1643 |
1 files changed, 866 insertions, 777 deletions
diff --git a/apps/metadata.c b/apps/metadata.c index 03ecf00..b57bf0f 100644 --- a/apps/metadata.c +++ b/apps/metadata.c @@ -20,6 +20,7 @@ #include <string.h> #include <stdlib.h> #include <ctype.h> +#include <inttypes.h> #include "metadata.h" #include "mp3_playback.h" @@ -27,912 +28,1000 @@ #include "atoi.h" #include "replaygain.h" #include "debug.h" +#include "system.h" -/* Simple file type probing by looking filename extension. */ -unsigned int probe_file_format(const char *filename) -{ - char *suffix; - - suffix = strrchr(filename, '.'); - if (suffix == NULL) - return AFMT_UNKNOWN; - suffix += 1; - - if (!strcasecmp("mp1", suffix)) - return AFMT_MPA_L1; - else if (!strcasecmp("mp2", suffix)) - return AFMT_MPA_L2; - else if (!strcasecmp("mpa", suffix)) - return AFMT_MPA_L2; - else if (!strcasecmp("mp3", suffix)) - return AFMT_MPA_L3; - else if (!strcasecmp("ogg", suffix)) - return AFMT_OGG_VORBIS; - else if (!strcasecmp("wav", suffix)) - return AFMT_PCM_WAV; - else if (!strcasecmp("flac", suffix)) - return AFMT_FLAC; - else if (!strcasecmp("mpc", suffix)) - return AFMT_MPC; - else if ((!strcasecmp("a52", suffix)) || (!strcasecmp("ac3", suffix))) - return AFMT_A52; - else if (!strcasecmp("wv", suffix)) - return AFMT_WAVPACK; - - return AFMT_UNKNOWN; -} +enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS }; -unsigned short a52_bitrates[]={32,40,48,56,64,80,96, - 112,128,160,192,224,256,320, - 384,448,512,576,640}; +#define APETAG_HEADER_LENGTH 32 +#define APETAG_HEADER_FORMAT "8LLLL" +#define APETAG_ITEM_HEADER_FORMAT "LL" +#define APETAG_ITEM_TYPE_MASK 3 -/* Only store frame sizes for 44.1KHz - others are simply multiples - of the bitrate */ -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}; +#define TAG_NAME_LENGTH 32 +#define TAG_VALUE_LENGTH 128 + +struct apetag_header +{ + char id[8]; + long version; + long length; + long item_count; + long flags; + char reserved[8]; +}; -const long wavpack_sample_rates [] = { 6000, 8000, 9600, 11025, 12000, 16000, - 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, 192000 }; +struct apetag_item_header +{ + long length; + long flags; +}; -/* Get metadata for track - return false if parsing showed problems with the - file that would prevent playback. */ +struct format_list +{ + char format; + char extension[5]; +}; -static bool get_apetag_info (struct mp3entry *entry, int fd); +static const struct format_list formats[] = +{ + { AFMT_MPA_L1, "mp1" }, + { AFMT_MPA_L2, "mp2" }, + { AFMT_MPA_L2, "mpa" }, + { AFMT_MPA_L3, "mp3" }, + { AFMT_OGG_VORBIS, "ogg" }, + { AFMT_PCM_WAV, "wav" }, + { AFMT_FLAC, "flac" }, + { AFMT_MPC, "mpc" }, + { AFMT_A52, "a52" }, + { AFMT_A52, "ac3" }, + { AFMT_WAVPACK, "wv" }, +}; -static bool get_vorbis_comments (struct mp3entry *entry, size_t bytes_remaining, int fd); +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 +}; -static void little_endian_to_native (void *data, char *format); +/* 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 +}; -bool get_metadata(struct track_info* track, int fd, const char* trackname, - bool v1first) { - unsigned long totalsamples,bytespersample,channels,bitspersample,numbytes; - unsigned long serialno=0, last_serialno=0; - int bytesperframe; - unsigned char* buf; - int i,j,eof; - int rc; - int segments; /* for Vorbis*/ - size_t bytes_remaining = 0; /* for Vorbis */ +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; - /* Load codec specific track tag information. */ - switch (track->id3.codectype) { - case AFMT_MPA_L1: - case AFMT_MPA_L2: - case AFMT_MPA_L3: - /* Should check the return value. */ - mp3info(&track->id3, trackname, v1first); - lseek(fd, 0, SEEK_SET); - - /* - logf("T:%s", track->id3.title); - logf("L:%d", track->id3.length); - logf("O:%d", track->id3.first_frame_offset); - logf("F:%d", track->id3.frequency); - */ - track->taginfo_ready = true; - break ; - - case AFMT_PCM_WAV: - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf=track->id3.path; - - lseek(fd, 0, SEEK_SET); - - rc = read(fd, buf, 44); - if (rc < 44) { - return false; - } + 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; +} - if ((memcmp(buf,"RIFF",4)!=0) || - (memcmp(&buf[8],"WAVEfmt",7)!=0)) { - logf("%s is not a WAV file\n",trackname); - return(false); - } +/* Convert a little-endian structure to native format using a format string. + * Does nothing on a little-endian machine. + */ +static void convert_endian(void *data, const char *format) +{ + while (*format) + { + switch (*format) + { + case 'L': + { + long* d = (long*) data; + + *d = SWAB32(*d); + data = d + 1; + } + + break; - /* FIX: Correctly parse WAV header - we assume canonical - 44-byte header */ + case 'S': + { + short* d = (short*) data; + + *d = SWAB16(*d); + data = d + 1; + } + + break; - bitspersample=buf[34]; - channels=buf[22]; + default: + if (isdigit(*format)) + { + data = ((char*) data) + *format - '0'; + } + + break; + } - if ((bitspersample!=16) || (channels != 2)) { - logf("Unsupported WAV file - %d bitspersample, %d channels\n", - bitspersample,channels); - return(false); - } + format++; + } +} - bytespersample=((bitspersample/8)*channels); - numbytes=(buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24)); - totalsamples=numbytes/bytespersample; +/* Read an unaligned 32-bit little endian long from buffer. */ +static unsigned long get_long(void* buf) +{ + unsigned char* p = (unsigned char*) buf; - track->id3.vbr=false; /* All WAV files are CBR */ - track->id3.filesize=filesize(fd); - track->id3.frequency=buf[24]|(buf[25]<<8)|(buf[26]<<16)|(buf[27]<<24); + return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); +} - /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ - track->id3.length=(totalsamples/track->id3.frequency)*1000; - track->id3.bitrate=(track->id3.frequency*bytespersample)/(1000/8); +/* Convert an UTF-8 string to Latin-1, overwriting the old string (the new + * string is never longer than the original, so this is safe). Non-latin-1 + * chars are replaced with '?'. + */ +static void convert_utf8(char* utf8) +{ + char* dest = utf8; + long code = 0; + unsigned char c; + int tail = 0; + int size = 0; + + while ((c = *utf8++) != 0) + { + if ((tail <= 0) && ((c <= 0x7f) || (c >= 0xc2))) + { + /* Start of new character. */ + if (c < 0x80) + { + size = 1; + } + else if (c < 0xe0) + { + size = 2; + c &= 0x1f; + } + else if (c < 0xf0) + { + size = 3; + c &= 0x0f; + } + else if (c < 0xf5) + { + size = 4; + c &= 0x07; + } + else + { + /* Invalid size. */ + size = 0; + } - lseek(fd, 0, SEEK_SET); - strncpy(track->id3.path,trackname,sizeof(track->id3.path)); - track->taginfo_ready = true; + code = c; + tail = size - 1; + } + else if ((tail > 0) && ((c & 0xc0) == 0x80)) + { + /* Valid continuation character. */ + code = (code << 6) | (c & 0x3f); + tail--; + + if (tail == 0) + { + if (((size == 2) && (code < 0x80)) + || ((size == 3) && (code < 0x800)) + || ((size == 4) && (code < 0x10000))) + { + /* Invalid encoding. */ + code = 0; + } + } + } + else + { + tail = -1; + } - break; + if ((tail == 0) && (code > 0)) + { + *dest++ = (code <= 0xff) ? (char) (code & 0xff) : '?'; + } + } - case AFMT_FLAC: - /* 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 */ + *dest = 0; +} - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf=track->id3.path; +/* 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 can be in more any format in a Vorbis tag, so don't try to + * parse it. + */ + if (type != TAGTYPE_VORBIS) + { + id3->year = atoi(value); + } + + 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 + { + 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; +} - lseek(fd, 0, SEEK_SET); +/* 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; - rc = read(fd, buf, 4); - if (rc < 4) { + if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0) + || (read(fd, &header, APETAG_HEADER_LENGTH) != APETAG_HEADER_LENGTH) + || (memcmp(header.id, "APETAGEX", sizeof(header.id)))) + { return false; - } - - if (memcmp(buf,"fLaC",4)!=0) { - logf("%s is not a FLAC file\n",trackname); - return(false); - } + } - while (1) { - rc = read(fd, buf, 4); - i = (buf[1]<<16)|(buf[2]<<8)|buf[3]; /* The length of the block */ + convert_endian(&header, APETAG_HEADER_FORMAT); + id3->genre = 0xff; - if ((buf[0]&0x7f)==0) { /* 0 is the STREAMINFO block */ - rc = read(fd, buf, i); /* FIXME: Don't trust the value of i */ - if (rc < 0) { + 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; - } - track->id3.vbr=true; /* All FLAC files are VBR */ - track->id3.filesize=filesize(fd); - - track->id3.frequency=(buf[10]<<12)|(buf[11]<<4)|((buf[12]&0xf0)>>4); + } - /* NOT NEEDED: bitspersample=(((buf[12]&0x01)<<4)|((buf[13]&0xf0)>>4))+1; */ + 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; - /* totalsamples is a 36-bit field, but we assume <= 32 bits are used */ - totalsamples=(buf[14]<<24)|(buf[15]<<16)|(buf[16]<<8)|buf[17]; + if (tag_remaining < sizeof(item)) + { + break; + } + + if (read(fd, &item, sizeof(item)) < (long) sizeof(item)) + { + return false; + } + + convert_endian(&item, APETAG_ITEM_HEADER_FORMAT); + tag_remaining -= sizeof(item); + r = read_string(fd, name, sizeof(name), 0, tag_remaining); + + if (r == -1) + { + return false; + } - /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ - track->id3.length=(totalsamples/track->id3.frequency)*1000; - track->id3.bitrate=(filesize(fd)*8)/track->id3.length; - } else if ((buf[0]&0x7f)==4) { /* 4 is the VORBIS_COMMENT block */ + tag_remaining -= r + item.length; - /* The next i bytes of the file contain the VORBIS COMMENTS - just skip them for now. */ - //lseek(fd, i, SEEK_CUR); - if (!get_vorbis_comments(&(track->id3), i, fd)) { - return false; - } + if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0) + { + long len; + + if (read_string(fd, value, sizeof(value), -1, item.length) + != item.length) + { + return false; + } - } else { - if (buf[0]&0x80) { /* If we have reached the last metadata block, abort. */ - break; - } else { - lseek(fd, i, SEEK_CUR); /* Skip to next metadata block */ - } + convert_utf8(value); + 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; + } + } } - } - - lseek(fd, 0, SEEK_SET); - strncpy(track->id3.path,trackname,sizeof(track->id3.path)); - track->taginfo_ready = true; - break; + } - case AFMT_OGG_VORBIS: - /* A simple parser to read vital metadata from an Ogg Vorbis file */ + return true; +} - /* 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". +/* 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; + long comment_count; + long len; + int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf); + int i; - 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... + id3->genre = 255; - */ + if (read(fd, &len, sizeof(len)) < (long) sizeof(len)) + { + return false; + } + + convert_endian(&len, "L"); + + if ((lseek(fd, len, SEEK_CUR) < 0) + || (read(fd, &comment_count, sizeof(comment_count)) + < (long) sizeof(comment_count))) + { + return false; + } + + convert_endian(&comment_count, "L"); + tag_remaining -= len + sizeof(len) + sizeof(comment_count); - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf=track->id3.path; + if (tag_remaining <= 0) + { + return true; + } - lseek(fd, 0, SEEK_SET); + for (i = 0; i < comment_count; i++) + { + char name[TAG_NAME_LENGTH]; + char value[TAG_VALUE_LENGTH]; + long read_len; - rc = read(fd, buf, 58); - if (rc < 4) { - return false; - } - - if ((memcmp(buf,"OggS",4)!=0) || (memcmp(&buf[29],"vorbis",6)!=0)) { - logf("%s is not an Ogg Vorbis file\n",trackname); - 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). - */ - serialno = buf[14]|(buf[15]<<8)|(buf[16]<<16)|(buf[17]<<24); - - /* Ogg stores integers in little-endian format. */ - track->id3.filesize=filesize(fd); - track->id3.frequency=buf[40]|(buf[41]<<8)|(buf[42]<<16)|(buf[43]<<24); - channels=buf[39]; - - /* 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) { - logf("1: Not an Ogg Vorbis file"); - 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++) { - bytes_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; - } - - /* 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)) { - logf("Not a vorbis comment packet"); - return false; - } - - bytes_remaining -= 7; - - if ( !get_vorbis_comments(&(track->id3), bytes_remaining, fd) ) { - logf("get_vorbis_comments failed"); - return(false); - } - - /* We now need to search for the last page in the file - identified by - by ('O','g','g','S',0) and retrieve totalsamples */ - - lseek(fd, -64*1024, SEEK_END); /* A page is always < 64 kB */ - eof=0; - j=0; /* The number of bytes currently in buffer */ - i=0; - totalsamples=0; - while (!eof) { - rc = read(fd, &buf[j], MAX_PATH-j); - if (rc <= 0) { - eof=1; - } else { - j+=rc; + if (tag_remaining < 4) + { + break; } - /* Inefficient (but simple) search */ - i=0; - while (i < (j-5)) { - if (memcmp(&buf[i],"OggS",5)==0) { - if (i < (j-17)) { - /* Note that this only reads the low 32 bits of a 64 bit value */ - totalsamples=(buf[i+6])|(buf[i+7]<<8)|(buf[i+8]<<16)|(buf[i+9]<<24); - last_serialno=(buf[i+14])|(buf[i+15]<<8)|(buf[i+16]<<16)|(buf[i+17]<<24); - j=0; /* We can discard the rest of the buffer */ - } else { - break; - } - } else { - i++; - } - } - if (i < (j-5)) { - /* Move OggS to start of buffer */ - while(i>0) buf[i--]=buf[j--]; - } else { - j=0; - } - } - - /* This file has mutiple vorbis bitstreams (or is corrupt) */ - /* FIXME we should display an error here */ - if (serialno != last_serialno) { - track->taginfo_ready=false; - logf("serialno mismatch"); - logf("%ld", serialno); - logf("%ld", last_serialno); - return false; - } - - track->id3.samples=totalsamples; - track->id3.length=(totalsamples/track->id3.frequency)*1000; - - /* The following calculation should use datasize, not filesize (i.e. exclude comments etc) */ - track->id3.bitrate=(filesize(fd)*8)/track->id3.length; - track->id3.vbr=true; - - lseek(fd, 0, SEEK_SET); - strncpy(track->id3.path,trackname,sizeof(track->id3.path)); - track->taginfo_ready = true; - break; - - case AFMT_WAVPACK: - /* A simple parser to read basic information from a WavPack file. - * This will fail on WavPack files that don't have the WavPack header - * as the first thing (i.e. self-extracting WavPack files) or WavPack - * files that have so much extra RIFF data stored in the first block - * that they don't have samples (very rare, I would think). - */ - - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf=track->id3.path; - - lseek(fd, 0, SEEK_SET); - - rc = read(fd, buf, 32); - if (rc < 32) { - return false; - } - - if (memcmp (buf, "wvpk", 4) != 0 || buf [9] != 4 || buf [8] < 2) { - 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 = (buf[15] << 24) | (buf[14] << 16) | (buf[13] << 8) | buf[12]; - track->id3.length = totalsamples / (track->id3.frequency / 100) * 10; - track->id3.bitrate = filesize (fd) / - (track->id3.length / 8); - } - - get_apetag_info (&track->id3, fd); /* use any apetag info we find */ - lseek (fd, 0, SEEK_SET); - strncpy (track->id3.path, trackname, sizeof (track->id3.path)); - track->taginfo_ready = true; - break; - - case AFMT_A52: - /* Use the trackname part of the id3 structure as a temporary buffer */ - buf=track->id3.path; - - lseek(fd, 0, SEEK_SET); - - /* We just need the first 5 bytes */ - rc = read(fd, buf, 5); - if (rc < 5) { - return false; - } - if ((buf[0]!=0x0b) || (buf[1]!=0x77)) { - logf("%s is not an A52/AC3 file\n",trackname); - return false; - } + if (read(fd, &len, sizeof(len)) < (long) sizeof(len)) + { + 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]; + convert_endian(&len, "L"); + tag_remaining -= 4; - track->id3.vbr=false; - track->id3.filesize = filesize (fd); + /* Quit if we've passed the end of the page */ + if (tag_remaining < len) + { + break; + } - switch (buf[4]&0xc0) { - case 0x00: - track->id3.frequency=48000; - bytesperframe=track->id3.bitrate*2*2; - break; - case 0x40: - track->id3.frequency=44100; - bytesperframe=a52_441framesizes[i]; - break; - case 0x80: - track->id3.frequency=32000; - bytesperframe=track->id3.bitrate*3*2; - break; - default: - logf("A52: Invalid samplerate code: 0x%02x\n",buf[4]&0xc0); - return false; - break; - } + tag_remaining -= len; + read_len = read_string(fd, name, sizeof(name), '=', len); + + if (read_len < 0) + { + return false; + } - /* One A52 frame contains 6 blocks, each containing 256 samples */ - totalsamples=(track->filesize/bytesperframe)*6*256; + len -= read_len; - track->id3.length=(totalsamples/track->id3.frequency)*1000; + if (read_string(fd, value, sizeof(value), -1, len) < 0) + { + return false; + } - lseek(fd, 0, SEEK_SET); - strncpy(track->id3.path,trackname,sizeof(track->id3.path)); - track->taginfo_ready = true; - break; + convert_utf8(value); + len = parse_tag(name, value, id3, buf, buf_remaining, + TAGTYPE_VORBIS); + buf += len; + buf_remaining -= len; + } - /* If we don't know how to read the metadata, just store the filename */ - default: - strncpy(track->id3.path,trackname,sizeof(track->id3.path)); - track->taginfo_ready = true; - break; - } + /* Skip to the end of the block */ + if (tag_remaining) + { + if (lseek(fd, tag_remaining, SEEK_CUR) < 0) + { + return false; + } + } - return true; + return true; } -/************************* APE TAG HANDLING CODE ****************************/ - -/* - * This is a first pass at APEv2 tag handling. I'm not sure if this should - * reside here, but I wanted to modify as little as possible since I don't - * have a feel for the complete system. It may be that APEv2 tags should be - * added to the ID3 handling code in the firmware directory. APEv2 tags are - * used in WavPack files and Musepack files by default, however they are - * also used in MP3 files sometimes (by Foobar2000). Also, WavPack files can - * also use ID3v1 tags (but not ID3v2), so it seems like some universal tag - * handler might be a reasonable approach. - * - * This code does not currently handle APEv1 tags, but I believe that this - * is not a problem because they were only used in Monkey's Audio files which - * will probably never be playable in RockBox (and certainly not by this CPU). +/* A simple parser to read vital metadata from an Ogg Vorbis file. Returns + * false if metadata needed by the Vorbis codec couldn't be read. */ - -#define APETAG_HEADER_FORMAT "8LLLL" -#define APETAG_HEADER_LENGTH 32 -#define APETAG_DATA_LIMIT 4096 - -struct apetag_header { - char id [8]; - long version, length, item_count, flags; - char res [8]; -}; - -static struct apetag { - struct apetag_header header; - char data [APETAG_DATA_LIMIT]; -} temp_apetag; - -static int get_apetag_item (struct apetag *tag, - const char *item, - char *value, - int size); - -static int load_apetag (int fd, struct apetag *tag); -static void UTF8ToAnsi (unsigned char *pUTF8); - -/* - * This function searches the specified file for an APEv2 tag and uses any - * information found there to populate the appropriate fields in the specified - * mp3entry structure. A temporary buffer is used to hold the tag during this - * operation. For now, the actual string data that needs to be held during the - * life of the track entry is stored in the "id3v2buf" field (which should not - * be used for any file that has an APEv2 tag). This limits the total space - * for the artist, title, album, composer and genre strings to 300 characters. - */ - -static bool get_apetag_info (struct mp3entry *entry, int fd) +static bool get_vorbis_metadata(int fd, struct mp3entry* id3) { - int rem_space = sizeof (entry->id3v2buf), str_space; - char *temp_buffer = entry->id3v2buf; + /* 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... + */ - if (rem_space <= 1 || !load_apetag (fd, &temp_apetag)) + /* Use the path name of the id3 structure as a temporary buffer. */ + unsigned char* buf = id3->path; + long comment_size; + long remaining = 0; + long last_serial = 0; + long serial; + 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)) + { + 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(&buf[14]); + id3->frequency = get_long(&buf[40]); + id3->filesize = filesize(fd); - if (get_apetag_item (&temp_apetag, "year", temp_buffer, rem_space)) - entry->year = atoi (temp_buffer); - - if (get_apetag_item (&temp_apetag, "track", temp_buffer, rem_space)) - entry->tracknum = atoi (temp_buffer); - - if (get_apetag_item (&temp_apetag, "replaygain_track_peak", temp_buffer, rem_space)) - entry->track_peak = get_replaypeak (temp_buffer); - - if (get_apetag_item (&temp_apetag, "replaygain_album_peak", temp_buffer, rem_space)) - entry->album_peak = get_replaypeak (temp_buffer); - - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "replaygain_track_gain", temp_buffer, rem_space)) { - entry->track_gain = get_replaygain (entry->track_gain_string = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + /* Comments are in second Ogg page */ + if (lseek(fd, 58, SEEK_SET) < 0) + { + return false; } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "replaygain_album_gain", temp_buffer, rem_space)) { - entry->album_gain = get_replaygain (entry->album_gain_string = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + /* Minimum header length for Ogg pages is 27. */ + if (read(fd, buf, 27) < 27) + { + return false; } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "artist", temp_buffer, rem_space)) { - UTF8ToAnsi (entry->artist = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + if (memcmp(buf, "OggS", 4) !=0 ) + { + return false; } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "title", temp_buffer, rem_space)) { - UTF8ToAnsi (entry->title = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + segments = buf[26]; + + /* read in segment table */ + if (read(fd, buf, segments) < segments) + { + return false; } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "album", temp_buffer, rem_space)) { - UTF8ToAnsi (entry->album = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + /* 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; + } } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "genre", temp_buffer, rem_space)) { - UTF8ToAnsi (entry->genre_string = temp_buffer); - str_space = strlen (temp_buffer) + 1; - temp_buffer += str_space; - rem_space -= str_space; + /* Now read in packet header (type and id string) */ + if (read(fd, buf, 7) < 7) + { + return false; } - if (rem_space > 1 && - get_apetag_item (&temp_apetag, "composer", temp_buffer, rem_space)) - UTF8ToAnsi (entry->composer = temp_buffer); + comment_size = remaining; + remaining -= 7; - return true; -} + /* 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; + } -/* - * Helper function to convert little-endian structures to easily usable native - * format using a format string (this does nothing on a little-endian machine). - */ + /* Failure to read the tags isn't fatal. */ + read_vorbis_tags(fd, id3, remaining); -static void little_endian_to_native (void *data, char *format) -{ - unsigned char *cp = (unsigned char *) data; - long temp; - - while (*format) { - switch (*format) { - case 'L': - temp = cp [0] + ((long) cp [1] << 8) + ((long) cp [2] << 16) + ((long) cp [3] << 24); - * (long *) cp = temp; - cp += 4; - break; + /* We now need to search for the last page in the file - identified by + * by ('O','g','g','S',0) and retrieve totalsamples. + */ - case 'S': - temp = cp [0] + (cp [1] << 8); - * (short *) cp = (short) temp; - cp += 2; - break; + if (lseek(fd, -64 * 1024, SEEK_END) < 0) /* A page is always < 64 kB */ + { + return false; + } - default: - if (*format >= '0' && *format <= '9') - cp += *format - '0'; + remaining = 0; - break; + while (!eof) + { + long 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 - 5)) + { + 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(&buf[i + 6]); + last_serial = get_long(&buf[i + 14]); + /* We can discard the rest of the buffer */ + remaining = 0; + } + else + { + break; + } + } + else + { + i++; + } } - format++; + if (i < (remaining - 5)) + { + /* Move OggS to start of buffer. */ + while (i >0) + { + buf[i--] = buf[remaining--]; + } + } + else + { + remaining = 0; + } } -} - -/* - * Attempt to obtain the named string-type item from the specified APEv2 tag. - * The tag value will be copied to "value" (including an appended terminating - * NULL) and the length of the string (including the NULL) will be returned. - * If the data will not fit in the specified "size" then it will be truncated - * early (but still terminated). If the specified item is not found then 0 is - * returned and written to the first character of "value". If "value" is - * passed in as NULL, then the specified size is ignored and the actual size - * required to store the value is returned. - * - * Note that this function does not work on binary tag data; only UTF-8 - * encoded strings. However, numeric data (like ReplayGain) is usually stored - * as strings. - * - * Also, APEv2 tags may have multiple values for a given item and these will - * all be copied to "value" with NULL separators (this is why the total data - * size is returned). Of course, it is possible to ignore any additional - * values by simply using up to the first NULL. - */ -static int get_apetag_item (struct apetag *tag, - const char *item, - char *value, - int size) -{ - if (value && size) - *value = 0; - - if (tag->header.id [0] == 'A') { - char *p = tag->data; - char *q = p + tag->header.length - APETAG_HEADER_LENGTH; - int i; + /* 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; + } - for (i = 0; i < tag->header.item_count; ++i) { - int vsize, flags, isize; + id3->length = (id3->samples / id3->frequency) * 1000; + id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length; + id3->vbr = true; + + return true; +} - vsize = * (long *) p; p += 4; - flags = * (long *) p; p += 4; - isize = strlen (p); +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 + */ - little_endian_to_native (&vsize, "L"); - little_endian_to_native (&flags, "L"); + /* Use the trackname part of the id3 structure as a temporary buffer */ + unsigned char* buf = id3->path; + bool rc = false; - if (p + isize + vsize + 1 > q) - break; + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 4) < 4)) + { + return rc; + } - if (isize && vsize && !stricmp (item, p) && !(flags & 6)) { + if (memcmp(buf,"fLaC",4) != 0) + { + return rc; + } - if (value) { - if (vsize + 1 > size) - vsize = size - 1; + 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]; - memcpy (value, p + isize + 1, vsize); - value [vsize] = 0; - } + if ((buf[0] & 0x7f) == 0) /* 0 is the STREAMINFO block */ + { + unsigned long totalsamples; - return vsize + 1; + /* 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 = (buf[14] << 24) | (buf[15] << 16) + | (buf[16] << 8) | buf[17]; + + /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ + id3->length = (totalsamples / id3->frequency) * 1000; + 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 - p += isize + vsize + 1; + { + /* Skip to next metadata block */ + if (lseek(fd, i, SEEK_CUR) < 0) + { + return rc; + } + } } } - - return 0; + + return true; } -/* - * Attempt to load an APEv2 tag from the specified file into the specified - * structure. If the APEv2 tag will not fit into the predefined data size, - * then the tag is not loaded. A return value of TRUE indicates success. - */ - -static int load_apetag (int fd, struct apetag *tag) +/* Simple file type probing by looking at the filename extension. */ +unsigned int probe_file_format(const char *filename) { - if (lseek (fd, -APETAG_HEADER_LENGTH, SEEK_END) == -1 || - read (fd, &tag->header, APETAG_HEADER_LENGTH) != APETAG_HEADER_LENGTH || - strncmp (tag->header.id, "APETAGEX", 8)) { - tag->header.id [0] = 0; - return false; - } - - little_endian_to_native (&tag->header, APETAG_HEADER_FORMAT); - - if (tag->header.version == 2000 && tag->header.item_count && - tag->header.length > APETAG_HEADER_LENGTH && - tag->header.length < APETAG_DATA_LIMIT) { - - int data_size = tag->header.length - APETAG_HEADER_LENGTH; + char *suffix; + unsigned int i; + + suffix = strrchr(filename, '.'); - if (lseek (fd, -tag->header.length, SEEK_END) == -1 || - read (fd, tag->data, data_size) != data_size) { - tag->header.id [0] = 0; - return false; - } - else - return true; + if (suffix == NULL) + { + return AFMT_UNKNOWN; } + + suffix += 1; - tag->header.id [0] = 0; - return false; + for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++) + { + if (strcasecmp(suffix, formats[i].extension) == 0) + { + return formats[i].format; + } + } + + return AFMT_UNKNOWN; } -/* - * This is a *VERY* boneheaded attempt to convert UTF-8 unicode character - * strings to ANSI. It simply maps the 16-bit Unicode characters that are - * less than 0x100 directly to an 8-bit value, and turns all the rest into - * question marks. This can be done "in-place" because the resulting string - * can only get smaller. +/* Get metadata for track - return false if parsing showed problems with the + * file that would prevent playback. */ - -static void UTF8ToAnsi (unsigned char *pUTF8) +bool get_metadata(struct track_info* track, int fd, const char* trackname, + bool v1first) { - unsigned char *pAnsi = pUTF8; - unsigned short widechar = 0; - int trail_bytes = 0; - - while (*pUTF8) { - if (*pUTF8 & 0x80) { - if (*pUTF8 & 0x40) { - if (trail_bytes) { - trail_bytes = 0; - *pAnsi++ = widechar < 0x100 ? widechar : '?'; - } - else { - char temp = *pUTF8; + unsigned char* buf; + unsigned long totalsamples; + unsigned long bytespersample; + unsigned long channels; + unsigned long bitspersample; + unsigned long numbytes; + int bytesperframe; + int i; + + /* Load codec specific track tag information. */ + + switch (track->id3.codectype) + { + case AFMT_MPA_L1: + case AFMT_MPA_L2: + case AFMT_MPA_L3: + if (mp3info(&track->id3, trackname, v1first)) + { + return false; + } - while (temp & 0x80) { - trail_bytes++; - temp <<= 1; - } + break; - widechar = temp >> trail_bytes--; - } - } - else if (trail_bytes) { - widechar = (widechar << 6) | (*pUTF8 & 0x3f); + case AFMT_FLAC: + if (!get_flac_metadata(fd, &(track->id3))) + { + return false; + } - if (!--trail_bytes) - *pAnsi++ = widechar < 0x100 ? widechar : '?'; - } + break; + + case AFMT_OGG_VORBIS: + if (!get_vorbis_metadata(fd, &(track->id3))) + { + return false; } - else - *pAnsi++ = *pUTF8; - pUTF8++; - } + break; - *pAnsi = 0; -} + case AFMT_PCM_WAV: + /* Use the trackname part of the id3 structure as a temporary buffer */ + buf = track->id3.path; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 44) < 44)) + { + return false; + } -/* This function extracts the information stored in the Vorbis comment header - * and stores it in id3v2buf of the current track. Currently the combined - * lengths of title, genre, album, and artist must be no longer than 296 bytes - * (the remaining 4 bytes are the null bytes at the end of the strings). This - * is wrong, since vorbis comments can be up to 2^32 - 1 bytes long. In - * practice I don't think this limitation will cause a problem. - * - * According to the docs, a vorbis bitstream *must* have a comment packet even - * if that packet is empty. Therefore if this function returns false the - * bitstream is corrupt and shouldn't be used. - * - * Additionally, vorbis comments *may* take up more than one Ogg page, and this - * only looks at the first page of comments. - */ -static bool get_vorbis_comments (struct mp3entry *entry, size_t bytes_remaining, int fd) -{ - int vendor_length; - int comment_count; - int comment_length; - int i = 0; - unsigned char temp[300]; - int buffer_remaining = sizeof(entry->id3v2buf) + sizeof(entry->id3v1buf); - char *buffer = entry->id3v2buf; - char **p = NULL; - - /* We've read in all header info, now start reading comments */ - - /* Set id3v1 genre to 255 (effectively 'none'), otherwise tracks - * without genre tags will show up as 'Blues' - */ - entry->genre=255; + if ((memcmp(buf,"RIFF",4) !=0 ) + || (memcmp(&buf[8], "WAVEfmt", 7) !=0 )) + { + logf("Not a WAV: %s\n", trackname); + return false; + } - if (read(fd, &vendor_length, 4) < 4) { - return false; - } - little_endian_to_native(&vendor_length, "L"); - lseek(fd, vendor_length, SEEK_CUR); + /* FIX: Correctly parse WAV header - we assume canonical + * 44-byte header + */ - if (read(fd, &comment_count, 4) < 4) { - return false; - } - little_endian_to_native(&comment_count, "L"); - bytes_remaining -= (vendor_length + 8); - if ( bytes_remaining <= 0 ) { - return true; - } + bitspersample = buf[34]; + channels = buf[22]; - for ( i = 0; i < comment_count; i++ ) { - int name_length = 0; + if ((bitspersample != 16) || (channels != 2)) + { + logf("Unsupported WAV - %d bps, %d channels\n", + bitspersample, channels); + return false; + } - if (bytes_remaining < 4) { - break; + bytespersample = ((bitspersample / 8) * channels); + numbytes = get_long(&buf[40]); + totalsamples = numbytes / bytespersample; + + track->id3.vbr = false; /* All WAV files are CBR */ + track->id3.filesize = filesize(fd); + track->id3.frequency = get_long(&buf[24]); + + /* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */ + track->id3.length = (totalsamples / track->id3.frequency) * 1000; + track->id3.bitrate = (track->id3.frequency * bytespersample) / (1000 / 8); + break; + + case AFMT_WAVPACK: + /* A simple parser to read basic information from a WavPack file. + * This will fail on WavPack files that don't have the WavPack header + * as the first thing (i.e. self-extracting WavPack files) or WavPack + * files that have so much extra RIFF data stored in the first block + * that they don't have samples (very rare, I would think). + */ + + /* Use the trackname part of the id3 structure as a temporary buffer */ + buf = track->id3.path; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 32) < 32)) + { + return false; } - bytes_remaining -= 4; - if (read(fd, &comment_length, 4) < 4) { + if (memcmp (buf, "wvpk", 4) != 0 || buf [9] != 4 || buf [8] < 2) + { + logf ("%s is not a WavPack file\n", trackname); return false; } - little_endian_to_native(&comment_length, "L"); + track->id3.vbr = true; /* All WavPack files are VBR */ + track->id3.filesize = filesize (fd); - /* Quit if we've passed the end of the page */ - if ( bytes_remaining < (unsigned)comment_length ) { - break; + 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(&buf[12]); + track->id3.length = totalsamples / (track->id3.frequency / 100) * 10; + track->id3.bitrate = filesize (fd) / (track->id3.length / 8); } - bytes_remaining -= (comment_length); - /* Skip comment if it won't fit in buffer */ - if ( (unsigned int)comment_length >= sizeof(temp) ) { - lseek(fd, comment_length, SEEK_CUR); - continue; + 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 = track->id3.path; + + if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 5) < 5)) + { + return false; } - if ( read(fd, temp, comment_length) < comment_length ) { + if ((buf[0] != 0x0b) || (buf[1] != 0x77)) + { + logf("%s is not an A52/AC3 file\n",trackname); return false; } - temp[comment_length] = '\0'; - UTF8ToAnsi(temp); - comment_length = strlen(temp); - - if (strncasecmp(temp, "TITLE=", 6) == 0) { - name_length = 5; - p = &(entry->title); - } else if (strncasecmp(temp, "ALBUM=", 6) == 0) { - name_length = 5; - p = &(entry->album); - } else if (strncasecmp(temp, "ARTIST=", 7) == 0) { - name_length = 6; - p = &(entry->artist); - } else if (strncasecmp(temp, "COMPOSER=", 9) == 0) { - name_length = 8; - p = &(entry->composer); - } else if (strncasecmp(temp, "GENRE=", 6) == 0) { - name_length = 5; - p = &(entry->genre_string); - } else if (strncasecmp(temp, "DATE=", 5) == 0) { - name_length = 4; - p = &(entry->year_string); - } else if (strncasecmp(temp, "TRACKNUMBER=", 12) == 0) { - name_length = 11; - p = &(entry->track_string); - } else { - int value_length = parse_replaygain(temp, NULL, entry, buffer, - buffer_remaining); - buffer_remaining -= value_length; - buffer += value_length; - p = NULL; - } - - if (p) { - comment_length -= (name_length + 1); - if ( comment_length < buffer_remaining ) { - strncpy(buffer, temp + name_length + 1, comment_length); - buffer[comment_length] = '\0'; - *p = buffer; - buffer += comment_length + 1; - buffer_remaining -= comment_length + 1; - } + 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); - /* Skip to the end of the block */ - if (bytes_remaining) { - lseek(fd, bytes_remaining, SEEK_CUR); + switch (buf[4] & 0xc0) + { + case 0x00: + track->id3.frequency = 48000; + bytesperframe=track->id3.bitrate * 2 * 2; + break; + + case 0x40: + track->id3.frequency = 44100; + bytesperframe = a52_441framesizes[i]; + break; + + case 0x80: + track->id3.frequency = 32000; + 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->filesize / bytesperframe) * 6 * 256; + track->id3.length = (totalsamples / track->id3.frequency) * 1000; + break; + + /* If we don't know how to read the metadata, just store the filename */ + default: + break; } + lseek(fd, 0, SEEK_SET); + strncpy(track->id3.path, trackname, sizeof(track->id3.path)); + track->taginfo_ready = true; + return true; } |