summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2002-04-23 08:43:04 +0000
committerDaniel Stenberg <daniel@haxx.se>2002-04-23 08:43:04 +0000
commit6755f82a7002b98fc522dab216ab32bb62c289c2 (patch)
treed012a0f33434463d52cef740d41d687c64f5818c
parente7cc45929aec0f8c2b8300cebfea3b2ac9a51c29 (diff)
id3 tag reading code, both v1 and v2. Still needs to be adjusted more to
remove the malloc()s and possible other stuff. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@185 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--firmware/id3.c549
1 files changed, 549 insertions, 0 deletions
diff --git a/firmware/id3.c b/firmware/id3.c
new file mode 100644
index 0000000000..825a54a718
--- /dev/null
+++ b/firmware/id3.c
@@ -0,0 +1,549 @@
+/***************************************************************************
+ * __________ __ ___.
+ * Open \______ \ ____ ____ | | _\_ |__ _______ ___
+ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
+ * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
+ * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
+ * \/ \/ \/ \/ \/
+ * $Id$
+ *
+ * Copyright (C) 2002 by Daniel Stenberg
+ *
+ * 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.
+ *
+ ****************************************************************************/
+
+/*
+ * Parts of this code has been stolen from the Ample project and was written
+ * by David Härdeman.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+struct mp3entry {
+ char *path;
+ char *title;
+ char *artist;
+ char *album;
+ int bitrate;
+ int frequency;
+ int id3v2len;
+ int id3v1len;
+ int filesize; /* in bytes */
+ int length; /* song length */
+};
+
+typedef struct mp3entry mp3entry;
+
+typedef unsigned char bool;
+#define TRUE 1
+#define FALSE 0
+
+/* Some utility macros used in getsonglength() */
+#define CHECKSYNC(x) (((x >> 21) & 0x07FF) == 0x7FF)
+#define BYTE0(x) ((x >> 24) & 0xFF)
+#define BYTE1(x) ((x >> 16) & 0xFF)
+#define BYTE2(x) ((x >> 8) & 0xFF)
+#define BYTE3(x) ((x >> 0) & 0xFF)
+
+#define UNSYNC(b1,b2,b3,b4) (((b1 & 0x7F) << (3*7)) + \
+ ((b2 & 0x7F) << (2*7)) + \
+ ((b3 & 0x7F) << (1*7)) + \
+ ((b4 & 0x7F) << (0*7)))
+
+#define HASID3V2(entry) entry->id3v2len > 0
+#define HASID3V1(entry) entry->id3v1len > 0
+
+/* Table of bitrates for MP3 files, all values in kilo.
+ * Indexed by version, layer and value of bit 15-12 in header.
+ */
+const int bitrate_table[2][3][16] =
+{
+ {
+ {0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,0},
+ {0,32,48,56, 64,80, 96, 112,128,160,192,224,256,320,384,0},
+ {0,32,40,48, 56,64, 80, 96, 112,128,160,192,224,256,320,0}
+ },
+ {
+ {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,0},
+ {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0},
+ {0, 8,16,24,32,40,48, 56, 64, 80, 96,112,128,144,160,0}
+ }
+};
+
+/* Table of samples per frame for MP3 files.
+ * Indexed by layer. Multiplied with 1000.
+ */
+const int bs[4] = {0, 384000, 1152000, 1152000};
+
+/* Table of sample frequency for MP3 files.
+ * Indexed by version and layer.
+ */
+const int freqtab[2][4] =
+{
+ {44100, 48000, 32000, 0},
+ {22050, 24000, 16000, 0},
+};
+
+/*
+ * Removes trailing spaces from a string.
+ *
+ * Arguments: buffer - the string to process
+ *
+ * Returns: void
+ */
+static void
+stripspaces(char *buffer)
+{
+ int i = 0;
+ while(*(buffer + i) != '\0')
+ i++;
+
+ for(;i >= 0; i--) {
+ if(*(buffer + i) == ' ')
+ *(buffer + i) = '\0';
+ else if(*(buffer + i) == '\0')
+ continue;
+ else
+ break;
+ }
+}
+
+/*
+ * Sets the title of an MP3 entry based on its ID3v1 tag.
+ *
+ * Arguments: file - the MP3 file to scen for a ID3v1 tag
+ * entry - the entry to set the title in
+ *
+ * Returns: TRUE if a title was found and created, else FALSE
+ */
+static bool
+setid3v1title(FILE *file, mp3entry *entry)
+{
+ char buffer[31];
+ int offsets[3] = {-95,-65,-125};
+ int i;
+ char *result;
+
+ for(i=0;i<3;i++) {
+ if(fseek(file, offsets[i], SEEK_END) != 0) {
+ free(result);
+ return FALSE;
+ }
+
+ buffer[0]=0;
+ fgets(buffer, 31, file);
+ stripspaces(buffer);
+
+ if(buffer[0]) {
+ switch(i) {
+ case 0:
+ entry->artist = strdup(buffer);
+ break;
+ case 1:
+ entry->album = strdup(buffer);
+ break;
+ case 2:
+ entry->title = strdup(buffer);
+ break;
+ }
+ }
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Sets the title of an MP3 entry based on its ID3v2 tag.
+ *
+ * Arguments: file - the MP3 file to scen for a ID3v2 tag
+ * entry - the entry to set the title in
+ *
+ * Returns: TRUE if a title was found and created, else FALSE
+ */
+static void
+setid3v2title(FILE *file, mp3entry *entry)
+{
+ char *buffer;
+ int minframesize;
+ int size, readsize = 0, headerlen;
+ char *title = NULL;
+ char *artist = NULL;
+ char *album = NULL;
+ char header[10];
+ unsigned short int version;
+
+ /* 10 = headerlength */
+ if(entry->id3v2len < 10)
+ return;
+
+ /* Check version */
+ fseek(file, 0, SEEK_SET);
+ fread(header, sizeof(char), 10, file);
+ version = (unsigned short int)header[3];
+
+ /* Read all frames in the tag */
+ size = entry->id3v2len - 10;
+ buffer = malloc(size + 1);
+ if(size != (int)fread(buffer, sizeof(char), size, file)) {
+ free(buffer);
+ return;
+ }
+ *(buffer + size) = '\0';
+
+ /* Set minimun frame size according to ID3v2 version */
+ if(version > 2)
+ minframesize = 12;
+ else
+ minframesize = 8;
+
+ /*
+ * We must have at least minframesize bytes left for the
+ * remaining frames to be interesting
+ */
+ while(size - readsize > minframesize) {
+
+ /* Read frame header and check length */
+ if(version > 2) {
+ memcpy(header, (buffer + readsize), 10);
+ readsize += 10;
+ headerlen = UNSYNC(header[4], header[5],
+ header[6], header[7]);
+ } else {
+ memcpy(header, (buffer + readsize), 6);
+ readsize += 6;
+ headerlen = (header[3] << 16) +
+ (header[4] << 8) +
+ (header[5]);
+ }
+ if(headerlen < 1)
+ continue;
+
+ /* Check for certain frame headers */
+ if(!strncmp(header, "TPE1", strlen("TPE1")) ||
+ !strncmp(header, "TP1", strlen("TP1"))) {
+ readsize++;
+ headerlen--;
+ if(headerlen > (size - readsize))
+ headerlen = (size - readsize);
+ artist = malloc(headerlen + 1);
+ snprintf(artist, headerlen + 1, "%s",
+ (buffer + readsize));
+ readsize += headerlen;
+ }
+ else if(!strncmp(header, "TIT2", strlen("TIT2")) ||
+ !strncmp(header, "TT2", strlen("TT2"))) {
+ readsize++;
+ headerlen--;
+ if(headerlen > (size - readsize))
+ headerlen = (size - readsize);
+ title = malloc(headerlen + 1);
+ snprintf(title, headerlen + 1, "%s",
+ (buffer + readsize));
+ readsize += headerlen;
+ }
+ else if(!strncmp(header, "TALB", strlen("TALB"))) {
+ readsize++;
+ headerlen--;
+ if(headerlen > (size - readsize))
+ headerlen = (size - readsize);
+ album = malloc(headerlen + 1);
+ snprintf(album, headerlen + 1, "%s",
+ (buffer + readsize));
+ readsize += headerlen;
+ }
+ }
+
+ if(artist)
+ entry->artist = artist;
+
+ if(title)
+ entry->title = title;
+
+ if(album)
+ entry->album = album;
+
+ free(buffer);
+}
+
+/*
+ * Calculates the size of the ID3v2 tag.
+ *
+ * Arguments: file - the file to search for a tag.
+ *
+ * Returns: the size of the tag or 0 if none was found
+ */
+static int
+getid3v2len(FILE *file)
+{
+ char buf[6];
+ int offset;
+
+ /* Make sure file has a ID3 tag */
+ if((fseek(file, 0, SEEK_SET) != 0) ||
+ (fread(buf, sizeof(char), 6, file) != 6) ||
+ (strncmp(buf, "ID3", strlen("ID3")) != 0))
+ offset = 0;
+ /* Now check what the ID3v2 size field says */
+ else if(fread(buf, sizeof(char), 4, file) != 4)
+ offset = 0;
+ else
+ offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
+
+ return offset;
+}
+
+static int
+getfilesize(FILE *file)
+{
+ /* seek to the end of it */
+ if(fseek(file, 0, SEEK_END))
+ return 0; /* unknown */
+
+ return ftell(file);
+}
+
+/*
+ * Calculates the size of the ID3v1 tag.
+ *
+ * Arguments: file - the file to search for a tag.
+ *
+ * Returns: the size of the tag or 0 if none was found
+ */
+static int
+getid3v1len(FILE *file)
+{
+ char buf[3];
+ int offset;
+
+ /* Check if we find "TAG" 128 bytes from EOF */
+ if((fseek(file, -128, SEEK_END) != 0) ||
+ (fread(buf, sizeof(char), 3, file) != 3) ||
+ (strncmp(buf, "TAG", 3) != 0))
+ offset = 0;
+ else
+ offset = 128;
+
+ return offset;
+}
+
+/*
+ * Calculates the length (in milliseconds) of an MP3 file. Currently this code
+ * doesn't care about VBR (Variable BitRate) files since it would have to scan
+ * through the entire file but this should become a config option in the
+ * future.
+ *
+ * Modified to only use integers.
+ *
+ * Arguments: file - the file to calculate the length upon
+ * entry - the entry to update with the length
+ *
+ * Returns: the song length in milliseconds,
+ * -1 means that it couldn't be calculated
+ */
+static int
+getsonglength(FILE *file, mp3entry *entry)
+{
+ long header;
+ int version;
+ int layer;
+ int bitindex;
+ int bitrate;
+ int freqindex;
+ int frequency;
+
+ long bpf;
+ long tpf;
+ int i;
+
+ /* Start searching after ID3v2 header */
+ if(fseek(file, entry->id3v2len, SEEK_SET))
+ return -1;
+
+ /* Fill up header with first 24 bits */
+ for(version = 0; version < 3; version++) {
+ header <<= 8;
+ if(!fread(&header, 1, 1, file))
+ return -1;
+ }
+
+ /* Loop trough file until we find a frame header */
+ restart:
+ do {
+ header <<= 8;
+ if(!fread(&header, 1, 1, file))
+ return -1;
+ } while(!CHECKSYNC(header));
+
+ /*
+ * Some files are filled with garbage in the beginning,
+ * if the bitrate index of the header is binary 1111
+ * that is a good is a good indicator
+ */
+ if((header & 0xF000) == 0xF000)
+ goto restart;
+
+#ifdef DEBUG_STANDALONE
+ fprintf(stderr,
+ "We found %x-%x-%x-%x and checksync %i and test %x\n",
+ BYTE0(header), BYTE1(header), BYTE2(header), BYTE3(header),
+ CHECKSYNC(header), (header & 0xF000) == 0xF000);
+#endif
+ /* MPEG Audio Version */
+ switch((header & 0x180000) >> 19) {
+ case 2:
+ version = 2;
+ break;
+ case 3:
+ version = 1;
+ break;
+ default:
+ return -1;
+ }
+
+ /* Layer */
+ switch((header & 0x060000) >> 17) {
+ case 1:
+ layer = 3;
+ break;
+ case 2:
+ layer = 2;
+ break;
+ case 3:
+ layer = 1;
+ break;
+ default:
+ return -1;
+ }
+
+ /* Bitrate */
+ bitindex = (header & 0xF000) >> 12;
+ bitrate = bitrate_table[version-1][layer-1][bitindex];
+ if(bitrate == 0)
+ return -1;
+
+ /* Sampling frequency */
+ freqindex = (header & 0x0C00) >> 10;
+ frequency = freqtab[version-1][freqindex];
+ if(frequency == 0)
+ return -1;
+
+#ifdef DEBUG_STANDALONE
+ fprintf(stderr,
+ "Version %i, lay %i, biti %i, bitr %i, freqi %i, freq %i\n",
+ version, layer, bitindex, bitrate, freqindex, frequency);
+#endif
+ entry->bitrate = bitrate;
+ entry->frequency = frequency;
+
+ /* Calculate bytes per frame, calculation depends on layer */
+ switch(layer) {
+ case 1:
+ bpf = bitrate_table[version - 1][layer - 1][bitindex];
+ bpf *= 12000.0 * 4.0;
+ bpf /= freqtab[version-1][freqindex] << (version - 1);
+ break;
+ case 2:
+ case 3:
+ bpf = bitrate_table[version - 1][layer - 1][bitindex];
+ bpf *= 144000;
+ bpf /= freqtab[version-1][freqindex] << (version - 1);
+ break;
+ default:
+ bpf = 1.0;
+ }
+
+ /* Calculate time per frame */
+ tpf = bs[layer] / freqtab[version-1][freqindex] << (version - 1);
+
+ /*
+ * Now song length is
+ * ((filesize)/(bytes per frame))*(time per frame)
+ */
+ return entry->filesize*tpf/bpf;
+}
+
+
+/*
+ * Checks all relevant information (such as ID3v1 tag, ID3v2 tag, length etc)
+ * about an MP3 file and updates it's entry accordingly.
+ *
+ * Arguments: entry - the entry to check and update with the new information
+ *
+ * Returns: void
+ */
+bool
+mp3info(mp3entry *entry, char *filename)
+{
+ FILE *file;
+ char *copy;
+ char *title;
+
+ if((file = fopen(filename, "r")) == NULL)
+ return TRUE;
+
+ memset(entry, 0, sizeof(mp3entry));
+
+ entry->path = filename;
+
+ entry->filesize = getfilesize(file);
+ entry->id3v2len = getid3v2len(file);
+ entry->id3v1len = getid3v1len(file);
+ entry->length = getsonglength(file, entry);
+ entry->title = NULL;
+
+ if(HASID3V2(entry))
+ setid3v2title(file, entry);
+
+ if(HASID3V1(entry) && !entry->title)
+ setid3v1title(file, entry);
+
+ fclose(file);
+
+ return FALSE;
+}
+
+#ifdef DEBUG_STANDALONE
+
+int main(int argc, char **argv)
+{
+ if(argc > 1) {
+ mp3entry mp3;
+ if(mp3info(&mp3, argv[1])) {
+ printf("Failed\n");
+ return 0;
+ }
+
+ printf("Title: %s\n"
+ "Artist: %s\n"
+ "Album: %s\n"
+ "Length: %.1f secs\n"
+ "Bitrate: %d\n"
+ "Frequency: %d\n",
+ mp3.title?mp3.title:"<blank>",
+ mp3.artist?mp3.artist:"<blank>",
+ mp3.album?mp3.album:"<blank>",
+ mp3.length/1000.0,
+ mp3.bitrate,
+ mp3.frequency);
+ }
+
+ return 0;
+}
+
+#endif
+
+/* -----------------------------------------------------------------
+ * local variables:
+ * eval: (load-file "rockbox-mode.el")
+ * end:
+ */