rockbox/firmware/id3.c
Daniel Stenberg 963a64770e no more malloc()s or strdup()s
the test program now takes any amount of files, which makes it easy to verify
lots of files at once using shell wildcards


git-svn-id: svn://svn.rockbox.org/rockbox/trunk@196 a1c6a512-1295-4272-9138-f99709370657
2002-04-23 11:46:16 +00:00

568 lines
14 KiB
C

/***************************************************************************
* __________ __ ___.
* 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;
static char keepit[3][32];
for(i=0;i<3;i++) {
if(fseek(file, offsets[i], SEEK_END) != 0)
return FALSE;
buffer[0]=0;
fgets(buffer, 31, file);
stripspaces(buffer);
if(buffer[0]) {
switch(i) {
case 0:
strcpy(keepit[0], buffer);
entry->artist = keepit[0];
break;
case 1:
strcpy(keepit[1], buffer);
entry->album = keepit[1];
break;
case 2:
strcpy(keepit[2], buffer);
entry->title = keepit[2];
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)
{
int minframesize;
int size, readsize = 0, headerlen;
char *title = NULL;
char *artist = NULL;
char *album = NULL;
char header[10];
unsigned short int version;
static char buffer[512];
int titlen, artistn, albumn;
/* 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;
if(size >= sizeof(buffer))
size = sizeof(buffer)-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 = buffer + readsize;
artistn = headerlen;
readsize += headerlen;
}
else if(!strncmp(header, "TIT2", strlen("TIT2")) ||
!strncmp(header, "TT2", strlen("TT2"))) {
readsize++;
headerlen--;
if(headerlen > (size - readsize))
headerlen = (size - readsize);
title = buffer + readsize;
titlen = headerlen;
readsize += headerlen;
}
else if(!strncmp(header, "TALB", strlen("TALB"))) {
readsize++;
headerlen--;
if(headerlen > (size - readsize))
headerlen = (size - readsize);
album = buffer + readsize;
albumn = headerlen;
readsize += headerlen;
}
}
if(artist) {
entry->artist = artist;
artist[artistn]=0;
}
if(title) {
entry->title = title;
title[titlen]=0;
}
if(album) {
entry->album = album;
album[albumn]=0;
}
}
/*
* 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_VERBOSE
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_VERBOSE
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
char *secs2str(int ms)
{
static char buffer[32];
int secs = ms/1000;
ms %= 1000;
sprintf(buffer, "%d:%02d.%d", secs/60, secs%60, ms/100);
return buffer;
}
int main(int argc, char **argv)
{
int i;
for(i=1; i<argc; i++) {
mp3entry mp3;
if(mp3info(&mp3, argv[i])) {
printf("Failed to get %s\n", argv[i]);
return 0;
}
printf("****** File: %s\n"
" Title: %s\n"
" Artist: %s\n"
" Album: %s\n"
" Length: %s\n"
" Bitrate: %d\n"
" Frequency: %d\n",
argv[i],
mp3.title?mp3.title:"<blank>",
mp3.artist?mp3.artist:"<blank>",
mp3.album?mp3.album:"<blank>",
secs2str(mp3.length),
mp3.bitrate,
mp3.frequency);
}
return 0;
}
#endif
/* -----------------------------------------------------------------
* local variables:
* eval: (load-file "rockbox-mode.el")
* end:
*/