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
This commit is contained in:
parent
e7cc45929a
commit
6755f82a70
1 changed files with 549 additions and 0 deletions
549
firmware/id3.c
Normal file
549
firmware/id3.c
Normal file
|
@ -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:
|
||||
*/
|
Loading…
Reference in a new issue