rockbox/apps/metadata.c
2006-10-29 22:18:30 +00:00

1939 lines
52 KiB
C

/***************************************************************************
* __________ __ ___.
* 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>
#include <inttypes.h>
#include "errno.h"
#include "metadata.h"
#include "mp3_playback.h"
#include "logf.h"
#include "atoi.h"
#include "replaygain.h"
#include "debug.h"
#include "system.h"
enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
#define APETAG_HEADER_LENGTH 32
#define APETAG_HEADER_FORMAT "8LLLL"
#define APETAG_ITEM_HEADER_FORMAT "LL"
#define APETAG_ITEM_TYPE_MASK 3
#define TAG_NAME_LENGTH 32
#define TAG_VALUE_LENGTH 128
#define MP4_ID(a, b, c, d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d))
#define MP4_3gp6 MP4_ID('3', 'g', 'p', '6')
#define MP4_alac MP4_ID('a', 'l', 'a', 'c')
#define MP4_calb MP4_ID(0xa9, 'a', 'l', 'b')
#define MP4_cART MP4_ID(0xa9, 'A', 'R', 'T')
#define MP4_cnam MP4_ID(0xa9, 'n', 'a', 'm')
#define MP4_cwrt MP4_ID(0xa9, 'w', 'r', 't')
#define MP4_ftyp MP4_ID('f', 't', 'y', 'p')
#define MP4_gnre MP4_ID('g', 'n', 'r', 'e')
#define MP4_hdlr MP4_ID('h', 'd', 'l', 'r')
#define MP4_ilst MP4_ID('i', 'l', 's', 't')
#define MP4_M4A MP4_ID('M', '4', 'A', ' ')
#define MP4_mdat MP4_ID('m', 'd', 'a', 't')
#define MP4_mdia MP4_ID('m', 'd', 'i', 'a')
#define MP4_mdir MP4_ID('m', 'd', 'i', 'r')
#define MP4_meta MP4_ID('m', 'e', 't', 'a')
#define MP4_minf MP4_ID('m', 'i', 'n', 'f')
#define MP4_moov MP4_ID('m', 'o', 'o', 'v')
#define MP4_mp4a MP4_ID('m', 'p', '4', 'a')
#define MP4_mp42 MP4_ID('m', 'p', '4', '2')
#define MP4_qt MP4_ID('q', 't', ' ', ' ')
#define MP4_soun MP4_ID('s', 'o', 'u', 'n')
#define MP4_stbl MP4_ID('s', 't', 'b', 'l')
#define MP4_stsd MP4_ID('s', 't', 's', 'd')
#define MP4_stts MP4_ID('s', 't', 't', 's')
#define MP4_trak MP4_ID('t', 'r', 'a', 'k')
#define MP4_trkn MP4_ID('t', 'r', 'k', 'n')
#define MP4_udta MP4_ID('u', 'd', 't', 'a')
#define MP4_extra MP4_ID('-', '-', '-', '-')
struct apetag_header
{
char id[8];
long version;
long length;
long item_count;
long flags;
char reserved[8];
};
struct apetag_item_header
{
long length;
long flags;
};
struct format_list
{
char format;
char extension[5];
};
static const struct format_list formats[] =
{
{ AFMT_MPA_L1, "mp1" },
{ AFMT_MPA_L2, "mp2" },
{ AFMT_MPA_L2, "mpa" },
{ AFMT_MPA_L3, "mp3" },
#if CONFIG_CODEC == SWCODEC
{ AFMT_OGG_VORBIS, "ogg" },
{ AFMT_PCM_WAV, "wav" },
{ AFMT_FLAC, "flac" },
{ AFMT_MPC, "mpc" },
{ AFMT_A52, "a52" },
{ AFMT_A52, "ac3" },
{ AFMT_WAVPACK, "wv" },
{ AFMT_ALAC, "m4a" },
{ AFMT_AAC, "mp4" },
{ AFMT_SHN, "shn" },
{ AFMT_AIFF, "aif" },
{ AFMT_AIFF, "aiff" },
{ AFMT_SID, "sid" },
{ AFMT_ADX, "adx" },
#endif
};
#if CONFIG_CODEC == SWCODEC
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
};
/* 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
};
static const long wavpack_sample_rates [] =
{
6000, 8000, 9600, 11025, 12000, 16000, 22050, 24000,
32000, 44100, 48000, 64000, 88200, 96000, 192000
};
/* 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;
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;
}
/* 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;
*d = letoh32(*d);
data = d + 1;
}
break;
case 'S':
{
short* d = (short*) data;
*d = letoh16(*d);
data = d + 1;
}
break;
default:
if (isdigit(*format))
{
data = ((char*) data) + *format - '0';
}
break;
}
format++;
}
}
/* Read an unsigned 16-bit integer from a big-endian file. */
#ifdef ROCKBOX_BIG_ENDIAN
#define read_uint16be(fd, buf) read((fd), (buf), 2)
#else
int read_uint16be(int fd, unsigned short* buf)
{
size_t n;
n = read(fd, (char*) buf, 2);
*buf = betoh16(*buf);
return n;
}
#endif
/* Read an unsigned 32-bit integer from a big-endian file. */
#ifdef ROCKBOX_BIG_ENDIAN
#define read_uint32be(fd,buf) read((fd), (buf), 4)
#else
int read_uint32be(int fd, unsigned int* buf)
{
size_t n;
n = read(fd, (char*) buf, 4);
*buf = betoh32(*buf);
return n;
}
#endif
/* Read an unaligned 32-bit little endian long from buffer. */
static unsigned long get_long(void* buf)
{
unsigned char* p = (unsigned char*) buf;
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
}
/* 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;
}
}
/* 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;
}
/* 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;
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))))
{
return false;
}
convert_endian(&header, APETAG_HEADER_FORMAT);
id3->genre = 0xff;
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)
{
return false;
}
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;
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;
}
tag_remaining -= r + item.length;
if ((item.flags & APETAG_ITEM_TYPE_MASK) == 0)
{
long len;
if (read_string(fd, value, sizeof(value), -1, item.length)
!= item.length)
{
return false;
}
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;
}
}
}
}
return true;
}
/* 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;
id3->genre = 255;
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);
if (tag_remaining <= 0)
{
return true;
}
for (i = 0; i < comment_count; i++)
{
char name[TAG_NAME_LENGTH];
char value[TAG_VALUE_LENGTH];
long read_len;
if (tag_remaining < 4)
{
break;
}
if (read(fd, &len, sizeof(len)) < (long) sizeof(len))
{
return false;
}
convert_endian(&len, "L");
tag_remaining -= 4;
/* Quit if we've passed the end of the page */
if (tag_remaining < len)
{
break;
}
tag_remaining -= len;
read_len = read_string(fd, name, sizeof(name), '=', len);
if (read_len < 0)
{
return false;
}
len -= read_len;
if (read_string(fd, value, sizeof(value), -1, len) < 0)
{
return false;
}
len = parse_tag(name, value, id3, buf, buf_remaining,
TAGTYPE_VORBIS);
buf += len;
buf_remaining -= len;
}
/* Skip to the end of the block */
if (tag_remaining)
{
if (lseek(fd, tag_remaining, SEEK_CUR) < 0)
{
return false;
}
}
return true;
}
/* 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;
}
}
/* 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.
*/
static bool get_vorbis_metadata(int fd, struct mp3entry* id3)
{
/* 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...
*/
/* 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;
long serial, r;
int segments;
int i;
bool eof = false;
if ((lseek(fd, 0, SEEK_SET) < 0) || (read(fd, buf, 58) < 4))
{
return false;
}
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);
/* Comments are in second Ogg page */
if (lseek(fd, 58, SEEK_SET) < 0)
{
return false;
}
/* Minimum header length for Ogg pages is 27. */
if (read(fd, buf, 27) < 27)
{
return false;
}
if (memcmp(buf, "OggS", 4) !=0 )
{
return false;
}
segments = buf[26];
/* read in segment table */
if (read(fd, buf, segments) < segments)
{
return false;
}
/* 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;
}
}
/* Now read in packet header (type and id string) */
if (read(fd, buf, 7) < 7)
{
return false;
}
comment_size = remaining;
remaining -= 7;
/* 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;
}
/* Failure to read the tags isn't fatal. */
read_vorbis_tags(fd, id3, remaining);
/* We now need to search for the last page in the file - identified by
* by ('O','g','g','S',0) and retrieve totalsamples.
*/
/* A page is always < 64 kB */
if (lseek(fd, -(MIN(64 * 1024, id3->filesize)), SEEK_END) < 0)
{
return false;
}
remaining = 0;
while (!eof)
{
r = read(fd, &buf[remaining], MAX_PATH - remaining);
if (r <= 0)
{
eof = true;
}
else
{
remaining += r;
}
/* Inefficient (but simple) search */
i = 0;
while (i < (remaining - 3))
{
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]);
/* 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;
}
else
{
break;
}
}
else
{
i++;
}
}
if (i < remaining)
{
/* Move the remaining bytes to start of buffer.
* Reuse var 'segments' as it is no longer needed */
segments = 0;
while (i < remaining)
{
buf[segments++] = buf[i++];
}
remaining = segments;
}
else
{
/* Discard the rest of the buffer */
remaining = 0;
}
}
/* 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;
}
id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
if (id3->length <= 0)
{
logf("ogg length invalid!");
return false;
}
id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
id3->vbr = true;
return true;
}
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
*/
/* Use the trackname part of the id3 structure as a temporary buffer */
unsigned char* buf = id3->path;
bool rc = false;
if (!skip_id3v2(fd, id3) || (read(fd, buf, 4) < 4))
{
return rc;
}
if (memcmp(buf, "fLaC", 4) != 0)
{
return rc;
}
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];
if ((buf[0] & 0x7f) == 0) /* 0 is the STREAMINFO block */
{
unsigned long totalsamples;
/* 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 = ((int64_t) totalsamples * 1000) / id3->frequency;
if (id3->length <= 0)
{
logf("flac length invalid!");
return false;
}
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;
}
else
{
/* Skip to next metadata block */
if (lseek(fd, i, SEEK_CUR) < 0)
{
return rc;
}
}
}
}
return true;
}
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;
/* get RIFF chunk header */
if ((lseek(fd, 0, SEEK_SET) < 0)
|| ((read_bytes = read(fd, buf, 12)) < 12))
{
return false;
}
if ((memcmp(buf, "RIFF",4) != 0)
|| (memcmp(&buf[8], "WAVE", 4) !=0 ))
{
return false;
}
/* iterate over WAVE chunks until 'data' chunk */
while (true)
{
/* get chunk header */
if ((read_bytes = read(fd, buf, 8)) < 8)
return false;
/* chunkSize */
i = get_long(&buf[4]);
if (memcmp(buf, "fmt ", 4) == 0)
{
/* get rest of chunk */
if ((read_bytes = read(fd, buf, 16)) < 16)
return false;
i -= 16;
/* skipping wFormatTag */
/* wChannels */
channels = buf[2] | (buf[3] << 8);
/* dwSamplesPerSec */
id3->frequency = get_long(&buf[4]);
/* dwAvgBytesPerSec */
id3->bitrate = (get_long(&buf[8]) * 8) / 1000;
/* skipping wBlockAlign */
/* wBitsPerSample */
bitspersample = buf[14] | (buf[15] << 8);
}
else if (memcmp(buf, "data", 4) == 0)
{
numbytes = i;
break;
}
else if (memcmp(buf, "fact", 4) == 0)
{
/* dwSampleLength */
if (i >= 4)
{
/* get rest of chunk */
if ((read_bytes = read(fd, buf, 2)) < 2)
return false;
i -= 2;
totalsamples = get_long(buf);
}
}
/* seek to next chunk (even chunk sizes must be padded) */
if (i & 0x01)
i++;
if(lseek(fd, i, SEEK_CUR) < 0)
return false;
}
if ((numbytes == 0) || (channels == 0))
{
return false;
}
if (totalsamples == 0)
{
/* for PCM only */
totalsamples = numbytes
/ ((((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 = ((int64_t) totalsamples * 1000) / id3->frequency;
return true;
}
/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
* buffer.
*/
unsigned long read_mp4_tag(int fd, unsigned int size_left, char* buffer,
unsigned int buffer_left)
{
unsigned int bytes_read = 0;
if (buffer_left == 0)
{
lseek(fd, size_left, SEEK_CUR); /* Skip everything */
}
else
{
/* Skip the data tag header - maybe we should parse it properly? */
lseek(fd, 16, SEEK_CUR);
size_left -= 16;
if (size_left > buffer_left)
{
read(fd, buffer, buffer_left);
lseek(fd, size_left - buffer_left, SEEK_CUR);
bytes_read = buffer_left;
}
else
{
read(fd, buffer, size_left);
bytes_read = size_left;
}
}
return bytes_read;
}
/* Read a string tag from an MP4 file */
unsigned int read_mp4_tag_string(int fd, int size_left, char** buffer,
unsigned int* buffer_left, char** dest)
{
unsigned int bytes_read = read_mp4_tag(fd, size_left, *buffer,
*buffer_left - 1);
unsigned int length = 0;
if (bytes_read)
{
(*buffer)[bytes_read] = 0;
*dest = *buffer;
length = strlen(*buffer) + 1;
*buffer_left -= length;
*buffer += length;
}
else
{
*dest = NULL;
}
return length;
}
static unsigned int read_mp4_atom(int fd, unsigned int* size,
unsigned int* type, unsigned int size_left)
{
read_uint32be(fd, size);
read_uint32be(fd, type);
if (*size == 1)
{
/* FAT32 doesn't support files this big, so something seems to
* be wrong. (64-bit sizes should only be used when required.)
*/
errno = EFBIG;
*type = 0;
return 0;
}
if (*size > 0)
{
if (*size > size_left)
{
size_left = 0;
}
else
{
size_left -= *size;
}
*size -= 8;
}
else
{
*size = size_left;
size_left = 0;
}
return size_left;
}
static bool read_mp4_tags(int fd, struct mp3entry* id3,
unsigned int size_left)
{
unsigned int size;
unsigned int type;
unsigned int buffer_left = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
char* buffer = id3->id3v2buf;
bool cwrt = false;
do
{
size_left = read_mp4_atom(fd, &size, &type, size_left);
/* DEBUGF("Tag atom: '%c%c%c%c' (%d bytes left)\n", type >> 24 & 0xff,
type >> 16 & 0xff, type >> 8 & 0xff, type & 0xff, size); */
switch (type)
{
case MP4_cnam:
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
&id3->title);
break;
case MP4_cART:
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
&id3->artist);
break;
case MP4_calb:
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
&id3->album);
break;
case MP4_cwrt:
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
&id3->composer);
cwrt = false;
break;
case MP4_gnre:
{
unsigned short genre;
read_mp4_tag(fd, size, (char*) &genre, sizeof(genre));
id3->genre = betoh16(genre);
}
break;
case MP4_trkn:
{
unsigned short n[2];
read_mp4_tag(fd, size, (char*) &n, sizeof(n));
id3->tracknum = betoh16(n[1]);
}
break;
case MP4_extra:
{
char tag_name[TAG_NAME_LENGTH];
unsigned int sub_size;
/* "mean" atom */
read_uint32be(fd, &sub_size);
size -= sub_size;
lseek(fd, sub_size - 4, SEEK_CUR);
/* "name" atom */
read_uint32be(fd, &sub_size);
size -= sub_size;
lseek(fd, 8, SEEK_CUR);
sub_size -= 12;
if (sub_size > sizeof(tag_name) - 1)
{
read(fd, tag_name, sizeof(tag_name) - 1);
lseek(fd, sub_size - sizeof(tag_name) - 1, SEEK_CUR);
tag_name[sizeof(tag_name) - 1] = 0;
}
else
{
read(fd, tag_name, sub_size);
tag_name[sub_size] = 0;
}
if ((strcasecmp(tag_name, "composer") == 0) && !cwrt)
{
read_mp4_tag_string(fd, size, &buffer, &buffer_left,
&id3->composer);
}
else
{
char* any;
unsigned int length = read_mp4_tag_string(fd, size,
&buffer, &buffer_left, &any);
if (length > 0)
{
/* Re-use the read buffer as the dest buffer... */
buffer -= length;
buffer_left += length;
parse_replaygain(tag_name, buffer, id3, buffer,
buffer_left);
}
}
}
break;
default:
lseek(fd, size, SEEK_CUR);
break;
}
}
while ((size_left > 0) && (errno == 0));
return true;
}
static bool read_mp4_container(int fd, struct mp3entry* id3,
unsigned int size_left)
{
unsigned int size;
unsigned int type;
unsigned int handler = 0;
bool rc = true;
do
{
size_left = read_mp4_atom(fd, &size, &type, size_left);
/* DEBUGF("Atom: '%c%c%c%c' (0x%08x, %d bytes left)\n",
(type >> 24) & 0xff, (type >> 16) & 0xff, (type >> 8) & 0xff,
type & 0xff, type, size); */
switch (type)
{
case MP4_ftyp:
{
unsigned int id;
read_uint32be(fd, &id);
size -= 4;
if ((id != MP4_M4A) && (id != MP4_mp42) && (id != MP4_qt)
&& (id != MP4_3gp6))
{
DEBUGF("Unknown MP4 file type: '%c%c%c%c'\n",
id >> 24 & 0xff, id >> 16 & 0xff, id >> 8 & 0xff,
id & 0xff);
return false;
}
}
break;
case MP4_meta:
lseek(fd, 4, SEEK_CUR); /* Skip version */
size -= 4;
/* Fall through */
case MP4_moov:
case MP4_udta:
case MP4_mdia:
case MP4_stbl:
case MP4_trak:
rc = read_mp4_container(fd, id3, size);
size = 0;
break;
case MP4_ilst:
if (handler == MP4_mdir)
{
rc = read_mp4_tags(fd, id3, size);
size = 0;
}
break;
case MP4_minf:
if (handler == MP4_soun)
{
rc = read_mp4_container(fd, id3, size);
size = 0;
}
break;
case MP4_stsd:
lseek(fd, 8, SEEK_CUR);
size -= 8;
rc = read_mp4_container(fd, id3, size);
size = 0;
break;
case MP4_hdlr:
lseek(fd, 8, SEEK_CUR);
read_uint32be(fd, &handler);
size -= 12;
/* DEBUGF(" Handler '%c%c%c%c'\n", handler >> 24 & 0xff,
handler >> 16 & 0xff, handler >> 8 & 0xff,handler & 0xff); */
break;
case MP4_stts:
{
unsigned int entries;
unsigned int i;
lseek(fd, 4, SEEK_CUR);
read_uint32be(fd, &entries);
id3->samples = 0;
for (i = 0; i < entries; i++)
{
unsigned int n;
unsigned int l;
read_uint32be(fd, &n);
read_uint32be(fd, &l);
id3->samples += n * l;
}
size = 0;
}
break;
case MP4_mp4a:
case MP4_alac:
{
unsigned int frequency;
id3->codectype = (type == MP4_mp4a) ? AFMT_AAC : AFMT_ALAC;
lseek(fd, 22, SEEK_CUR);
read_uint32be(fd, &frequency);
size -= 26;
id3->frequency = frequency;
/* There're some child atoms here, but we don't need them */
}
break;
case MP4_mdat:
id3->filesize = size;
break;
default:
break;
}
lseek(fd, size, SEEK_CUR);
}
while (rc && (size_left > 0) && (errno == 0) && (id3->filesize == 0));
/* Break on non-zero filesize, since Rockbox currently doesn't support
* metadata after the mdat atom (which sets the filesize field).
*/
return rc;
}
static bool get_mp4_metadata(int fd, struct mp3entry* id3)
{
id3->codectype = AFMT_UNKNOWN;
id3->genre = 255;
id3->filesize = 0;
errno = 0;
if (read_mp4_container(fd, id3, filesize(fd)) && (errno == 0)
&& (id3->samples > 0) && (id3->frequency > 0)
&& (id3->filesize > 0))
{
if (id3->codectype == AFMT_UNKNOWN)
{
logf("Not an ALAC or AAC file");
return false;
}
id3->length = ((int64_t) id3->samples * 1000) / id3->frequency;
if (id3->length <= 0)
{
logf("mp4 length invalid!");
return false;
}
id3->bitrate = ((int64_t) id3->filesize * 8) / id3->length;
DEBUGF("MP4 bitrate %d, frequency %d Hz, length %d ms\n",
id3->bitrate, id3->frequency, id3->length);
}
else
{
logf("MP4 metadata error");
DEBUGF("MP4 metadata error. errno %d, length %d, frequency %d, filesize %d\n",
errno, id3->length, id3->frequency, id3->filesize);
return false;
}
return true;
}
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;
int i;
if (!skip_id3v2(fd, id3))
return false;
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;
/* 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,
"%d.%d dB", track_gain/100, abs(track_gain)%100);
id3->album_gain_string = id3->toc + bufused + 1;
bufused = snprintf(id3->album_gain_string, 45,
"%d.%d dB", album_gain/100, abs(album_gain)%100);
}
} else {
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;
}
id3->vbr = true;
/* Estimate bitrate, we should probably subtract the various header sizes
here for super-accurate results */
id3->length = ((int64_t) samples * 1000) / id3->frequency;
if (id3->length <= 0)
{
logf("mpc length invalid!");
return false;
}
id3->filesize = filesize(fd);
id3->bitrate = id3->filesize * 8 / id3->length;
return true;
}
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;
}
static bool get_adx_metadata(int fd, struct mp3entry* id3)
{
/* Use the trackname part of the id3 structure as a temporary buffer */
unsigned char * buf = id3->path;
int chanstart, channels, read_bytes;
int looping = 0, start_adr = 0, end_adr = 0;
/* try to get the basic header */
if ((lseek(fd, 0, SEEK_SET) < 0)
|| ((read_bytes = read(fd, buf, 0x38)) < 0x38))
{
DEBUGF("lseek or read failed\n");
return false;
}
/* ADX starts with 0x80 */
if (buf[0] != 0x80) {
DEBUGF("get_adx_metadata: wrong first byte %c\n",buf[0]);
return false;
}
/* check for a reasonable offset */
chanstart = ((buf[2] << 8) | buf[3]) + 4;
if (chanstart > 4096) {
DEBUGF("get_adx_metadata: bad chanstart %i\n", chanstart);
return false;
}
/* check for a workable number of channels */
channels = buf[7];
if (channels != 1 && channels != 2) {
DEBUGF("get_adx_metadata: bad channel count %i\n",channels);
return false;
}
id3->frequency = (buf[8] << 24) | (buf[9] << 16) | (buf[10] << 8) | buf[11];
/* 32 samples per 18 bytes */
id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000;
id3->length = ((unsigned long)
(buf[12] << 24) | (buf[13] << 16) | (buf[14] << 8) | buf[15]) /
id3->frequency * 1000;
id3->vbr = false;
id3->filesize = filesize(fd);
/* get loop info */
if (!memcmp(buf+0x10,"\x01\xF4\x03\x00",4)) {
/* Soul Calibur 2 style (type 03) */
DEBUGF("get_adx_metadata: type 03 found\n");
/* check if header is too small for loop data */
if (chanstart-6 < 0x2c) looping=0;
else {
looping = (buf[0x18]) ||
(buf[0x19]) ||
(buf[0x1a]) ||
(buf[0x1b]);
end_adr = (buf[0x28]<<24) |
(buf[0x29]<<16) |
(buf[0x2a]<<8) |
(buf[0x2b]);
start_adr = (
(buf[0x1c]<<24) |
(buf[0x1d]<<16) |
(buf[0x1e]<<8) |
(buf[0x1f])
)/32*channels*18+chanstart;
}
} else if (!memcmp(buf+0x10,"\x01\xF4\x04\x00",4)) {
/* Standard (type 04) */
DEBUGF("get_adx_metadata: type 04 found\n");
/* check if header is too small for loop data */
if (chanstart-6 < 0x38) looping=0;
else {
looping = (buf[0x24]) ||
(buf[0x25]) ||
(buf[0x26]) ||
(buf[0x27]);
end_adr = (buf[0x34]<<24) |
(buf[0x35]<<16) |
(buf[0x36]<<8) |
buf[0x37];
start_adr = (
(buf[0x28]<<24) |
(buf[0x29]<<16) |
(buf[0x2a]<<8) |
(buf[0x2b])
)/32*channels*18+chanstart;
}
} else {
DEBUGF("get_adx_metadata: error, couldn't determine ADX type\n");
return false;
}
if (looping) {
/* 2 loops, 10 second fade */
id3->length = (start_adr-chanstart + 2*(end_adr-start_adr))
*8 / id3->bitrate + 10000;
}
/* try to get the channel header */
if ((lseek(fd, chanstart-6, SEEK_SET) < 0)
|| ((read_bytes = read(fd, buf, 6)) < 6))
{
return false;
}
/* check channel header */
if (memcmp(buf, "(c)CRI", 6) != 0) return false;
return true;
}
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 = ((int64_t) numSampleFrames * 1000) / id3->frequency;
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;
}
#endif /* CONFIG_CODEC == SWCODEC */
/* Simple file type probing by looking at the filename extension. */
unsigned int probe_file_format(const char *filename)
{
char *suffix;
unsigned int i;
suffix = strrchr(filename, '.');
if (suffix == NULL)
{
return AFMT_UNKNOWN;
}
suffix += 1;
for (i = 0; i < sizeof(formats) / sizeof(formats[0]); i++)
{
if (strcasecmp(suffix, formats[i].extension) == 0)
{
return formats[i].format;
}
}
return AFMT_UNKNOWN;
}
/* Get metadata for track - return false if parsing showed problems with the
* file that would prevent playback.
*/
bool get_metadata(struct track_info* track, int fd, const char* trackname,
bool v1first)
{
#if CONFIG_CODEC == SWCODEC
unsigned char* buf;
unsigned long totalsamples;
int i;
#endif
/* Take our best guess at the codec type based on file extension */
track->id3.codectype = probe_file_format(trackname);
/* Load codec specific track tag information and confirm the codec type. */
switch (track->id3.codectype)
{
case AFMT_MPA_L1:
case AFMT_MPA_L2:
case AFMT_MPA_L3:
if (!get_mp3_metadata(fd, &track->id3, trackname, v1first))
{
return false;
}
break;
#if CONFIG_CODEC == SWCODEC
case AFMT_FLAC:
if (!get_flac_metadata(fd, &(track->id3)))
{
return false;
}
break;
case AFMT_MPC:
if (!get_musepack_metadata(fd, &(track->id3)))
return false;
read_ape_tags(fd, &(track->id3));
break;
case AFMT_OGG_VORBIS:
if (!get_vorbis_metadata(fd, &(track->id3)))
{
return false;
}
break;
case AFMT_PCM_WAV:
if (!get_wave_metadata(fd, &(track->id3)))
{
return false;
}
break;
case AFMT_WAVPACK:
/* 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).
*/
/* Use the trackname part of the id3 structure as a temporary buffer */
buf = track->id3.path;
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;
}
}
if (i == 256) {
logf ("%s is not a WavPack file\n", trackname);
return false;
}
track->id3.vbr = true; /* All WavPack files are VBR */
track->id3.filesize = filesize (fd);
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);
}
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;
}
if ((buf[0] != 0x0b) || (buf[1] != 0x77))
{
logf("%s is not an A52/AC3 file\n",trackname);
return false;
}
i = buf[4] & 0x3e;
if (i > 36)
{
logf("A52: Invalid frmsizecod: %d\n",i);
return false;
}
track->id3.bitrate = a52_bitrates[i >> 1];
track->id3.vbr = false;
track->id3.filesize = filesize(fd);
switch (buf[4] & 0xc0)
{
case 0x00:
track->id3.frequency = 48000;
track->id3.bytesperframe=track->id3.bitrate * 2 * 2;
break;
case 0x40:
track->id3.frequency = 44100;
track->id3.bytesperframe = a52_441framesizes[i];
break;
case 0x80:
track->id3.frequency = 32000;
track->id3.bytesperframe = track->id3.bitrate * 3 * 2;
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 */
totalsamples = track->id3.filesize / track->id3.bytesperframe * 6 * 256;
track->id3.length = totalsamples / track->id3.frequency * 1000;
break;
case AFMT_ALAC:
case AFMT_AAC:
if (!get_mp4_metadata(fd, &(track->id3)))
{
return false;
}
break;
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;
case AFMT_SID:
if (!get_sid_metadata(fd, &(track->id3)))
{
return false;
}
break;
case AFMT_ADX:
if (!get_adx_metadata(fd, &(track->id3)))
{
DEBUGF("get_adx_metadata error\n");
return false;
}
break;
case AFMT_AIFF:
if (!get_aiff_metadata(fd, &(track->id3)))
{
return false;
}
break;
#endif /* CONFIG_CODEC == SWCODEC */
default:
/* If we don't know how to read the metadata, assume we can't play
the file */
return false;
break;
}
/* We have successfully read the metadata from the file */
lseek(fd, 0, SEEK_SET);
strncpy(track->id3.path, trackname, sizeof(track->id3.path));
track->taginfo_ready = true;
return true;
}