2005-06-14 22:27:57 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
|
|
* \/ \/ \/ \/ \/
|
|
|
|
* $Id$
|
|
|
|
*
|
|
|
|
* Copyright (C) 2005 Dave Chapman
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <ctype.h>
|
2005-09-22 16:58:03 +00:00
|
|
|
#include <inttypes.h>
|
2005-06-14 22:27:57 +00:00
|
|
|
|
|
|
|
#include "metadata.h"
|
|
|
|
#include "mp3_playback.h"
|
|
|
|
#include "logf.h"
|
2005-06-19 20:27:46 +00:00
|
|
|
#include "atoi.h"
|
2005-07-24 15:32:28 +00:00
|
|
|
#include "replaygain.h"
|
|
|
|
#include "debug.h"
|
2005-09-22 16:58:03 +00:00
|
|
|
#include "system.h"
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
#define APETAG_HEADER_LENGTH 32
|
|
|
|
#define APETAG_HEADER_FORMAT "8LLLL"
|
|
|
|
#define APETAG_ITEM_HEADER_FORMAT "LL"
|
|
|
|
#define APETAG_ITEM_TYPE_MASK 3
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
#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];
|
|
|
|
};
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
struct apetag_item_header
|
|
|
|
{
|
|
|
|
long length;
|
|
|
|
long flags;
|
|
|
|
};
|
2005-06-27 00:12:40 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
struct format_list
|
|
|
|
{
|
|
|
|
char format;
|
|
|
|
char extension[5];
|
|
|
|
};
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
static const struct format_list formats[] =
|
|
|
|
{
|
|
|
|
{ AFMT_MPA_L1, "mp1" },
|
|
|
|
{ AFMT_MPA_L2, "mp2" },
|
|
|
|
{ AFMT_MPA_L2, "mpa" },
|
|
|
|
{ AFMT_MPA_L3, "mp3" },
|
2006-03-26 11:33:42 +00:00
|
|
|
#if CONFIG_CODEC == SWCODEC
|
2005-09-22 16:58:03 +00:00
|
|
|
{ AFMT_OGG_VORBIS, "ogg" },
|
|
|
|
{ AFMT_PCM_WAV, "wav" },
|
|
|
|
{ AFMT_FLAC, "flac" },
|
|
|
|
{ AFMT_MPC, "mpc" },
|
|
|
|
{ AFMT_A52, "a52" },
|
|
|
|
{ AFMT_A52, "ac3" },
|
|
|
|
{ AFMT_WAVPACK, "wv" },
|
2005-09-22 21:55:37 +00:00
|
|
|
{ AFMT_ALAC, "m4a" },
|
2005-10-31 20:56:29 +00:00
|
|
|
{ AFMT_AAC, "mp4" },
|
2005-11-11 19:45:36 +00:00
|
|
|
{ AFMT_SHN, "shn" },
|
2006-02-01 16:42:02 +00:00
|
|
|
{ AFMT_AIFF, "aif" },
|
|
|
|
{ AFMT_AIFF, "aiff" },
|
2006-07-18 18:33:12 +00:00
|
|
|
{ AFMT_SID, "sid" },
|
2006-03-26 11:33:42 +00:00
|
|
|
#endif
|
2005-09-22 16:58:03 +00:00
|
|
|
};
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2006-03-26 11:33:42 +00:00
|
|
|
#if CONFIG_CODEC == SWCODEC
|
2005-09-22 16:58:03 +00:00
|
|
|
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
|
|
|
|
};
|
2005-07-05 08:43:36 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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
|
|
|
|
};
|
2005-07-05 08:43:36 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
static const long wavpack_sample_rates [] =
|
|
|
|
{
|
|
|
|
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
|
|
|
|
32000, 44100, 48000, 64000, 88200, 96000, 192000
|
|
|
|
};
|
2005-07-28 18:43:33 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
|
|
|
|
2005-10-06 19:27:43 +00:00
|
|
|
*d = letoh32(*d);
|
2005-09-22 16:58:03 +00:00
|
|
|
data = d + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
case 'S':
|
|
|
|
{
|
|
|
|
short* d = (short*) data;
|
|
|
|
|
2005-10-06 19:27:43 +00:00
|
|
|
*d = letoh16(*d);
|
2005-09-22 16:58:03 +00:00
|
|
|
data = d + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
default:
|
|
|
|
if (isdigit(*format))
|
|
|
|
{
|
|
|
|
data = ((char*) data) + *format - '0';
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
format++;
|
|
|
|
}
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 21:55:37 +00:00
|
|
|
/* read_uint32be() - read an unsigned integer from a big-endian
|
|
|
|
(e.g. Quicktime) file. This is used by the .m4a parser
|
|
|
|
*/
|
|
|
|
#ifdef ROCKBOX_BIG_ENDIAN
|
|
|
|
#define read_uint32be(fd,buf) read((fd),(buf),4)
|
|
|
|
#else
|
|
|
|
int read_uint32be(int fd, unsigned int* buf) {
|
|
|
|
char tmp;
|
|
|
|
char* p=(char*)buf;
|
|
|
|
size_t n;
|
|
|
|
|
2005-09-22 22:47:29 +00:00
|
|
|
n=read(fd,p,4);
|
2005-09-22 21:55:37 +00:00
|
|
|
if (n==4) {
|
|
|
|
tmp=p[0];
|
|
|
|
p[0]=p[3];
|
|
|
|
p[3]=tmp;
|
2005-09-27 01:03:51 +00:00
|
|
|
tmp=p[2];
|
|
|
|
p[2]=p[1];
|
|
|
|
p[1]=tmp;
|
2005-09-22 21:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return(n);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Read an unaligned 32-bit little endian long from buffer. */
|
|
|
|
static unsigned long get_long(void* buf)
|
|
|
|
{
|
|
|
|
unsigned char* p = (unsigned char*) buf;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 21:55:37 +00:00
|
|
|
/* Read a string tag from an M4A file */
|
|
|
|
void read_m4a_tag_string(int fd, int len,char** bufptr,size_t* bytes_remaining, char** dest)
|
|
|
|
{
|
|
|
|
int data_length;
|
|
|
|
|
|
|
|
if (bytes_remaining==0) {
|
|
|
|
lseek(fd,len,SEEK_CUR); /* Skip everything */
|
|
|
|
} else {
|
|
|
|
/* Skip the data tag header - maybe we should parse it properly? */
|
|
|
|
lseek(fd,16,SEEK_CUR);
|
|
|
|
len-=16;
|
|
|
|
|
|
|
|
*dest=*bufptr;
|
|
|
|
if ((size_t)len+1 > *bytes_remaining) {
|
|
|
|
read(fd,*bufptr,*bytes_remaining-1);
|
|
|
|
lseek(fd,len-(*bytes_remaining-1),SEEK_CUR);
|
|
|
|
*bufptr+=(*bytes_remaining-1);
|
|
|
|
} else {
|
|
|
|
read(fd,*bufptr,len);
|
|
|
|
*bufptr+=len;
|
|
|
|
}
|
|
|
|
**bufptr=(char)0;
|
|
|
|
|
|
|
|
data_length = strlen(*dest)+1;
|
|
|
|
*bufptr=(*dest)+data_length;
|
|
|
|
*bytes_remaining-=data_length;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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))))
|
|
|
|
{
|
2005-06-14 22:27:57 +00:00
|
|
|
return false;
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
convert_endian(&header, APETAG_HEADER_FORMAT);
|
|
|
|
id3->genre = 0xff;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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)
|
|
|
|
{
|
2005-06-14 22:27:57 +00:00
|
|
|
return false;
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
tag_remaining -= r + item.length;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0)
|
|
|
|
{
|
|
|
|
long len;
|
|
|
|
|
|
|
|
if (read_string(fd, value, sizeof(value), -1, item.length)
|
|
|
|
!= item.length)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
}
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
return true;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
id3->genre = 255;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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);
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (tag_remaining <= 0)
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
for (i = 0; i < comment_count; i++)
|
|
|
|
{
|
|
|
|
char name[TAG_NAME_LENGTH];
|
|
|
|
char value[TAG_VALUE_LENGTH];
|
|
|
|
long read_len;
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (tag_remaining < 4)
|
|
|
|
{
|
|
|
|
break;
|
2005-06-14 22:27:57 +00:00
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (read(fd, &len, sizeof(len)) < (long) sizeof(len))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
convert_endian(&len, "L");
|
|
|
|
tag_remaining -= 4;
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Quit if we've passed the end of the page */
|
|
|
|
if (tag_remaining < len)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
tag_remaining -= len;
|
|
|
|
read_len = read_string(fd, name, sizeof(name), '=', len);
|
|
|
|
|
|
|
|
if (read_len < 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
len -= read_len;
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (read_string(fd, value, sizeof(value), -1, len) < 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
len = parse_tag(name, value, id3, buf, buf_remaining,
|
|
|
|
TAGTYPE_VORBIS);
|
|
|
|
buf += len;
|
|
|
|
buf_remaining -= len;
|
|
|
|
}
|
2005-06-14 23:12:34 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Skip to the end of the block */
|
|
|
|
if (tag_remaining)
|
|
|
|
{
|
|
|
|
if (lseek(fd, tag_remaining, SEEK_CUR) < 0)
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2005-06-14 22:27:57 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
return true;
|
2005-06-14 22:27:57 +00:00
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-11-05 13:58:08 +00:00
|
|
|
/* Skip an ID3v2 tag if it can be found. We assume the tag is located at the
|
|
|
|
* start of the file, which should be true in all cases where we need to skip it.
|
|
|
|
* Returns true if successfully skipped or not skipped, and false if
|
|
|
|
* something went wrong while skipping.
|
|
|
|
*/
|
|
|
|
static bool skip_id3v2(int fd, struct mp3entry *id3)
|
|
|
|
{
|
|
|
|
char buf[4];
|
|
|
|
|
|
|
|
read(fd, buf, 4);
|
|
|
|
if (memcmp(buf, "ID3", 3) == 0)
|
|
|
|
{
|
|
|
|
/* We have found an ID3v2 tag at the start of the file - find its
|
|
|
|
length and then skip it. */
|
|
|
|
if ((id3->first_frame_offset = getid3v2len(fd)) == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if ((lseek(fd, id3->first_frame_offset, SEEK_SET) < 0))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
} else {
|
|
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
id3->first_frame_offset = 0;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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.
|
2005-06-19 20:27:46 +00:00
|
|
|
*/
|
2005-09-22 16:58:03 +00:00
|
|
|
static bool get_vorbis_metadata(int fd, struct mp3entry* id3)
|
2005-06-19 20:27:46 +00:00
|
|
|
{
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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...
|
|
|
|
*/
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
2006-05-01 13:42:35 +00:00
|
|
|
long serial, r;
|
2005-09-22 16:58:03 +00:00
|
|
|
int segments;
|
|
|
|
int i;
|
|
|
|
bool eof = false;
|
|
|
|
|
|
|
|
if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 4))
|
|
|
|
{
|
2005-06-19 20:27:46 +00:00
|
|
|
return false;
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Comments are in second Ogg page */
|
|
|
|
if (lseek(fd, 58, SEEK_SET) < 0)
|
|
|
|
{
|
|
|
|
return false;
|
2005-07-25 03:34:25 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Minimum header length for Ogg pages is 27. */
|
|
|
|
if (read(fd, buf, 27) < 27)
|
|
|
|
{
|
|
|
|
return false;
|
2005-07-25 03:34:25 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (memcmp(buf, "OggS", 4) !=0 )
|
|
|
|
{
|
|
|
|
return false;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
segments = buf[26];
|
|
|
|
|
|
|
|
/* read in segment table */
|
|
|
|
if (read(fd, buf, segments) < segments)
|
|
|
|
{
|
|
|
|
return false;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Now read in packet header (type and id string) */
|
|
|
|
if (read(fd, buf, 7) < 7)
|
|
|
|
{
|
|
|
|
return false;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
comment_size = remaining;
|
|
|
|
remaining -= 7;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Failure to read the tags isn't fatal. */
|
|
|
|
read_vorbis_tags(fd, id3, remaining);
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* We now need to search for the last page in the file - identified by
|
|
|
|
* by ('O','g','g','S',0) and retrieve totalsamples.
|
|
|
|
*/
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2006-05-20 14:30:32 +00:00
|
|
|
/* A page is always < 64 kB */
|
|
|
|
if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
remaining = 0;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
while (!eof)
|
|
|
|
{
|
2006-05-01 13:42:35 +00:00
|
|
|
r = read(fd, &buf[remaining], MAX_PATH - remaining);
|
2005-09-22 16:58:03 +00:00
|
|
|
|
|
|
|
if (r <= 0)
|
|
|
|
{
|
|
|
|
eof = true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
remaining += r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Inefficient (but simple) search */
|
|
|
|
i = 0;
|
|
|
|
|
2006-05-01 13:42:35 +00:00
|
|
|
while (i < (remaining - 3))
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
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]);
|
2006-05-01 13:42:35 +00:00
|
|
|
|
|
|
|
/* If this page is very small the beginning of the next
|
|
|
|
* header could be in buffer. Jump near end of this header
|
|
|
|
* and continue */
|
|
|
|
i += 27;
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
i++;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2006-05-01 13:42:35 +00:00
|
|
|
if (i < remaining)
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
2006-05-01 13:42:35 +00:00
|
|
|
/* Move the remaining bytes to start of buffer.
|
|
|
|
* Reuse var 'segments' as it is no longer needed */
|
|
|
|
segments = 0;
|
|
|
|
while (i < remaining)
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
2006-05-01 13:42:35 +00:00
|
|
|
buf[segments++] = buf[i++];
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
2006-05-01 13:42:35 +00:00
|
|
|
remaining = segments;
|
2005-09-22 16:58:03 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2006-05-01 13:42:35 +00:00
|
|
|
/* Discard the rest of the buffer */
|
2005-09-22 16:58:03 +00:00
|
|
|
remaining = 0;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
id3->length = (id3->samples / id3->frequency) * 1000;
|
|
|
|
id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
|
|
|
|
id3->vbr = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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
|
|
|
|
*/
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
unsigned char* buf = id3->path;
|
|
|
|
bool rc = false;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-11-05 13:58:08 +00:00
|
|
|
if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4))
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
return rc;
|
|
|
|
}
|
2005-11-05 13:58:08 +00:00
|
|
|
|
|
|
|
if (memcmp(buf, "fLaC", 4) != 0)
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
return rc;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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];
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if ((buf[0] & 0x7f) == 0) /* 0 is the STREAMINFO block */
|
|
|
|
{
|
|
|
|
unsigned long totalsamples;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* 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;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
else
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
/* Skip to next metadata block */
|
|
|
|
if (lseek(fd, i, SEEK_CUR) < 0)
|
|
|
|
{
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
}
|
2005-09-22 16:58:03 +00:00
|
|
|
|
|
|
|
return true;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 19:36:25 +00:00
|
|
|
static bool get_wave_metadata(int fd, struct mp3entry* id3)
|
|
|
|
{
|
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
unsigned char* buf = id3->path;
|
|
|
|
unsigned long totalsamples = 0;
|
|
|
|
unsigned long channels = 0;
|
|
|
|
unsigned long bitspersample = 0;
|
|
|
|
unsigned long numbytes = 0;
|
|
|
|
int read_bytes;
|
|
|
|
int i;
|
|
|
|
|
2006-06-27 22:27:21 +00:00
|
|
|
/* get RIFF chunk header */
|
|
|
|
if ((lseek(fd, 0, SEEK_SET) < 0)
|
|
|
|
|| ((read_bytes = read(fd, buf, 12)) < 12))
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2006-06-27 22:27:21 +00:00
|
|
|
|
2005-09-22 19:36:25 +00:00
|
|
|
if ((memcmp(buf, "RIFF",4) != 0)
|
|
|
|
|| (memcmp(&buf[8], "WAVE", 4) !=0 ))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2006-06-27 22:27:21 +00:00
|
|
|
/* iterate over WAVE chunks until 'data' chunk */
|
|
|
|
while (true)
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
2006-06-27 22:27:21 +00:00
|
|
|
/* get chunk header */
|
|
|
|
if ((read_bytes = read(fd, buf, 8)) < 8)
|
|
|
|
return false;
|
|
|
|
|
2005-09-22 19:36:25 +00:00
|
|
|
/* chunkSize */
|
|
|
|
i = get_long(&buf[4]);
|
2006-06-27 22:27:21 +00:00
|
|
|
|
2005-09-22 19:36:25 +00:00
|
|
|
if (memcmp(buf, "fmt ", 4) == 0)
|
|
|
|
{
|
2006-06-27 22:27:21 +00:00
|
|
|
/* get rest of chunk */
|
|
|
|
if ((read_bytes = read(fd, buf, 16)) < 16)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
i -= 16;
|
|
|
|
|
2005-09-22 19:36:25 +00:00
|
|
|
/* skipping wFormatTag */
|
|
|
|
/* wChannels */
|
2006-06-27 22:27:21 +00:00
|
|
|
channels = buf[2] | (buf[3] << 8);
|
2005-09-22 19:36:25 +00:00
|
|
|
/* dwSamplesPerSec */
|
2006-06-27 22:27:21 +00:00
|
|
|
id3->frequency = get_long(&buf[4]);
|
2005-09-22 19:36:25 +00:00
|
|
|
/* dwAvgBytesPerSec */
|
2006-06-27 22:27:21 +00:00
|
|
|
id3->bitrate = (get_long(&buf[8]) * 8) / 1000;
|
2005-09-22 19:36:25 +00:00
|
|
|
/* skipping wBlockAlign */
|
|
|
|
/* wBitsPerSample */
|
2006-06-27 22:27:21 +00:00
|
|
|
bitspersample = buf[14] | (buf[15] << 8);
|
2005-09-22 19:36:25 +00:00
|
|
|
}
|
2006-06-27 22:27:21 +00:00
|
|
|
else if (memcmp(buf, "data", 4) == 0)
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
|
|
|
numbytes = i;
|
2006-06-27 22:27:21 +00:00
|
|
|
break;
|
2005-09-22 19:36:25 +00:00
|
|
|
}
|
2006-06-27 22:27:21 +00:00
|
|
|
else if (memcmp(buf, "fact", 4) == 0)
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
|
|
|
/* dwSampleLength */
|
2006-06-27 22:27:21 +00:00
|
|
|
if (i >= 4)
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
2006-06-27 22:27:21 +00:00
|
|
|
/* get rest of chunk */
|
|
|
|
if ((read_bytes = read(fd, buf, 2)) < 2)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
i -= 2;
|
|
|
|
totalsamples = get_long(buf);
|
2005-09-22 19:36:25 +00:00
|
|
|
}
|
|
|
|
}
|
2006-06-27 22:27:21 +00:00
|
|
|
|
|
|
|
/* seek to next chunk (even chunk sizes must be padded) */
|
2005-09-22 19:36:25 +00:00
|
|
|
if (i & 0x01)
|
|
|
|
i++;
|
2006-06-27 22:27:21 +00:00
|
|
|
|
|
|
|
if(lseek(fd, i, SEEK_CUR) < 0)
|
|
|
|
return false;
|
2005-09-22 19:36:25 +00:00
|
|
|
}
|
|
|
|
|
2006-06-27 22:27:21 +00:00
|
|
|
if ((numbytes == 0) || (channels == 0))
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2006-06-27 22:27:21 +00:00
|
|
|
|
|
|
|
if (totalsamples == 0)
|
2005-09-22 19:36:25 +00:00
|
|
|
{
|
|
|
|
/* for PCM only */
|
2006-06-27 22:27:21 +00:00
|
|
|
totalsamples = numbytes
|
2005-09-22 19:36:25 +00:00
|
|
|
/ ((((bitspersample - 1) / 8) + 1) * channels);
|
|
|
|
}
|
|
|
|
|
|
|
|
id3->vbr = false; /* All WAV files are CBR */
|
|
|
|
id3->filesize = filesize(fd);
|
|
|
|
|
|
|
|
/* Calculate track length (in ms) and estimate the bitrate (in kbit/s) */
|
|
|
|
id3->length = (totalsamples / id3->frequency) * 1000;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2005-10-31 20:56:29 +00:00
|
|
|
static bool get_m4a_metadata(int fd, struct mp3entry* id3)
|
2005-09-22 21:55:37 +00:00
|
|
|
{
|
|
|
|
unsigned char* buf;
|
|
|
|
unsigned long totalsamples;
|
|
|
|
int i,j,k;
|
|
|
|
size_t n;
|
|
|
|
size_t bytes_remaining;
|
|
|
|
char* id3buf;
|
|
|
|
unsigned int compressedsize;
|
|
|
|
unsigned int sample_count;
|
|
|
|
unsigned int sample_duration;
|
|
|
|
int numentries;
|
|
|
|
int entry_size;
|
|
|
|
int size_remaining;
|
|
|
|
int chunk_len;
|
|
|
|
unsigned char chunk_id[4];
|
|
|
|
int sub_chunk_len;
|
|
|
|
unsigned char sub_chunk_id[4];
|
|
|
|
|
|
|
|
/* A simple parser to read vital metadata from an ALAC file.
|
|
|
|
This parser also works for AAC files - they are both stored in
|
|
|
|
a Quicktime M4A container. */
|
|
|
|
|
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
buf=id3->path;
|
|
|
|
|
|
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
|
|
|
|
totalsamples=0;
|
|
|
|
compressedsize=0;
|
|
|
|
/* read the chunks - we stop when we find the mdat chunk and set compressedsize */
|
|
|
|
while (compressedsize==0) {
|
|
|
|
n=read_uint32be(fd,&chunk_len);
|
|
|
|
|
|
|
|
// This means it was a 64-bit file, so we have problems.
|
|
|
|
if (chunk_len == 1) {
|
|
|
|
logf("need 64bit support\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
n=read(fd,&chunk_id,4);
|
2006-08-26 10:45:49 +00:00
|
|
|
if (n < 4)
|
|
|
|
return false;
|
|
|
|
|
2005-09-22 21:55:37 +00:00
|
|
|
if (memcmp(&chunk_id,"ftyp",4)==0) {
|
|
|
|
/* Check for M4A type */
|
|
|
|
n=read(fd,&chunk_id,4);
|
2005-11-02 14:01:27 +00:00
|
|
|
if ((memcmp(&chunk_id,"M4A ",4)!=0) &&
|
|
|
|
(memcmp(&chunk_id,"mp42",4)!=0)) {
|
2005-09-22 21:55:37 +00:00
|
|
|
logf("Not an M4A file, aborting\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* Skip rest of chunk */
|
|
|
|
lseek(fd, chunk_len - 8 - 4, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
} else if (memcmp(&chunk_id,"moov",4)==0) {
|
|
|
|
size_remaining=chunk_len - 8; /* FIXME not 8 */
|
|
|
|
|
|
|
|
while (size_remaining > 0) {
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
if ((sub_chunk_len < 1) || (sub_chunk_len > size_remaining)) {
|
|
|
|
logf("Strange sub_chunk_len value inside moov: %d (remaining: %d)\n",sub_chunk_len,size_remaining);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
size_remaining-=8;
|
|
|
|
|
|
|
|
if (memcmp(&sub_chunk_id,"mvhd",4)==0) {
|
|
|
|
/* We don't need anything from here - skip */
|
|
|
|
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
size_remaining-=(sub_chunk_len-8);
|
|
|
|
} else if (memcmp(&sub_chunk_id,"udta",4)==0) {
|
|
|
|
/* The udta chunk contains the metadata - track, artist, album etc.
|
|
|
|
The format appears to be:
|
|
|
|
udta
|
|
|
|
meta
|
|
|
|
hdlr
|
|
|
|
ilst
|
|
|
|
.nam
|
|
|
|
[rest of tags]
|
|
|
|
free
|
|
|
|
|
|
|
|
NOTE: This code was written by examination of some .m4a files
|
|
|
|
produced by iTunes v4.9 - it may not therefore be 100%
|
|
|
|
compliant with all streams. But it should fail gracefully.
|
|
|
|
*/
|
|
|
|
j=(sub_chunk_len-8);
|
|
|
|
size_remaining-=j;
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
j-=8;
|
|
|
|
if (memcmp(&sub_chunk_id,"meta",4)==0) {
|
|
|
|
lseek(fd, 4, SEEK_CUR);
|
|
|
|
j-=4;
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
j-=8;
|
|
|
|
if (memcmp(&sub_chunk_id,"hdlr",4)==0) {
|
|
|
|
lseek(fd, sub_chunk_len - 8, SEEK_CUR);
|
|
|
|
j-=(sub_chunk_len - 8);
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
j-=8;
|
|
|
|
if (memcmp(&sub_chunk_id,"ilst",4)==0) {
|
|
|
|
/* Here are the actual tags. We use the id3v2 300-byte buffer
|
|
|
|
to store the string data */
|
|
|
|
bytes_remaining=sizeof(id3->id3v2buf);
|
|
|
|
id3->genre=255; /* Not every track is the Blues */
|
|
|
|
id3buf=id3->id3v2buf;
|
|
|
|
k=sub_chunk_len-8;
|
|
|
|
j-=k;
|
|
|
|
while (k > 0) {
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
k-=8;
|
|
|
|
if (memcmp(sub_chunk_id,"\251nam",4)==0) {
|
|
|
|
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->title);
|
|
|
|
} else if (memcmp(sub_chunk_id,"\251ART",4)==0) {
|
|
|
|
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->artist);
|
|
|
|
} else if (memcmp(sub_chunk_id,"\251alb",4)==0) {
|
|
|
|
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->album);
|
|
|
|
} else if (memcmp(sub_chunk_id,"\251gen",4)==0) {
|
|
|
|
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->genre_string);
|
|
|
|
} else if (memcmp(sub_chunk_id,"\251day",4)==0) {
|
|
|
|
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->year_string);
|
|
|
|
} else if (memcmp(sub_chunk_id,"trkn",4)==0) {
|
|
|
|
if (sub_chunk_len==0x20) {
|
|
|
|
read(fd,buf,sub_chunk_len-8);
|
|
|
|
id3->tracknum=buf[19];
|
|
|
|
} else {
|
|
|
|
lseek(fd, sub_chunk_len-8,SEEK_CUR);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
lseek(fd, sub_chunk_len-8,SEEK_CUR);
|
|
|
|
}
|
|
|
|
k-=(sub_chunk_len-8);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Skip any remaining data in udta chunk */
|
|
|
|
lseek(fd, j, SEEK_CUR);
|
|
|
|
} else if (memcmp(&sub_chunk_id,"trak",4)==0) {
|
|
|
|
/* Format of trak chunk:
|
|
|
|
tkhd
|
|
|
|
mdia
|
|
|
|
mdhd
|
|
|
|
hdlr
|
|
|
|
minf
|
|
|
|
smhd
|
|
|
|
dinf
|
|
|
|
stbl
|
|
|
|
stsd - Samplerate, Samplesize, Numchannels
|
|
|
|
stts - time_to_sample array - RLE'd table containing duration of each block
|
|
|
|
stsz - sample_byte_size array - ?Size in bytes of each compressed block
|
|
|
|
stsc - Seek table related?
|
|
|
|
stco - Seek table related?
|
|
|
|
*/
|
|
|
|
|
|
|
|
/* Skip tkhd - not needed */
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
if (memcmp(&sub_chunk_id,"tkhd",4)!=0) {
|
|
|
|
logf("Expecting tkhd\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
size_remaining-=sub_chunk_len;
|
|
|
|
|
2005-09-24 11:40:26 +00:00
|
|
|
/* Process mdia - skipping possible edts */
|
2005-09-22 21:55:37 +00:00
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
2005-09-24 11:40:26 +00:00
|
|
|
if (memcmp(&sub_chunk_id,"edts",4)==0) {
|
|
|
|
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
size_remaining-=sub_chunk_len;
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
}
|
|
|
|
|
2005-09-22 21:55:37 +00:00
|
|
|
if (memcmp(&sub_chunk_id,"mdia",4)!=0) {
|
|
|
|
logf("Expecting mdia\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
size_remaining-=sub_chunk_len;
|
|
|
|
j=sub_chunk_len-8;
|
|
|
|
|
|
|
|
while (j > 0) {
|
|
|
|
n=read_uint32be(fd,&sub_chunk_len);
|
|
|
|
n=read(fd,&sub_chunk_id,4);
|
|
|
|
j-=4;
|
|
|
|
if (memcmp(&sub_chunk_id,"minf",4)==0) {
|
|
|
|
j=sub_chunk_len-8;
|
|
|
|
} else if (memcmp(&sub_chunk_id,"stbl",4)==0) {
|
|
|
|
j=sub_chunk_len-8;
|
|
|
|
} else if (memcmp(&sub_chunk_id,"stsd",4)==0) {
|
|
|
|
n=read(fd,buf,sub_chunk_len-8);
|
|
|
|
j-=sub_chunk_len;
|
|
|
|
i=0;
|
|
|
|
/* Skip version and flags */
|
|
|
|
i+=4;
|
|
|
|
|
|
|
|
numentries=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
|
|
|
|
i+=4;
|
|
|
|
if (numentries!=1) {
|
|
|
|
logf("ERROR: Expecting only one entry in stsd\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
entry_size=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
|
|
|
|
i+=4;
|
|
|
|
|
2005-12-01 20:39:19 +00:00
|
|
|
if (memcmp(&buf[i],"alac",4)==0) {
|
|
|
|
id3->codectype=AFMT_ALAC;
|
|
|
|
} else if (memcmp(&buf[i],"mp4a",4)==0) {
|
|
|
|
id3->codectype=AFMT_AAC;
|
|
|
|
} else {
|
|
|
|
logf("Not an ALAC or AAC file\n");
|
2005-09-22 21:55:37 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
//numchannels=(buf[i+20]<<8)|buf[i+21]; /* Not used - assume Stereo */
|
|
|
|
//samplesize=(buf[i+22]<<8)|buf[i+23]; /* Not used - assume 16-bit */
|
|
|
|
|
|
|
|
/* Samplerate is 32-bit fixed point, but this works for < 65536 Hz */
|
|
|
|
id3->frequency=(buf[i+28]<<8)|buf[i+29];
|
|
|
|
} else if (memcmp(&sub_chunk_id,"stts",4)==0) {
|
|
|
|
j-=sub_chunk_len;
|
|
|
|
i=8;
|
|
|
|
n=read(fd,buf,8);
|
|
|
|
i+=8;
|
|
|
|
numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
|
|
|
|
for (k=0;k<numentries;k++) {
|
|
|
|
n=read_uint32be(fd,&sample_count);
|
|
|
|
n=read_uint32be(fd,&sample_duration);
|
|
|
|
totalsamples+=sample_count*sample_duration;
|
|
|
|
i+=8;
|
|
|
|
}
|
|
|
|
if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
|
|
|
|
} else if (memcmp(&sub_chunk_id,"stsz",4)==0) {
|
|
|
|
j-=sub_chunk_len;
|
|
|
|
i=8;
|
|
|
|
n=read(fd,buf,8);
|
|
|
|
i+=8;
|
|
|
|
numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
|
|
|
|
for (k=0;k<numentries;k++) {
|
|
|
|
n=read_uint32be(fd,&sample_count);
|
|
|
|
n=read_uint32be(fd,&sample_duration);
|
|
|
|
totalsamples+=sample_count*sample_duration;
|
|
|
|
i+=8;
|
|
|
|
}
|
|
|
|
if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
|
|
|
|
} else {
|
|
|
|
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
j-=sub_chunk_len;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logf("Unexpected sub_chunk_id inside moov: %c%c%c%c\n",
|
|
|
|
sub_chunk_id[0],sub_chunk_id[1],sub_chunk_id[2],sub_chunk_id[3]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (memcmp(&chunk_id,"mdat",4)==0) {
|
|
|
|
/* once we hit mdat we stop reading and return.
|
|
|
|
* this is on the assumption that there is no furhter interesting
|
|
|
|
* stuff in the stream. if there is stuff will fail (:()).
|
|
|
|
* But we need the read pointer to be at the mdat stuff
|
|
|
|
* for the decoder. And we don't want to rely on fseek/ftell,
|
|
|
|
* as they may not always be avilable */
|
|
|
|
lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
compressedsize=chunk_len-8;
|
|
|
|
} else if (memcmp(&chunk_id,"free",4)==0) {
|
|
|
|
/* these following atoms can be skipped !!!! */
|
|
|
|
lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
|
|
|
} else {
|
|
|
|
logf("(top) unknown chunk id: %c%c%c%c\n", chunk_id[0],chunk_id[1],chunk_id[2],chunk_id[3]);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
id3->vbr=true; /* All ALAC files are VBR */
|
|
|
|
id3->filesize=filesize(fd);
|
|
|
|
id3->samples=totalsamples;
|
|
|
|
id3->length=(10*totalsamples)/(id3->frequency/100);
|
|
|
|
id3->bitrate=(compressedsize*8)/id3->length;;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2005-11-04 21:34:03 +00:00
|
|
|
static bool get_musepack_metadata(int fd, struct mp3entry *id3)
|
|
|
|
{
|
|
|
|
const int32_t sfreqs_sv7[4] = { 44100, 48000, 37800, 32000 };
|
|
|
|
uint32_t header[8];
|
|
|
|
uint64_t samples = 0;
|
2005-11-05 13:58:08 +00:00
|
|
|
int i;
|
2005-11-04 21:34:03 +00:00
|
|
|
|
2005-11-05 13:58:08 +00:00
|
|
|
if (!skip_id3v2(fd, id3))
|
|
|
|
return false;
|
2005-11-04 21:34:03 +00:00
|
|
|
if (read(fd, header, 4*8) != 4*8) return false;
|
|
|
|
/* Musepack files are little endian, might need swapping */
|
|
|
|
for (i = 1; i < 8; i++)
|
|
|
|
header[i] = letoh32(header[i]);
|
|
|
|
if (!memcmp(header, "MP+", 3)) { /* Compare to sig "MP+" */
|
|
|
|
unsigned int streamversion;
|
|
|
|
|
|
|
|
header[0] = letoh32(header[0]);
|
|
|
|
streamversion = (header[0] >> 24) & 15;
|
|
|
|
if (streamversion >= 8) {
|
|
|
|
return false; /* SV8 or higher don't exist yet, so no support */
|
|
|
|
} else if (streamversion == 7) {
|
|
|
|
unsigned int gapless = (header[5] >> 31) & 0x0001;
|
|
|
|
unsigned int last_frame_samples = (header[5] >> 20) & 0x07ff;
|
|
|
|
int track_gain, album_gain;
|
|
|
|
unsigned int bufused;
|
|
|
|
|
|
|
|
id3->frequency = sfreqs_sv7[(header[2] >> 16) & 0x0003];
|
|
|
|
samples = (uint64_t)header[1]*1152; /* 1152 is mpc frame size */
|
|
|
|
if (gapless)
|
|
|
|
samples -= 1152 - last_frame_samples;
|
|
|
|
else
|
|
|
|
samples -= 481; /* Musepack subband synth filter delay */
|
|
|
|
|
|
|
|
/* Extract ReplayGain data from header */
|
|
|
|
track_gain = (int16_t)((header[3] >> 16) & 0xffff);
|
|
|
|
id3->track_gain = get_replaygain_int(track_gain);
|
|
|
|
id3->track_peak = ((uint16_t)(header[3] & 0xffff)) << 9;
|
|
|
|
|
|
|
|
album_gain = (int16_t)((header[4] >> 16) & 0xffff);
|
|
|
|
id3->album_gain = get_replaygain_int(album_gain);
|
|
|
|
id3->album_peak = ((uint16_t)(header[4] & 0xffff)) << 9;
|
|
|
|
|
2005-11-05 13:58:08 +00:00
|
|
|
/* Write replaygain values to strings for use in id3 screen. We use
|
|
|
|
the XING header as buffer space since Musepack files shouldn't
|
|
|
|
need to use it in any other way */
|
|
|
|
id3->track_gain_string = id3->toc;
|
|
|
|
bufused = snprintf(id3->track_gain_string, 45,
|
2005-11-04 21:34:03 +00:00
|
|
|
"%d.%d dB", track_gain/100, abs(track_gain)%100);
|
2005-11-05 13:58:08 +00:00
|
|
|
id3->album_gain_string = id3->toc + bufused + 1;
|
|
|
|
bufused = snprintf(id3->album_gain_string, 45,
|
2005-11-04 21:34:03 +00:00
|
|
|
"%d.%d dB", album_gain/100, abs(album_gain)%100);
|
|
|
|
}
|
|
|
|
} else {
|
2006-09-01 12:05:04 +00:00
|
|
|
header[0] = letoh32(header[0]);
|
|
|
|
unsigned int streamversion = (header[0] >> 11) & 0x03FF;
|
|
|
|
if (streamversion != 4 && streamversion != 5 && streamversion != 6)
|
|
|
|
return false;
|
|
|
|
id3->frequency = 44100;
|
|
|
|
id3->track_gain = 0;
|
|
|
|
id3->track_peak = 0;
|
|
|
|
id3->album_gain = 0;
|
|
|
|
id3->album_peak = 0;
|
|
|
|
|
|
|
|
if (streamversion >= 5)
|
|
|
|
samples = (uint64_t)header[1]*1152; // 32 bit
|
|
|
|
else
|
|
|
|
samples = (uint64_t)(header[1] >> 16)*1152; // 16 bit
|
|
|
|
|
|
|
|
samples -= 576;
|
|
|
|
if (streamversion < 6)
|
|
|
|
samples -= 1152;
|
2005-11-04 21:34:03 +00:00
|
|
|
}
|
|
|
|
|
2006-03-05 17:23:34 +00:00
|
|
|
id3->vbr = true;
|
2005-11-04 21:34:03 +00:00
|
|
|
/* Estimate bitrate, we should probably subtract the various header sizes
|
|
|
|
here for super-accurate results */
|
|
|
|
id3->length = samples/id3->frequency*1000;
|
|
|
|
id3->filesize = filesize(fd);
|
|
|
|
id3->bitrate = id3->filesize*8/id3->length;
|
|
|
|
return true;
|
|
|
|
}
|
2006-07-18 18:52:23 +00:00
|
|
|
|
|
|
|
static bool get_sid_metadata(int fd, struct mp3entry* id3)
|
|
|
|
{
|
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
unsigned char* buf = id3->path;
|
|
|
|
int read_bytes;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
|
|
|
|
if ((lseek(fd, 0, SEEK_SET) < 0)
|
|
|
|
|| ((read_bytes = read(fd, buf, sizeof(id3->path))) < 44))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((memcmp(buf, "PSID",4) != 0))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = id3->id3v2buf;
|
|
|
|
|
|
|
|
/* Copy Title */
|
|
|
|
strcpy(p, &buf[0x16]);
|
|
|
|
id3->title = p;
|
|
|
|
p += strlen(p)+1;
|
|
|
|
|
|
|
|
/* Copy Artist */
|
|
|
|
strcpy(p, &buf[0x36]);
|
|
|
|
id3->artist = p;
|
|
|
|
p += strlen(p)+1;
|
|
|
|
|
|
|
|
id3->bitrate = 706;
|
|
|
|
id3->frequency = 44100;
|
|
|
|
/* New idea as posted by Marco Alanen (ravon):
|
|
|
|
* Set the songlength in seconds to the number of subsongs
|
|
|
|
* so every second represents a subsong.
|
|
|
|
* Users can then skip the current subsong by seeking */
|
|
|
|
id3->length = (buf[0xf]-1)*1000;
|
|
|
|
id3->vbr = false;
|
|
|
|
id3->filesize = filesize(fd);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2006-03-26 11:33:42 +00:00
|
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
2005-11-04 21:34:03 +00:00
|
|
|
|
2006-02-01 16:42:02 +00:00
|
|
|
static bool get_aiff_metadata(int fd, struct mp3entry* id3)
|
|
|
|
{
|
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
unsigned char* buf = id3->path;
|
|
|
|
unsigned long numChannels = 0;
|
|
|
|
unsigned long numSampleFrames = 0;
|
|
|
|
unsigned long sampleSize = 0;
|
|
|
|
unsigned long sampleRate = 0;
|
|
|
|
unsigned long numbytes = 0;
|
|
|
|
int read_bytes;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if ((lseek(fd, 0, SEEK_SET) < 0)
|
|
|
|
|| ((read_bytes = read(fd, buf, sizeof(id3->path))) < 44))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((memcmp(buf, "FORM",4) != 0)
|
|
|
|
|| (memcmp(&buf[8], "AIFF", 4) !=0 ))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf += 12;
|
|
|
|
read_bytes -= 12;
|
|
|
|
|
|
|
|
while ((numbytes == 0) && (read_bytes >= 8))
|
|
|
|
{
|
|
|
|
/* chunkSize */
|
|
|
|
i = ((buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7]);
|
|
|
|
|
|
|
|
if (memcmp(buf, "COMM", 4) == 0)
|
|
|
|
{
|
|
|
|
/* numChannels */
|
|
|
|
numChannels = ((buf[8]<<8)|buf[9]);
|
|
|
|
/* numSampleFrames */
|
|
|
|
numSampleFrames =((buf[10]<<24)|(buf[11]<<16)|(buf[12]<<8)|buf[13]);
|
|
|
|
/* sampleSize */
|
|
|
|
sampleSize = ((buf[14]<<8)|buf[15]);
|
|
|
|
/* sampleRate */
|
|
|
|
sampleRate = ((buf[18]<<24)|(buf[19]<<16)|(buf[20]<<8)|buf[21]);
|
|
|
|
sampleRate = sampleRate >> (16+14-buf[17]);
|
|
|
|
/* save format infos */
|
|
|
|
id3->bitrate = (sampleSize * numChannels * sampleRate) / 1000;
|
|
|
|
id3->frequency = sampleRate;
|
|
|
|
id3->length = (numSampleFrames / id3->frequency) * 1000;
|
|
|
|
id3->vbr = false; /* AIFF files are CBR */
|
|
|
|
id3->filesize = filesize(fd);
|
|
|
|
}
|
|
|
|
else if (memcmp(buf, "SSND", 4) == 0)
|
|
|
|
{
|
|
|
|
numbytes = i - 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (i & 0x01)
|
|
|
|
{
|
|
|
|
i++; /* odd chunk sizes must be padded */
|
|
|
|
}
|
|
|
|
buf += i + 8;
|
|
|
|
read_bytes -= i + 8;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((numbytes == 0) || (numChannels == 0))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Simple file type probing by looking at the filename extension. */
|
2006-03-26 11:33:42 +00:00
|
|
|
unsigned int probe_file_format(const char *filename)
|
2005-06-19 20:27:46 +00:00
|
|
|
{
|
2005-09-22 16:58:03 +00:00
|
|
|
char *suffix;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
suffix = strrchr(filename, '.');
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if (suffix == NULL)
|
|
|
|
{
|
|
|
|
return AFMT_UNKNOWN;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
2005-09-22 16:58:03 +00:00
|
|
|
|
|
|
|
suffix += 1;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++)
|
|
|
|
{
|
|
|
|
if (strcasecmp(suffix, formats[i].extension) == 0)
|
|
|
|
{
|
|
|
|
return formats[i].format;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return AFMT_UNKNOWN;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
/* Get metadata for track - return false if parsing showed problems with the
|
|
|
|
* file that would prevent playback.
|
2005-06-19 20:27:46 +00:00
|
|
|
*/
|
2005-09-22 16:58:03 +00:00
|
|
|
bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
|
|
|
bool v1first)
|
2005-06-19 20:27:46 +00:00
|
|
|
{
|
2006-03-26 11:33:42 +00:00
|
|
|
#if CONFIG_CODEC == SWCODEC
|
2005-09-22 16:58:03 +00:00
|
|
|
unsigned char* buf;
|
|
|
|
unsigned long totalsamples;
|
|
|
|
int i;
|
2006-03-26 11:33:42 +00:00
|
|
|
#endif
|
2005-12-01 20:39:19 +00:00
|
|
|
|
|
|
|
/* Take our best guess at the codec type based on file extension */
|
2005-12-01 18:44:11 +00:00
|
|
|
track->id3.codectype = probe_file_format(trackname);
|
2005-12-01 20:39:19 +00:00
|
|
|
|
|
|
|
/* Load codec specific track tag information and confirm the codec type. */
|
2005-09-22 16:58:03 +00:00
|
|
|
switch (track->id3.codectype)
|
|
|
|
{
|
|
|
|
case AFMT_MPA_L1:
|
|
|
|
case AFMT_MPA_L2:
|
|
|
|
case AFMT_MPA_L3:
|
|
|
|
if (mp3info(&track->id3, trackname, v1first))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2006-03-26 11:33:42 +00:00
|
|
|
#if CONFIG_CODEC == SWCODEC
|
2005-09-22 16:58:03 +00:00
|
|
|
case AFMT_FLAC:
|
|
|
|
if (!get_flac_metadata(fd, &(track->id3)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
|
|
|
|
2005-10-10 18:18:24 +00:00
|
|
|
case AFMT_MPC:
|
2005-11-04 21:34:03 +00:00
|
|
|
if (!get_musepack_metadata(fd, &(track->id3)))
|
|
|
|
return false;
|
2005-10-10 22:11:51 +00:00
|
|
|
read_ape_tags(fd, &(track->id3));
|
2005-10-10 18:18:24 +00:00
|
|
|
break;
|
2005-11-04 21:34:03 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
case AFMT_OGG_VORBIS:
|
|
|
|
if (!get_vorbis_metadata(fd, &(track->id3)))
|
|
|
|
{
|
|
|
|
return false;
|
2005-06-19 20:27:46 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
2005-06-19 20:27:46 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
case AFMT_PCM_WAV:
|
2005-09-22 19:36:25 +00:00
|
|
|
if (!get_wave_metadata(fd, &(track->id3)))
|
2005-09-22 16:58:03 +00:00
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
2005-07-05 08:43:36 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case AFMT_WAVPACK:
|
2006-09-01 23:17:51 +00:00
|
|
|
/* A simple parser to read basic information from a WavPack file. This
|
|
|
|
* now works with self-extrating WavPack files and also will fail on
|
|
|
|
* WavPack files containing floating-point audio data (although these
|
|
|
|
* should be possible to play in theory).
|
2005-09-22 16:58:03 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
/* Use the trackname part of the id3 structure as a temporary buffer */
|
|
|
|
buf = track->id3.path;
|
|
|
|
|
2006-09-01 23:17:51 +00:00
|
|
|
for (i = 0; i < 256; ++i) {
|
|
|
|
|
|
|
|
/* at every 256 bytes into file, try to read a WavPack header */
|
|
|
|
|
|
|
|
if ((lseek(fd, i * 256, SEEK_SET) < 0) || (read(fd, buf, 32) < 32))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if valid WavPack 4 header version & not floating data, break */
|
|
|
|
|
|
|
|
if (memcmp (buf, "wvpk", 4) == 0 && buf [9] == 4 &&
|
|
|
|
(buf [8] >= 2 && buf [8] <= 0x10) && !(buf [24] & 0x80))
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
2005-07-28 20:21:54 +00:00
|
|
|
}
|
|
|
|
|
2006-09-01 23:17:51 +00:00
|
|
|
if (i == 256) {
|
2005-09-22 16:58:03 +00:00
|
|
|
logf ("%s is not a WavPack file\n", trackname);
|
2005-07-05 08:43:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
track->id3.vbr = true; /* All WavPack files are VBR */
|
|
|
|
track->id3.filesize = filesize (fd);
|
2005-07-05 08:43:36 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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);
|
2005-07-05 08:43:36 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
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;
|
2005-07-05 08:43:36 +00:00
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
if ((buf[0] != 0x0b) || (buf[1] != 0x77))
|
|
|
|
{
|
|
|
|
logf("%s is not an A52/AC3 file\n",trackname);
|
2005-07-05 08:43:36 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
i = buf[4] & 0x3e;
|
|
|
|
|
|
|
|
if (i > 36)
|
|
|
|
{
|
|
|
|
logf("A52: Invalid frmsizecod: %d\n",i);
|
|
|
|
return false;
|
2005-07-05 08:43:36 +00:00
|
|
|
}
|
2005-09-22 16:58:03 +00:00
|
|
|
|
|
|
|
track->id3.bitrate = a52_bitrates[i >> 1];
|
|
|
|
track->id3.vbr = false;
|
|
|
|
track->id3.filesize = filesize(fd);
|
2005-07-28 18:43:33 +00:00
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
switch (buf[4] & 0xc0)
|
|
|
|
{
|
|
|
|
case 0x00:
|
|
|
|
track->id3.frequency = 48000;
|
2005-10-22 09:10:51 +00:00
|
|
|
track->id3.bytesperframe=track->id3.bitrate * 2 * 2;
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x40:
|
|
|
|
track->id3.frequency = 44100;
|
2005-10-22 09:10:51 +00:00
|
|
|
track->id3.bytesperframe = a52_441framesizes[i];
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x80:
|
|
|
|
track->id3.frequency = 32000;
|
2005-10-22 09:10:51 +00:00
|
|
|
track->id3.bytesperframe = track->id3.bitrate * 3 * 2;
|
2005-09-22 16:58:03 +00:00
|
|
|
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 */
|
2006-04-11 03:54:24 +00:00
|
|
|
totalsamples = track->id3.filesize / track->id3.bytesperframe * 6 * 256;
|
|
|
|
track->id3.length = totalsamples / track->id3.frequency * 1000;
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
|
|
|
|
2005-09-22 21:55:37 +00:00
|
|
|
case AFMT_ALAC:
|
2005-10-31 20:56:29 +00:00
|
|
|
case AFMT_AAC:
|
|
|
|
if (!get_m4a_metadata(fd, &(track->id3)))
|
2005-09-22 21:55:37 +00:00
|
|
|
{
|
2005-12-01 20:39:19 +00:00
|
|
|
return false;
|
2005-09-22 21:55:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2005-11-13 11:04:02 +00:00
|
|
|
case AFMT_SHN:
|
|
|
|
track->id3.vbr = true;
|
|
|
|
track->id3.filesize = filesize(fd);
|
|
|
|
if (!skip_id3v2(fd, &(track->id3)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* TODO: read the id3v2 header if it exists */
|
|
|
|
break;
|
2006-07-18 18:33:12 +00:00
|
|
|
|
|
|
|
case AFMT_SID:
|
|
|
|
if (!get_sid_metadata(fd, &(track->id3)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
2006-03-26 11:33:42 +00:00
|
|
|
#endif /* CONFIG_CODEC == SWCODEC */
|
2005-11-13 11:04:02 +00:00
|
|
|
|
2006-02-01 16:42:02 +00:00
|
|
|
case AFMT_AIFF:
|
|
|
|
if (!get_aiff_metadata(fd, &(track->id3)))
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
default:
|
2005-12-01 20:39:19 +00:00
|
|
|
/* If we don't know how to read the metadata, assume we can't play
|
|
|
|
the file */
|
|
|
|
return false;
|
2005-09-22 16:58:03 +00:00
|
|
|
break;
|
2005-07-28 20:21:54 +00:00
|
|
|
}
|
|
|
|
|
2005-12-01 20:39:19 +00:00
|
|
|
/* We have successfully read the metadata from the file */
|
|
|
|
|
2005-09-22 16:58:03 +00:00
|
|
|
lseek(fd, 0, SEEK_SET);
|
|
|
|
strncpy(track->id3.path, trackname, sizeof(track->id3.path));
|
|
|
|
track->taginfo_ready = true;
|
|
|
|
|
2005-07-05 08:43:36 +00:00
|
|
|
return true;
|
|
|
|
}
|
2006-03-26 11:33:42 +00:00
|
|
|
|