2002-04-23 08:43:04 +00:00
|
|
|
|
/***************************************************************************
|
|
|
|
|
* __________ __ ___.
|
|
|
|
|
* 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
|
2002-08-19 14:15:33 +00:00
|
|
|
|
* by David H<EFBFBD>rdeman. It has since been extended and enhanced pretty much by
|
|
|
|
|
* all sorts of friendly Rockbox people.
|
|
|
|
|
*
|
2002-04-23 08:43:04 +00:00
|
|
|
|
*/
|
2003-06-04 15:09:35 +00:00
|
|
|
|
|
|
|
|
|
/* tagResolver and associated code copyright 2003 Thomas Paul Diffenbach
|
|
|
|
|
*/
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <errno.h>
|
2002-05-13 12:29:34 +00:00
|
|
|
|
#include <stdbool.h>
|
2003-06-04 15:09:35 +00:00
|
|
|
|
#include <stddef.h>
|
2002-05-08 15:36:18 +00:00
|
|
|
|
#include "file.h"
|
2002-06-26 14:51:09 +00:00
|
|
|
|
#include "debug.h"
|
2002-06-26 21:11:29 +00:00
|
|
|
|
#include "atoi.h"
|
2002-04-30 12:42:37 +00:00
|
|
|
|
|
2002-05-05 10:32:20 +00:00
|
|
|
|
#include "id3.h"
|
2003-03-10 14:55:31 +00:00
|
|
|
|
#include "mp3data.h"
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2002-10-01 13:20:23 +00:00
|
|
|
|
#define UNSYNC(b0,b1,b2,b3) (((b0 & 0x7F) << (3*7)) | \
|
|
|
|
|
((b1 & 0x7F) << (2*7)) | \
|
|
|
|
|
((b2 & 0x7F) << (1*7)) | \
|
|
|
|
|
((b3 & 0x7F) << (0*7)))
|
|
|
|
|
|
|
|
|
|
#define BYTES2INT(b0,b1,b2,b3) (((b0 & 0xFF) << (3*8)) | \
|
|
|
|
|
((b1 & 0xFF) << (2*8)) | \
|
|
|
|
|
((b2 & 0xFF) << (1*8)) | \
|
|
|
|
|
((b3 & 0xFF) << (0*8)))
|
2002-09-30 18:41:59 +00:00
|
|
|
|
|
2003-06-04 15:09:35 +00:00
|
|
|
|
/*
|
|
|
|
|
HOW TO ADD ADDITIONAL ID3 VERSION 2 TAGS
|
|
|
|
|
Code and comments by Thomas Paul Diffenbach
|
|
|
|
|
|
|
|
|
|
To add another ID3v2 Tag, do the following:
|
|
|
|
|
1. add a char* named for the tag to struct mp3entry in id3.h,
|
|
|
|
|
(I (tpd) prefer to use char* rather than ints, even for what seems like
|
|
|
|
|
numerical values, for cases where a number won't do, e.g.,
|
|
|
|
|
YEAR: "circa 1765", "1790/1977" (composed/performed), "28 Feb 1969"
|
|
|
|
|
TRACK: "1/12", "1 of 12", GENRE: "Freeform genre name"
|
|
|
|
|
Text is more flexible, and as the main use of id3 data is to
|
|
|
|
|
display it, converting it to an int just means reconverting to
|
|
|
|
|
display it, at a runtime cost.)
|
|
|
|
|
|
|
|
|
|
2. If any special processing beyond copying the tag value from the Id3
|
|
|
|
|
block to the struct mp3entry is rrequired (such as converting to an
|
|
|
|
|
int), write a function to perform this special processing.
|
|
|
|
|
|
|
|
|
|
This function's prototype must match that of
|
|
|
|
|
typedef tagPostProcessFunc, that is it must be:
|
|
|
|
|
int func( struct mp3entry*, char* tag, int bufferpos )
|
|
|
|
|
the first argument is a pointer to the current mp3entry structure the
|
|
|
|
|
second argument is a pointer to the null terminated string value of the
|
|
|
|
|
tag found the third argument is the offset of the next free byte in the
|
|
|
|
|
mp3entry's buffer your function should return the corrected offset; if
|
|
|
|
|
you don't lengthen or shorten the tag string, you can return the third
|
|
|
|
|
argument unchanged.
|
|
|
|
|
|
|
|
|
|
Unless you have a good reason no to, make the function static.
|
|
|
|
|
TO JUST COPY THE TAG NO SPECIAL PROCESSING FUNCTION IS NEEDED.
|
|
|
|
|
|
|
|
|
|
3. add one or more entries to the tagList array, using the format:
|
|
|
|
|
char* ID3 Tag symbolic name -- see the ID3 specification for these,
|
|
|
|
|
sizeof() that name minus 1,
|
|
|
|
|
offsetof( struct mp3entry, variable_name_in_struct_mp3entry ),
|
|
|
|
|
pointer to your special processing function or NULL
|
|
|
|
|
if you need no special processing
|
|
|
|
|
Many ID3 symbolic names come in more than one form. You can add both
|
|
|
|
|
forms, each referencing the same variable in struct mp3entry.
|
|
|
|
|
If both forms are present, the last found will be used.
|
|
|
|
|
|
|
|
|
|
4. Alternately, use the TAG_LIST_ENTRY macro with
|
|
|
|
|
ID3 tag symbolic name,
|
|
|
|
|
variable in struct mp3entry,
|
|
|
|
|
special processing function address
|
|
|
|
|
|
|
|
|
|
5. Add code to wps-display.c function get_tag to assign a printf-like
|
|
|
|
|
format specifier for the tag */
|
|
|
|
|
|
|
|
|
|
/* Structure for ID3 Tag extraction information */
|
|
|
|
|
struct tag_resolver {
|
|
|
|
|
const char* tag;
|
|
|
|
|
int tag_length;
|
|
|
|
|
size_t offset;
|
|
|
|
|
int (*ppFunc)(struct mp3entry*, char* tag, int bufferpos);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
/* parse numeric value from string */
|
|
|
|
|
static int parsenum( struct mp3entry* entry, char* tag, int bufferpos )
|
|
|
|
|
{
|
|
|
|
|
entry->tracknum = atoi( tag );
|
|
|
|
|
return bufferpos;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* parse numeric genre from string */
|
|
|
|
|
static int parsegenre( struct mp3entry* entry, char* tag, int bufferpos )
|
|
|
|
|
{
|
|
|
|
|
if( tag[ 1 ] == '(' && tag[ 2 ] != '(' ) {
|
|
|
|
|
entry->genre = atoi( tag + 2 );
|
|
|
|
|
entry->genre_string = 0;
|
|
|
|
|
return tag - entry->id3v2buf;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
entry->genre = 0xFF;
|
|
|
|
|
return bufferpos;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct tag_resolver taglist[] = {
|
|
|
|
|
{ "TPE1", 4, offsetof(struct mp3entry, artist), NULL },
|
|
|
|
|
{ "TP1", 3, offsetof(struct mp3entry, artist), NULL },
|
|
|
|
|
{ "TIT2", 4, offsetof(struct mp3entry, title), NULL },
|
|
|
|
|
{ "TT2", 3, offsetof(struct mp3entry, title), NULL },
|
|
|
|
|
{ "TALB", 4, offsetof(struct mp3entry, album), NULL },
|
|
|
|
|
{ "TRCK", 4, offsetof(struct mp3entry, track_string), &parsenum },
|
|
|
|
|
{ "TYER", 4, offsetof(struct mp3entry, year_string), &parsenum },
|
|
|
|
|
{ "TYR", 3, offsetof(struct mp3entry, year_string), &parsenum },
|
|
|
|
|
{ "TCON", 4, offsetof(struct mp3entry, genre_string), &parsegenre },
|
2003-06-05 08:01:38 +00:00
|
|
|
|
{ "TCOM", 4, offsetof(struct mp3entry, composer), NULL }
|
2003-06-04 15:09:35 +00:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define TAGLIST_SIZE ((int)(sizeof(taglist) / sizeof(taglist[0])))
|
|
|
|
|
|
2002-09-30 18:45:50 +00:00
|
|
|
|
/* Checks to see if the passed in string is a 16-bit wide Unicode v2
|
|
|
|
|
string. If it is, we attempt to convert it to a 8-bit ASCII string
|
|
|
|
|
(for valid 8-bit ASCII characters). If it's not unicode, we leave
|
|
|
|
|
it alone. At some point we should fully support unicode strings */
|
2002-10-01 13:20:23 +00:00
|
|
|
|
static int unicode_munge(char** string, int *len) {
|
|
|
|
|
int tmp;
|
|
|
|
|
bool le = false;
|
|
|
|
|
int i;
|
|
|
|
|
char *str = *string;
|
|
|
|
|
char *outstr = *string;
|
|
|
|
|
|
|
|
|
|
if(str[0] > 0x03) {
|
|
|
|
|
/* Plain old string */
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Type 0x00 is ordinary ISO 8859-1 */
|
|
|
|
|
if(str[0] == 0x00) {
|
2002-10-01 14:36:37 +00:00
|
|
|
|
(*len)--;
|
|
|
|
|
(*string)++; /* Skip the encoding type byte */
|
2002-10-01 13:20:23 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Unicode with or without BOM */
|
|
|
|
|
if(str[0] == 0x01 || str[0] == 0x02) {
|
|
|
|
|
str++;
|
2002-11-12 22:35:32 +00:00
|
|
|
|
tmp = BYTES2INT(0, 0, str[0], str[1]);
|
2002-10-01 13:20:23 +00:00
|
|
|
|
|
2002-11-12 22:35:32 +00:00
|
|
|
|
/* Now check if there is a BOM (zero-width non-breaking space, 0xfeff)
|
|
|
|
|
and if it is in little or big endian format */
|
|
|
|
|
if(tmp == 0xfffe) { /* Little endian? */
|
2002-10-01 13:20:23 +00:00
|
|
|
|
le = true;
|
|
|
|
|
str += 2;
|
|
|
|
|
}
|
|
|
|
|
|
2002-11-12 22:35:32 +00:00
|
|
|
|
if(tmp == 0xfeff) /* Big endian? */
|
2002-10-01 13:20:23 +00:00
|
|
|
|
str += 2;
|
|
|
|
|
|
|
|
|
|
i = 0;
|
2002-09-30 18:45:50 +00:00
|
|
|
|
|
|
|
|
|
do {
|
2002-10-01 13:20:23 +00:00
|
|
|
|
if(le) {
|
|
|
|
|
if(str[1])
|
|
|
|
|
outstr[i++] = '.';
|
|
|
|
|
else
|
|
|
|
|
outstr[i++] = str[0];
|
|
|
|
|
} else {
|
|
|
|
|
if(str[0])
|
|
|
|
|
outstr[i++] = '.';
|
|
|
|
|
else
|
|
|
|
|
outstr[i++] = str[1];
|
2002-09-30 18:45:50 +00:00
|
|
|
|
}
|
2002-11-12 22:35:32 +00:00
|
|
|
|
str += 2;
|
2002-10-01 13:20:23 +00:00
|
|
|
|
} while(str[0] || str[1]);
|
|
|
|
|
|
|
|
|
|
*len = i;
|
|
|
|
|
return 0;
|
2002-09-30 18:45:50 +00:00
|
|
|
|
}
|
2002-10-01 13:20:23 +00:00
|
|
|
|
|
|
|
|
|
/* If we come here, the string was of an unsupported type */
|
|
|
|
|
*len = 1;
|
|
|
|
|
outstr[0] = 0;
|
|
|
|
|
return -1;
|
2002-09-30 18:45:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
*
|
2002-05-13 12:29:34 +00:00
|
|
|
|
* Returns: true if a title was found and created, else false
|
2002-04-23 08:43:04 +00:00
|
|
|
|
*/
|
2002-07-04 22:10:43 +00:00
|
|
|
|
static bool setid3v1title(int fd, struct mp3entry *entry)
|
2002-04-23 08:43:04 +00:00
|
|
|
|
{
|
2002-12-05 14:55:14 +00:00
|
|
|
|
unsigned char buffer[128];
|
2002-12-05 15:21:18 +00:00
|
|
|
|
static char offsets[] = {3, 33, 63, 93, 125, 127};
|
2002-12-05 14:55:14 +00:00
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
|
|
if (-1 == lseek(fd, -128, SEEK_END))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (read(fd, buffer, sizeof buffer) != sizeof buffer)
|
|
|
|
|
return false;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2002-12-05 14:55:14 +00:00
|
|
|
|
if (strncmp(buffer, "TAG", 3))
|
|
|
|
|
return false;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
entry->id3v1len = 128;
|
2002-12-09 15:01:37 +00:00
|
|
|
|
entry->id3version = ID3_VER_1_0;
|
|
|
|
|
|
2002-12-05 15:21:18 +00:00
|
|
|
|
for (i=0; i < (int)sizeof offsets; i++) {
|
2002-12-05 14:55:14 +00:00
|
|
|
|
char* ptr = buffer + offsets[i];
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2002-12-05 14:55:14 +00:00
|
|
|
|
if (i<3) {
|
|
|
|
|
/* kill trailing space in strings */
|
|
|
|
|
for (j=29; j && ptr[j]==' '; j--)
|
|
|
|
|
ptr[j] = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch(i) {
|
|
|
|
|
case 0:
|
2002-12-09 15:01:37 +00:00
|
|
|
|
strncpy(entry->id3v1buf[2], ptr, 30);
|
2002-12-05 14:55:14 +00:00
|
|
|
|
entry->title = entry->id3v1buf[2];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 1:
|
2002-12-09 15:01:37 +00:00
|
|
|
|
strncpy(entry->id3v1buf[0], ptr, 30);
|
2002-12-05 14:55:14 +00:00
|
|
|
|
entry->artist = entry->id3v1buf[0];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 2:
|
2002-12-09 15:01:37 +00:00
|
|
|
|
strncpy(entry->id3v1buf[1], ptr, 30);
|
2002-12-05 14:55:14 +00:00
|
|
|
|
entry->album = entry->id3v1buf[1];
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
|
|
|
|
ptr[4] = 0;
|
|
|
|
|
entry->year = atoi(ptr);
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 4:
|
|
|
|
|
/* id3v1.1 uses last two bytes of comment field for track
|
|
|
|
|
number: first must be 0 and second is track num */
|
2002-12-09 15:01:37 +00:00
|
|
|
|
if (!ptr[0] && ptr[1]) {
|
2002-12-05 14:55:14 +00:00
|
|
|
|
entry->tracknum = ptr[1];
|
2002-12-09 15:01:37 +00:00
|
|
|
|
entry->id3version = ID3_VER_1_1;
|
|
|
|
|
}
|
2002-12-05 14:55:14 +00:00
|
|
|
|
break;
|
2002-12-05 15:21:18 +00:00
|
|
|
|
|
|
|
|
|
case 5:
|
|
|
|
|
/* genre */
|
|
|
|
|
entry->genre = ptr[0];
|
|
|
|
|
break;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-05-13 12:29:34 +00:00
|
|
|
|
return true;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Sets the title of an MP3 entry based on its ID3v2 tag.
|
|
|
|
|
*
|
2002-10-29 10:29:57 +00:00
|
|
|
|
* Arguments: file - the MP3 file to scan for a ID3v2 tag
|
2002-04-23 08:43:04 +00:00
|
|
|
|
* entry - the entry to set the title in
|
|
|
|
|
*
|
2002-05-13 12:29:34 +00:00
|
|
|
|
* Returns: true if a title was found and created, else false
|
2002-04-23 08:43:04 +00:00
|
|
|
|
*/
|
2002-07-04 22:10:43 +00:00
|
|
|
|
static void setid3v2title(int fd, struct mp3entry *entry)
|
2002-04-23 08:43:04 +00:00
|
|
|
|
{
|
2002-08-30 22:01:35 +00:00
|
|
|
|
int minframesize;
|
2002-04-30 12:42:37 +00:00
|
|
|
|
int size;
|
2002-10-28 23:00:57 +00:00
|
|
|
|
int bufferpos = 0, totframelen, framelen;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
char header[10];
|
2002-12-09 15:01:37 +00:00
|
|
|
|
unsigned char version;
|
2002-06-04 22:05:00 +00:00
|
|
|
|
char *buffer = entry->id3v2buf;
|
2002-10-28 23:00:57 +00:00
|
|
|
|
int bytesread = 0;
|
|
|
|
|
int buffersize = sizeof(entry->id3v2buf);
|
2003-03-10 14:55:31 +00:00
|
|
|
|
int flags;
|
|
|
|
|
int skip;
|
2003-06-04 15:09:35 +00:00
|
|
|
|
int i;
|
|
|
|
|
|
2002-10-28 23:00:57 +00:00
|
|
|
|
/* Bail out if the tag is shorter than 10 bytes */
|
2002-04-23 08:43:04 +00:00
|
|
|
|
if(entry->id3v2len < 10)
|
|
|
|
|
return;
|
|
|
|
|
|
2002-10-28 23:00:57 +00:00
|
|
|
|
/* Read the ID3 tag version from the header */
|
2002-04-30 12:42:37 +00:00
|
|
|
|
lseek(fd, 0, SEEK_SET);
|
2002-05-08 07:10:16 +00:00
|
|
|
|
if(10 != read(fd, header, 10))
|
|
|
|
|
return;
|
|
|
|
|
|
2002-10-28 23:00:57 +00:00
|
|
|
|
/* Get the total ID3 tag size */
|
2002-04-23 08:43:04 +00:00
|
|
|
|
size = entry->id3v2len - 10;
|
2002-04-23 11:46:16 +00:00
|
|
|
|
|
2002-12-09 15:01:37 +00:00
|
|
|
|
version = header[3];
|
|
|
|
|
switch ( version ) {
|
|
|
|
|
case 2:
|
2002-12-12 15:20:37 +00:00
|
|
|
|
version = ID3_VER_2_2;
|
2002-12-09 15:01:37 +00:00
|
|
|
|
minframesize = 8;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 3:
|
2002-12-12 15:20:37 +00:00
|
|
|
|
version = ID3_VER_2_3;
|
2002-12-09 15:01:37 +00:00
|
|
|
|
minframesize = 12;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case 4:
|
2002-12-12 15:20:37 +00:00
|
|
|
|
version = ID3_VER_2_4;
|
2002-12-09 15:01:37 +00:00
|
|
|
|
minframesize = 12;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
/* unsupported id3 version */
|
|
|
|
|
return;
|
|
|
|
|
}
|
2002-12-12 15:20:37 +00:00
|
|
|
|
entry->id3version = version;
|
2003-03-10 15:43:16 +00:00
|
|
|
|
entry->tracknum = entry->year = entry->genre = 0;
|
|
|
|
|
entry->title = entry->artist = entry->album = NULL;
|
2003-06-04 15:09:35 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
/* Skip the extended header if it is present */
|
|
|
|
|
if(version >= ID3_VER_2_4) {
|
|
|
|
|
if(header[5] & 0x40) {
|
|
|
|
|
if(4 != read(fd, header, 4))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
framelen = UNSYNC(header[0], header[1],
|
|
|
|
|
header[2], header[3]);
|
|
|
|
|
|
|
|
|
|
lseek(fd, framelen - 4, SEEK_CUR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
/*
|
|
|
|
|
* We must have at least minframesize bytes left for the
|
|
|
|
|
* remaining frames to be interesting
|
|
|
|
|
*/
|
2003-06-04 15:09:35 +00:00
|
|
|
|
while(size > minframesize ) {
|
2003-03-10 14:55:31 +00:00
|
|
|
|
flags = 0;
|
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
/* Read frame header and check length */
|
2002-12-12 15:20:37 +00:00
|
|
|
|
if(version >= ID3_VER_2_3) {
|
2002-10-28 23:00:57 +00:00
|
|
|
|
if(10 != read(fd, header, 10))
|
|
|
|
|
return;
|
|
|
|
|
/* Adjust for the 10 bytes we read */
|
|
|
|
|
size -= 10;
|
2003-03-10 14:55:31 +00:00
|
|
|
|
|
|
|
|
|
flags = BYTES2INT(0, 0, header[8], header[9]);
|
2002-10-28 23:00:57 +00:00
|
|
|
|
|
2002-12-12 15:20:37 +00:00
|
|
|
|
if (version >= ID3_VER_2_4) {
|
2002-10-28 23:00:57 +00:00
|
|
|
|
framelen = UNSYNC(header[4], header[5],
|
|
|
|
|
header[6], header[7]);
|
2002-09-30 18:41:59 +00:00
|
|
|
|
} else {
|
|
|
|
|
/* version .3 files don't use synchsafe ints for
|
|
|
|
|
* size */
|
2002-10-28 23:00:57 +00:00
|
|
|
|
framelen = BYTES2INT(header[4], header[5],
|
|
|
|
|
header[6], header[7]);
|
2002-09-30 18:41:59 +00:00
|
|
|
|
}
|
2002-04-23 08:43:04 +00:00
|
|
|
|
} else {
|
2002-10-28 23:00:57 +00:00
|
|
|
|
if(6 != read(fd, header, 6))
|
|
|
|
|
return;
|
|
|
|
|
/* Adjust for the 6 bytes we read */
|
|
|
|
|
size -= 6;
|
|
|
|
|
|
|
|
|
|
framelen = BYTES2INT(0, header[3], header[4], header[5]);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
2002-08-30 22:01:35 +00:00
|
|
|
|
|
2002-10-28 23:00:57 +00:00
|
|
|
|
/* Keep track of the total size */
|
2002-10-29 10:29:57 +00:00
|
|
|
|
totframelen = framelen;
|
2002-10-28 23:00:57 +00:00
|
|
|
|
|
|
|
|
|
if(framelen == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
if(flags)
|
|
|
|
|
{
|
|
|
|
|
skip = 0;
|
|
|
|
|
|
|
|
|
|
if(flags & 0x0040) /* Grouping identity */
|
|
|
|
|
skip++;
|
|
|
|
|
|
2003-03-10 15:32:59 +00:00
|
|
|
|
if(flags & 0x000c) /* Compression or encryption */
|
2003-03-10 14:55:31 +00:00
|
|
|
|
{
|
|
|
|
|
/* Skip it using the total size in case
|
|
|
|
|
it was truncated */
|
|
|
|
|
size -= totframelen;
|
|
|
|
|
lseek(fd, totframelen, SEEK_CUR);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
2003-03-10 15:32:59 +00:00
|
|
|
|
/* The Unsynchronization flag can't be trusted, so we
|
|
|
|
|
don't check it for now... */
|
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
if(flags & 0x0001) /* Data length indicator */
|
|
|
|
|
skip += 4;
|
|
|
|
|
|
|
|
|
|
if(skip)
|
|
|
|
|
{
|
|
|
|
|
lseek(fd, skip, SEEK_CUR);
|
|
|
|
|
framelen -= skip;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2002-10-28 23:00:57 +00:00
|
|
|
|
/* If the frame is larger than the remaining buffer space we try
|
|
|
|
|
to read as much as would fit in the buffer */
|
|
|
|
|
if(framelen >= buffersize - bufferpos)
|
|
|
|
|
framelen = buffersize - bufferpos - 1;
|
2002-10-29 10:29:57 +00:00
|
|
|
|
|
2002-12-05 14:55:14 +00:00
|
|
|
|
DEBUGF("id3v2 frame: %.4s\n", header);
|
|
|
|
|
|
2003-06-04 15:09:35 +00:00
|
|
|
|
/* Check for certain frame headers
|
2003-03-18 01:31:35 +00:00
|
|
|
|
|
2003-06-04 15:09:35 +00:00
|
|
|
|
'size' is the amount of frame bytes remaining. We decrement it by
|
|
|
|
|
the amount of bytes we read. If we fail to read as many bytes as
|
|
|
|
|
we expect, we assume that we can't read from this file, and bail
|
|
|
|
|
out.
|
|
|
|
|
|
|
|
|
|
For each frame. we will iterate over the list of supported tags,
|
|
|
|
|
and read the tag into entry's buffer. All tags will be kept as
|
|
|
|
|
strings, for cases where a number won't do, e.g., YEAR: "circa
|
|
|
|
|
1765", "1790/1977" (composed/performed), "28 Feb 1969" TRACK:
|
|
|
|
|
"1/12", "1 of 12", GENRE: "Freeform genre name" Text is more
|
|
|
|
|
flexible, and as the main use of id3 data is to display it,
|
|
|
|
|
converting it to an int just means reconverting to display it, at a
|
|
|
|
|
runtime cost.
|
|
|
|
|
|
|
|
|
|
For tags that the current code does convert to ints, a post
|
|
|
|
|
processing function will be called via a pointer to function. */
|
|
|
|
|
|
|
|
|
|
for (i=0; i<TAGLIST_SIZE; i++) {
|
|
|
|
|
struct tag_resolver* tr = &taglist[i];
|
|
|
|
|
char** ptag = (char**) (((char*)entry) + tr->offset);
|
|
|
|
|
char* tag;
|
2003-03-18 01:31:35 +00:00
|
|
|
|
|
2003-06-04 15:09:35 +00:00
|
|
|
|
if( !*ptag && !memcmp( header, tr->tag, tr->tag_length ) ) {
|
|
|
|
|
|
|
|
|
|
/* found a tag matching one in tagList, and not yet filled */
|
|
|
|
|
bytesread = read(fd, buffer + bufferpos, framelen);
|
|
|
|
|
if( bytesread != framelen )
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
size -= bytesread;
|
|
|
|
|
*ptag = buffer + bufferpos;
|
|
|
|
|
unicode_munge( ptag, &bytesread );
|
|
|
|
|
tag = *ptag;
|
|
|
|
|
tag[bytesread + 1] = 0;
|
|
|
|
|
bufferpos += bytesread + 2;
|
|
|
|
|
if( tr->ppFunc )
|
|
|
|
|
bufferpos = tr->ppFunc(entry, tag, bufferpos);
|
|
|
|
|
break;
|
|
|
|
|
}
|
2002-12-05 14:55:14 +00:00
|
|
|
|
}
|
2003-06-04 15:09:35 +00:00
|
|
|
|
|
|
|
|
|
if( i == TAGLIST_SIZE ) {
|
|
|
|
|
/* no tag in tagList was found, or it was a repeat.
|
|
|
|
|
skip it using the total size */
|
|
|
|
|
|
2002-10-29 10:29:57 +00:00
|
|
|
|
size -= totframelen;
|
2003-06-04 15:09:35 +00:00
|
|
|
|
if( lseek(fd, totframelen, SEEK_CUR) == -1 )
|
|
|
|
|
return;
|
2002-10-29 10:29:57 +00:00
|
|
|
|
}
|
2002-06-26 21:11:29 +00:00
|
|
|
|
}
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2002-07-04 22:10:43 +00:00
|
|
|
|
static int getid3v2len(int fd)
|
2002-04-23 08:43:04 +00:00
|
|
|
|
{
|
|
|
|
|
char buf[6];
|
|
|
|
|
int offset;
|
|
|
|
|
|
|
|
|
|
/* Make sure file has a ID3 tag */
|
2002-04-30 12:42:37 +00:00
|
|
|
|
if((-1 == lseek(fd, 0, SEEK_SET)) ||
|
|
|
|
|
(read(fd, buf, 6) != 6) ||
|
2002-04-23 08:43:04 +00:00
|
|
|
|
(strncmp(buf, "ID3", strlen("ID3")) != 0))
|
|
|
|
|
offset = 0;
|
2002-07-04 22:10:43 +00:00
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
/* Now check what the ID3v2 size field says */
|
|
|
|
|
else
|
2003-06-04 15:09:35 +00:00
|
|
|
|
if(read(fd, buf, 4) != 4)
|
|
|
|
|
offset = 0;
|
|
|
|
|
else
|
|
|
|
|
offset = UNSYNC(buf[0], buf[1], buf[2], buf[3]) + 10;
|
2002-07-04 22:10:43 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
DEBUGF("ID3V2 Length: 0x%x\n", offset);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
return offset;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
2002-08-19 14:15:33 +00:00
|
|
|
|
* Calculates the length (in milliseconds) of an MP3 file.
|
2002-04-23 08:43:04 +00:00
|
|
|
|
*
|
|
|
|
|
* 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,
|
2002-08-22 07:59:31 +00:00
|
|
|
|
* 0 means that it couldn't be calculated
|
2002-04-23 08:43:04 +00:00
|
|
|
|
*/
|
2002-07-04 22:10:43 +00:00
|
|
|
|
static int getsonglength(int fd, struct mp3entry *entry)
|
2002-04-23 08:43:04 +00:00
|
|
|
|
{
|
2002-07-03 21:04:09 +00:00
|
|
|
|
unsigned int filetime = 0;
|
2003-03-10 14:55:31 +00:00
|
|
|
|
struct mp3info info;
|
2002-07-22 16:05:46 +00:00
|
|
|
|
int bytecount;
|
2003-03-10 14:55:31 +00:00
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
/* Start searching after ID3v2 header */
|
2002-04-30 12:42:37 +00:00
|
|
|
|
if(-1 == lseek(fd, entry->id3v2len, SEEK_SET))
|
2002-08-22 07:59:31 +00:00
|
|
|
|
return 0;
|
2002-08-19 14:15:33 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
bytecount = get_mp3file_info(fd, &info);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
DEBUGF("Space between ID3V2 tag and first audio frame: 0x%x bytes\n",
|
|
|
|
|
bytecount);
|
2002-07-18 02:08:23 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
if(bytecount < 0)
|
2002-07-03 21:04:09 +00:00
|
|
|
|
return -1;
|
2003-03-10 14:55:31 +00:00
|
|
|
|
|
|
|
|
|
bytecount += entry->id3v2len;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
entry->bitrate = info.bitrate;
|
2003-03-13 19:12:05 +00:00
|
|
|
|
entry->frequency = info.frequency;
|
|
|
|
|
entry->version = info.version;
|
|
|
|
|
entry->layer = info.layer;
|
2002-07-10 13:17:31 +00:00
|
|
|
|
|
2002-07-03 21:04:09 +00:00
|
|
|
|
/* If the file time hasn't been established, this may be a fixed
|
|
|
|
|
rate MP3, so just use the default formula */
|
2003-03-10 14:55:31 +00:00
|
|
|
|
|
|
|
|
|
filetime = info.file_time;
|
|
|
|
|
|
2002-07-03 21:04:09 +00:00
|
|
|
|
if(filetime == 0)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Now song length is
|
|
|
|
|
* ((filesize)/(bytes per frame))*(time per frame)
|
|
|
|
|
*/
|
2003-03-10 14:55:31 +00:00
|
|
|
|
filetime = entry->filesize/info.frame_size*info.frame_time;
|
2002-07-03 21:04:09 +00:00
|
|
|
|
}
|
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
entry->tpf = info.frame_time;
|
|
|
|
|
entry->bpf = info.frame_size;
|
|
|
|
|
|
|
|
|
|
entry->vbr = info.is_vbr;
|
|
|
|
|
entry->has_toc = info.has_toc;
|
|
|
|
|
memcpy(entry->toc, info.toc, sizeof(info.toc));
|
|
|
|
|
|
2003-03-10 18:25:40 +00:00
|
|
|
|
entry->vbr_header_pos = info.vbr_header_pos;
|
2003-03-10 14:55:31 +00:00
|
|
|
|
|
2002-10-27 23:07:26 +00:00
|
|
|
|
/* Update the seek point for the first playable frame */
|
|
|
|
|
entry->first_frame_offset = bytecount;
|
|
|
|
|
DEBUGF("First frame is at %x\n", entry->first_frame_offset);
|
|
|
|
|
|
2002-07-03 21:04:09 +00:00
|
|
|
|
return filetime;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* 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
|
|
|
|
|
*/
|
2002-07-04 22:10:43 +00:00
|
|
|
|
bool mp3info(struct mp3entry *entry, char *filename)
|
2002-04-23 08:43:04 +00:00
|
|
|
|
{
|
2002-04-30 12:42:37 +00:00
|
|
|
|
int fd;
|
|
|
|
|
fd = open(filename, O_RDONLY);
|
|
|
|
|
if(-1 == fd)
|
2002-05-13 12:29:34 +00:00
|
|
|
|
return true;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2002-06-25 11:39:22 +00:00
|
|
|
|
memset(entry, 0, sizeof(struct mp3entry));
|
2002-06-27 08:33:58 +00:00
|
|
|
|
|
|
|
|
|
strncpy(entry->path, filename, sizeof(entry->path));
|
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
entry->title = NULL;
|
2003-03-18 00:45:27 +00:00
|
|
|
|
entry->filesize = filesize(fd);
|
2002-06-26 14:51:09 +00:00
|
|
|
|
entry->id3v2len = getid3v2len(fd);
|
2002-06-26 21:11:29 +00:00
|
|
|
|
entry->tracknum = 0;
|
2003-02-11 15:00:56 +00:00
|
|
|
|
entry->genre = 0xff;
|
2002-06-26 21:11:29 +00:00
|
|
|
|
|
2002-09-16 08:18:51 +00:00
|
|
|
|
if (entry->id3v2len)
|
2002-04-30 12:42:37 +00:00
|
|
|
|
setid3v2title(fd, entry);
|
2002-06-26 14:51:09 +00:00
|
|
|
|
entry->length = getsonglength(fd, entry);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2003-03-10 14:55:31 +00:00
|
|
|
|
/* Subtract the meta information from the file size to get
|
|
|
|
|
the true size of the MP3 stream */
|
|
|
|
|
entry->filesize -= entry->first_frame_offset;
|
|
|
|
|
|
2002-12-03 15:38:39 +00:00
|
|
|
|
/* only seek to end of file if no id3v2 tags were found */
|
|
|
|
|
if (!entry->id3v2len) {
|
2002-12-05 14:55:14 +00:00
|
|
|
|
if(!entry->title)
|
2002-12-03 15:38:39 +00:00
|
|
|
|
setid3v1title(fd, entry);
|
|
|
|
|
}
|
2002-04-30 12:42:37 +00:00
|
|
|
|
|
|
|
|
|
close(fd);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
|
2002-08-22 07:59:31 +00:00
|
|
|
|
if(!entry->length || (entry->filesize < 8 ))
|
|
|
|
|
/* no song length or less than 8 bytes is hereby considered to be an
|
|
|
|
|
invalid mp3 and won't be played by us! */
|
|
|
|
|
return true;
|
|
|
|
|
|
2002-05-13 12:29:34 +00:00
|
|
|
|
return false;
|
2002-04-23 08:43:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#ifdef DEBUG_STANDALONE
|
|
|
|
|
|
2002-04-23 11:46:16 +00:00
|
|
|
|
char *secs2str(int ms)
|
|
|
|
|
{
|
|
|
|
|
static char buffer[32];
|
|
|
|
|
int secs = ms/1000;
|
|
|
|
|
ms %= 1000;
|
2002-05-02 11:44:15 +00:00
|
|
|
|
snprintf(buffer, sizeof(buffer), "%d:%02d.%d", secs/60, secs%60, ms/100);
|
2002-04-23 11:46:16 +00:00
|
|
|
|
return buffer;
|
|
|
|
|
}
|
|
|
|
|
|
2002-04-23 08:43:04 +00:00
|
|
|
|
int main(int argc, char **argv)
|
|
|
|
|
{
|
2002-04-23 11:46:16 +00:00
|
|
|
|
int i;
|
|
|
|
|
for(i=1; i<argc; i++) {
|
2002-06-25 11:39:22 +00:00
|
|
|
|
struct mp3entry mp3;
|
2003-06-04 15:09:35 +00:00
|
|
|
|
mp3.album = "Bogus";
|
2002-04-23 11:46:16 +00:00
|
|
|
|
if(mp3info(&mp3, argv[i])) {
|
|
|
|
|
printf("Failed to get %s\n", argv[i]);
|
2002-04-23 08:43:04 +00:00
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2002-04-23 11:46:16 +00:00
|
|
|
|
printf("****** File: %s\n"
|
|
|
|
|
" Title: %s\n"
|
|
|
|
|
" Artist: %s\n"
|
|
|
|
|
" Album: %s\n"
|
2003-06-04 15:09:35 +00:00
|
|
|
|
" Genre: %s (%d) \n"
|
|
|
|
|
" Composer: %s\n"
|
|
|
|
|
" Year: %s (%d)\n"
|
|
|
|
|
" Track: %s (%d)\n"
|
2002-04-26 08:56:43 +00:00
|
|
|
|
" Length: %s / %d s\n"
|
2002-04-23 11:46:16 +00:00
|
|
|
|
" Bitrate: %d\n"
|
|
|
|
|
" Frequency: %d\n",
|
|
|
|
|
argv[i],
|
2002-04-23 08:43:04 +00:00
|
|
|
|
mp3.title?mp3.title:"<blank>",
|
|
|
|
|
mp3.artist?mp3.artist:"<blank>",
|
|
|
|
|
mp3.album?mp3.album:"<blank>",
|
2003-06-04 15:09:35 +00:00
|
|
|
|
mp3.genre_string?mp3.genre_string:"<blank>",
|
|
|
|
|
mp3.genre,
|
|
|
|
|
mp3.composer?mp3.composer:"<blank>",
|
|
|
|
|
mp3.year_string?mp3.year_string:"<blank>",
|
|
|
|
|
mp3.year,
|
|
|
|
|
mp3.track_string?mp3.track_string:"<blank>",
|
|
|
|
|
mp3.tracknum,
|
2002-04-23 11:46:16 +00:00
|
|
|
|
secs2str(mp3.length),
|
2002-04-26 08:56:43 +00:00
|
|
|
|
mp3.length/1000,
|
2002-04-23 08:43:04 +00:00
|
|
|
|
mp3.bitrate,
|
|
|
|
|
mp3.frequency);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|