forked from len0rd/rockbox
split up the metadata parser
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@13637 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
c3206a455a
commit
2175d1edf6
18 changed files with 2602 additions and 2149 deletions
|
|
@ -35,6 +35,7 @@ gui/*.[ch]
|
|||
keymaps/*.[ch]
|
||||
lang/*.lang
|
||||
menus/*.[ch]
|
||||
metadata/*.[ch]
|
||||
player/*.[ch]
|
||||
plugins/*.[ch]
|
||||
plugins/*.pl
|
||||
|
|
|
|||
15
apps/SOURCES
15
apps/SOURCES
|
|
@ -106,6 +106,21 @@ eq_arm.S
|
|||
#endif
|
||||
#endif
|
||||
metadata.c
|
||||
#if CONFIG_CODEC == SWCODEC
|
||||
metadata/metadata_common.c
|
||||
metadata/aiff.c
|
||||
metadata/ape.c
|
||||
metadata/adx.c
|
||||
metadata/flac.c
|
||||
metadata/monkeys.c
|
||||
metadata/mp4.c
|
||||
metadata/mpc.c
|
||||
metadata/sid.c
|
||||
metadata/spc.c
|
||||
metadata/speex.c
|
||||
metadata/vorbis.c
|
||||
metadata/wave.c
|
||||
#endif
|
||||
#ifdef HAVE_TAGCACHE
|
||||
tagcache.c
|
||||
#endif
|
||||
|
|
|
|||
2156
apps/metadata.c
2156
apps/metadata.c
File diff suppressed because it is too large
Load diff
115
apps/metadata/adx.c
Normal file
115
apps/metadata/adx.c
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "debug.h"
|
||||
|
||||
bool get_adx_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char * buf = (unsigned char *)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 = get_long_be(&buf[8]);
|
||||
/* 32 samples per 18 bytes */
|
||||
id3->bitrate = id3->frequency * channels * 18 * 8 / 32 / 1000;
|
||||
id3->length = get_long_be(&buf[12]) / 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 = get_long_be(&buf[0x18]);
|
||||
end_adr = get_long_be(&buf[0x28]);
|
||||
start_adr = get_long_be(&buf[0x1c])/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 = get_long_be(&buf[0x24]);
|
||||
end_adr = get_long_be(&buf[0x34]);
|
||||
start_adr = get_long_be(&buf[0x28])/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;
|
||||
}
|
||||
98
apps/metadata/aiff.c
Normal file
98
apps/metadata/aiff.c
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
|
||||
bool get_aiff_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char* buf = (unsigned char *)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))) < 54))
|
||||
{
|
||||
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 = get_long_be(&buf[4]);
|
||||
|
||||
if (memcmp(buf, "COMM", 4) == 0)
|
||||
{
|
||||
/* numChannels */
|
||||
numChannels = ((buf[8]<<8)|buf[9]);
|
||||
/* numSampleFrames */
|
||||
numSampleFrames = get_long_be(&buf[10]);
|
||||
/* sampleSize */
|
||||
sampleSize = ((buf[14]<<8)|buf[15]);
|
||||
/* sampleRate */
|
||||
sampleRate = get_long_be(&buf[18]);
|
||||
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;
|
||||
}
|
||||
132
apps/metadata/ape.c
Normal file
132
apps/metadata/ape.c
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "structec.h"
|
||||
|
||||
#define APETAG_HEADER_LENGTH 32
|
||||
#define APETAG_HEADER_FORMAT "8llll8"
|
||||
#define APETAG_ITEM_HEADER_FORMAT "ll"
|
||||
#define APETAG_ITEM_TYPE_MASK 3
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
bool read_ape_tags(int fd, struct mp3entry* id3)
|
||||
{
|
||||
struct apetag_header header;
|
||||
|
||||
if ((lseek(fd, -APETAG_HEADER_LENGTH, SEEK_END) < 0)
|
||||
|| (ecread(fd, &header, 1, APETAG_HEADER_FORMAT, IS_BIG_ENDIAN) != APETAG_HEADER_LENGTH)
|
||||
|| (memcmp(header.id, "APETAGEX", sizeof(header.id))))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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 (ecread(fd, &item, 1, APETAG_ITEM_HEADER_FORMAT, IS_BIG_ENDIAN) < (long) sizeof(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
122
apps/metadata/flac.c
Normal file
122
apps/metadata/flac.c
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "logf.h"
|
||||
|
||||
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 = (unsigned char *)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 = get_long_be(&buf[14]);
|
||||
|
||||
/* 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;
|
||||
}
|
||||
273
apps/metadata/metadata_common.c
Normal file
273
apps/metadata/metadata_common.c
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "replaygain.h"
|
||||
#include "atoi.h"
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* 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.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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. */
|
||||
unsigned long get_long_le(void* buf)
|
||||
{
|
||||
unsigned char* p = (unsigned char*) buf;
|
||||
|
||||
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
||||
}
|
||||
|
||||
/* Read an unaligned 16-bit little endian short from buffer. */
|
||||
unsigned short get_short_le(void* buf)
|
||||
{
|
||||
unsigned char* p = (unsigned char*) buf;
|
||||
|
||||
return p[0] | (p[1] << 8);
|
||||
}
|
||||
|
||||
/* Read an unaligned 32-bit big endian long from buffer. */
|
||||
unsigned long get_long_be(void* buf)
|
||||
{
|
||||
unsigned char* p = (unsigned char*) buf;
|
||||
|
||||
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
||||
}
|
||||
|
||||
/* Read an unaligned 32-bit little endian long from buffer. */
|
||||
long get_slong(void* buf)
|
||||
{
|
||||
unsigned char* p = (unsigned char*) buf;
|
||||
|
||||
return p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24);
|
||||
}
|
||||
|
||||
static char* skip_space(char* str)
|
||||
{
|
||||
while (isspace(*str))
|
||||
{
|
||||
str++;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
unsigned long get_itunes_int32(char* value, int count)
|
||||
{
|
||||
static const char hexdigits[] = "0123456789ABCDEF";
|
||||
const char* c;
|
||||
int r = 0;
|
||||
|
||||
while (count-- > 0)
|
||||
{
|
||||
value = skip_space(value);
|
||||
|
||||
while (*value && !isspace(*value))
|
||||
{
|
||||
value++;
|
||||
}
|
||||
}
|
||||
|
||||
value = skip_space(value);
|
||||
|
||||
while (*value && ((c = strchr(hexdigits, toupper(*value))) != NULL))
|
||||
{
|
||||
r = (r << 4) | (c - hexdigits);
|
||||
value++;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
/* 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).
|
||||
*/
|
||||
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's can be in any format in Vorbis. However most of them
|
||||
* are in ISO8601 format so if we try and parse the first part
|
||||
* of the tag as a number, we should get the year. If we get crap,
|
||||
* then act like we never parsed it.
|
||||
*/
|
||||
id3->year = atoi(value);
|
||||
if (id3->year < 1900)
|
||||
{ /* yeah, not likely */
|
||||
id3->year = 0;
|
||||
}
|
||||
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 if (strcasecmp(name, "comment") == 0)
|
||||
{
|
||||
p = &(id3->comment);
|
||||
}
|
||||
else if (strcasecmp(name, "albumartist") == 0)
|
||||
{
|
||||
p = &(id3->albumartist);
|
||||
}
|
||||
else if (strcasecmp(name, "album artist") == 0)
|
||||
{
|
||||
p = &(id3->albumartist);
|
||||
}
|
||||
else if (strcasecmp(name, "ensemble") == 0)
|
||||
{
|
||||
p = &(id3->albumartist);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
45
apps/metadata/metadata_common.h
Normal file
45
apps/metadata/metadata_common.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "id3.h"
|
||||
|
||||
#ifdef ROCKBOX_BIG_ENDIAN
|
||||
#define IS_BIG_ENDIAN 1
|
||||
#else
|
||||
#define IS_BIG_ENDIAN 0
|
||||
#endif
|
||||
|
||||
#define TAG_NAME_LENGTH 32
|
||||
#define TAG_VALUE_LENGTH 128
|
||||
|
||||
enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
|
||||
|
||||
bool read_ape_tags(int fd, struct mp3entry* id3);
|
||||
bool read_vorbis_tags(int fd, struct mp3entry *id3,
|
||||
long tag_remaining);
|
||||
|
||||
bool skip_id3v2(int fd, struct mp3entry *id3);
|
||||
long read_string(int fd, char* buf, long buf_size, int eos, long size);
|
||||
int read_uint32be(int fd, unsigned int* buf);
|
||||
unsigned long get_long_le(void* buf);
|
||||
unsigned short get_short_le(void* buf);
|
||||
unsigned long get_long_be(void* buf);
|
||||
long get_slong(void* buf);
|
||||
unsigned long get_itunes_int32(char* value, int count);
|
||||
long parse_tag(const char* name, char* value, struct mp3entry* id3,
|
||||
char* buf, long buf_remaining, enum tagtype type);
|
||||
31
apps/metadata/metadata_parsers.h
Normal file
31
apps/metadata/metadata_parsers.h
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "id3.h"
|
||||
|
||||
bool get_adx_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_aiff_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_flac_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_mp4_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_monkeys_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_musepack_metadata(int fd, struct mp3entry *id3);
|
||||
bool get_sid_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_spc_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_speex_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_vorbis_metadata(int fd, struct mp3entry* id3);
|
||||
bool get_wave_metadata(int fd, struct mp3entry* id3);
|
||||
92
apps/metadata/monkeys.c
Normal file
92
apps/metadata/monkeys.c
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
|
||||
bool get_monkeys_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char* buf = (unsigned char *)id3->path;
|
||||
unsigned char* header;
|
||||
bool rc = false;
|
||||
uint32_t descriptorlength;
|
||||
uint32_t totalsamples;
|
||||
uint32_t blocksperframe, finalframeblocks, totalframes;
|
||||
int fileversion;
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
|
||||
if (read(fd, buf, 4) < 4)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (memcmp(buf, "MAC ", 4) != 0)
|
||||
{
|
||||
return rc;
|
||||
}
|
||||
|
||||
read(fd, buf + 4, MAX_PATH - 4);
|
||||
|
||||
fileversion = get_short_le(buf+4);
|
||||
if (fileversion < 3970)
|
||||
{
|
||||
/* Not supported */
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fileversion >= 3980)
|
||||
{
|
||||
descriptorlength = get_long_le(buf+8);
|
||||
|
||||
header = buf + descriptorlength;
|
||||
|
||||
blocksperframe = get_long_le(header+4);
|
||||
finalframeblocks = get_long_le(header+8);
|
||||
totalframes = get_long_le(header+12);
|
||||
id3->frequency = get_long_le(header+20);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* v3.95 and later files all have a fixed framesize */
|
||||
blocksperframe = 73728 * 4;
|
||||
|
||||
finalframeblocks = get_long_le(buf+28);
|
||||
totalframes = get_long_le(buf+24);
|
||||
id3->frequency = get_long_le(buf+12);
|
||||
}
|
||||
|
||||
id3->vbr = true; /* All FLAC files are VBR */
|
||||
id3->filesize = filesize(fd);
|
||||
|
||||
totalsamples = finalframeblocks;
|
||||
if (totalframes > 1)
|
||||
totalsamples += blocksperframe * (totalframes-1);
|
||||
|
||||
id3->length = ((int64_t) totalsamples * 1000) / id3->frequency;
|
||||
id3->bitrate = (id3->filesize * 8) / id3->length;
|
||||
return true;
|
||||
}
|
||||
669
apps/metadata/mp4.c
Normal file
669
apps/metadata/mp4.c
Normal file
|
|
@ -0,0 +1,669 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "errno.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "logf.h"
|
||||
#include "debug.h"
|
||||
#include "replaygain.h"
|
||||
|
||||
#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_esds MP4_ID('e', 's', 'd', 's')
|
||||
#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_M4B MP4_ID('M', '4', 'B', ' ')
|
||||
#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('-', '-', '-', '-')
|
||||
|
||||
/* Read the tag data from an MP4 file, storing up to buffer_size bytes in
|
||||
* buffer.
|
||||
*/
|
||||
static 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 */
|
||||
static 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 unsigned int read_mp4_length(int fd, unsigned int* size)
|
||||
{
|
||||
unsigned int length = 0;
|
||||
int bytes = 0;
|
||||
unsigned char c;
|
||||
|
||||
do
|
||||
{
|
||||
read(fd, &c, 1);
|
||||
bytes++;
|
||||
(*size)--;
|
||||
length = (length << 7) | (c & 0x7F);
|
||||
}
|
||||
while ((c & 0x80) && (bytes < 4) && (*size > 0));
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static bool read_mp4_esds(int fd, struct mp3entry* id3,
|
||||
unsigned int* size)
|
||||
{
|
||||
unsigned char buf[8];
|
||||
bool sbr = false;
|
||||
|
||||
lseek(fd, 4, SEEK_CUR); /* Version and flags. */
|
||||
read(fd, buf, 1); /* Verify ES_DescrTag. */
|
||||
*size -= 5;
|
||||
|
||||
if (*buf == 3)
|
||||
{
|
||||
/* read length */
|
||||
if (read_mp4_length(fd, size) < 20)
|
||||
{
|
||||
return sbr;
|
||||
}
|
||||
|
||||
lseek(fd, 3, SEEK_CUR);
|
||||
*size -= 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
lseek(fd, 2, SEEK_CUR);
|
||||
*size -= 2;
|
||||
}
|
||||
|
||||
read(fd, buf, 1); /* Verify DecoderConfigDescrTab. */
|
||||
*size -= 1;
|
||||
|
||||
if (*buf != 4)
|
||||
{
|
||||
return sbr;
|
||||
}
|
||||
|
||||
if (read_mp4_length(fd, size) < 13)
|
||||
{
|
||||
return sbr;
|
||||
}
|
||||
|
||||
lseek(fd, 13, SEEK_CUR); /* Skip audio type, bit rates, etc. */
|
||||
read(fd, buf, 1);
|
||||
*size -= 14;
|
||||
|
||||
if (*buf != 5) /* Verify DecSpecificInfoTag. */
|
||||
{
|
||||
return sbr;
|
||||
}
|
||||
|
||||
{
|
||||
static const int sample_rates[] =
|
||||
{
|
||||
96000, 88200, 64000, 48000, 44100, 32000,
|
||||
24000, 22050, 16000, 12000, 11025, 8000
|
||||
};
|
||||
unsigned long bits;
|
||||
unsigned int length;
|
||||
unsigned int index;
|
||||
unsigned int type;
|
||||
|
||||
/* Read the (leading part of the) decoder config. */
|
||||
length = read_mp4_length(fd, size);
|
||||
length = MIN(length, *size);
|
||||
length = MIN(length, sizeof(buf));
|
||||
memset(buf, 0, sizeof(buf));
|
||||
read(fd, buf, length);
|
||||
*size -= length;
|
||||
|
||||
/* Maybe time to write a simple read_bits function... */
|
||||
|
||||
/* Decoder config format:
|
||||
* Object type - 5 bits
|
||||
* Frequency index - 4 bits
|
||||
* Channel configuration - 4 bits
|
||||
*/
|
||||
bits = get_long_be(buf);
|
||||
type = bits >> 27; /* Object type - 5 bits */
|
||||
index = (bits >> 23) & 0xf; /* Frequency index - 4 bits */
|
||||
|
||||
if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
||||
{
|
||||
id3->frequency = sample_rates[index];
|
||||
}
|
||||
|
||||
if (type == 5)
|
||||
{
|
||||
DEBUGF("MP4: SBR\n");
|
||||
unsigned int old_index = index;
|
||||
|
||||
sbr = true;
|
||||
index = (bits >> 15) & 0xf; /* Frequency index - 4 bits */
|
||||
|
||||
if (index == 15)
|
||||
{
|
||||
/* 17 bits read so far... */
|
||||
bits = get_long_be(&buf[2]);
|
||||
id3->frequency = (bits >> 7) & 0x00ffffff;
|
||||
}
|
||||
else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
||||
{
|
||||
id3->frequency = sample_rates[index];
|
||||
}
|
||||
|
||||
if (old_index == index)
|
||||
{
|
||||
/* Downsampled SBR */
|
||||
id3->frequency *= 2;
|
||||
}
|
||||
}
|
||||
/* Skip 13 bits from above, plus 3 bits, then read 11 bits */
|
||||
else if ((length >= 4) && (((bits >> 5) & 0x7ff) == 0x2b7))
|
||||
{
|
||||
/* extensionAudioObjectType */
|
||||
DEBUGF("MP4: extensionAudioType\n");
|
||||
type = bits & 0x1f; /* Object type - 5 bits*/
|
||||
bits = get_long_be(&buf[4]);
|
||||
|
||||
if (type == 5)
|
||||
{
|
||||
sbr = bits >> 31;
|
||||
|
||||
if (sbr)
|
||||
{
|
||||
unsigned int old_index = index;
|
||||
|
||||
/* 1 bit read so far */
|
||||
index = (bits >> 27) & 0xf; /* Frequency index - 4 bits */
|
||||
|
||||
if (index == 15)
|
||||
{
|
||||
/* 5 bits read so far */
|
||||
id3->frequency = (bits >> 3) & 0x00ffffff;
|
||||
}
|
||||
else if (index < (sizeof(sample_rates) / sizeof(*sample_rates)))
|
||||
{
|
||||
id3->frequency = sample_rates[index];
|
||||
}
|
||||
|
||||
if (old_index == index)
|
||||
{
|
||||
/* Downsampled SBR */
|
||||
id3->frequency *= 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!sbr && (id3->frequency <= 24000) && (length <= 2))
|
||||
{
|
||||
/* Double the frequency for low-frequency files without a "long"
|
||||
* DecSpecificConfig header. The file may or may not contain SBR,
|
||||
* but here we guess it does if the header is short. This can
|
||||
* fail on some files, but it's the best we can do, short of
|
||||
* decoding (parts of) the file.
|
||||
*/
|
||||
id3->frequency *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
return sbr;
|
||||
}
|
||||
|
||||
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_string = id3_get_num_genre(betoh16(genre) - 1);
|
||||
}
|
||||
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 if (strcasecmp(tag_name, "iTunSMPB") == 0)
|
||||
{
|
||||
char value[TAG_VALUE_LENGTH];
|
||||
char* value_p = value;
|
||||
char* any;
|
||||
unsigned int length = sizeof(value);
|
||||
|
||||
read_mp4_tag_string(fd, size, &value_p, &length, &any);
|
||||
id3->lead_trim = get_itunes_int32(value, 1);
|
||||
id3->tail_trim = get_itunes_int32(value, 2);
|
||||
DEBUGF("AAC: lead_trim %d, tail_trim %d\n",
|
||||
id3->lead_trim, id3->tail_trim);
|
||||
}
|
||||
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;
|
||||
|
||||
if (parse_replaygain(tag_name, buffer, id3,
|
||||
buffer, buffer_left) > 0)
|
||||
{
|
||||
/* Data used, keep it. */
|
||||
buffer += length;
|
||||
buffer_left -= length;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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_M4B) && (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;
|
||||
|
||||
if (type == MP4_mp4a)
|
||||
{
|
||||
unsigned int subsize;
|
||||
unsigned int subtype;
|
||||
|
||||
/* Get frequency from the decoder info tag, if possible. */
|
||||
lseek(fd, 2, SEEK_CUR);
|
||||
/* The esds atom is a part of the mp4a atom, so ignore
|
||||
* the returned size (it's already accounted for).
|
||||
*/
|
||||
read_mp4_atom(fd, &subsize, &subtype, size);
|
||||
size -= 10;
|
||||
|
||||
if (subtype == MP4_esds)
|
||||
{
|
||||
read_mp4_esds(fd, id3, &size);
|
||||
}
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
bool get_mp4_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
id3->codectype = AFMT_UNKNOWN;
|
||||
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 %ld Hz, length %ld ms\n",
|
||||
id3->bitrate, id3->frequency, id3->length);
|
||||
}
|
||||
else
|
||||
{
|
||||
logf("MP4 metadata error");
|
||||
DEBUGF("MP4 metadata error. errno %d, length %ld, frequency %ld, filesize %ld\n",
|
||||
errno, id3->length, id3->frequency, id3->filesize);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
118
apps/metadata/mpc.c
Normal file
118
apps/metadata/mpc.c
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "logf.h"
|
||||
#include "replaygain.h"
|
||||
|
||||
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 = (char *)id3->toc;
|
||||
bufused = snprintf(id3->track_gain_string, 45,
|
||||
"%d.%d dB", track_gain/100, abs(track_gain)%100);
|
||||
id3->album_gain_string = (char *)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;
|
||||
}
|
||||
88
apps/metadata/sid.c
Normal file
88
apps/metadata/sid.c
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "atoi.h"
|
||||
#include "rbunicode.h"
|
||||
|
||||
/* PSID metadata info is available here:
|
||||
http://www.unusedino.de/ec64/technical/formats/sidplay.html */
|
||||
bool get_sid_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char* buf = (unsigned char *)id3->path;
|
||||
int read_bytes;
|
||||
char *p;
|
||||
|
||||
|
||||
if ((lseek(fd, 0, SEEK_SET) < 0)
|
||||
|| ((read_bytes = read(fd, buf, 0x80)) < 0x80))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((memcmp(buf, "PSID",4) != 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
p = id3->id3v2buf;
|
||||
|
||||
/* Copy Title (assumed max 0x1f letters + 1 zero byte) */
|
||||
id3->title = p;
|
||||
buf[0x16+0x1f] = 0;
|
||||
p = iso_decode(&buf[0x16], p, 0, strlen(&buf[0x16])+1);
|
||||
|
||||
/* Copy Artist (assumed max 0x1f letters + 1 zero byte) */
|
||||
id3->artist = p;
|
||||
buf[0x36+0x1f] = 0;
|
||||
p = iso_decode(&buf[0x36], p, 0, strlen(&buf[0x36])+1);
|
||||
|
||||
/* Copy Year (assumed max 4 letters + 1 zero byte) */
|
||||
buf[0x56+0x4] = 0;
|
||||
id3->year = atoi(&buf[0x56]);
|
||||
|
||||
/* Copy Album (assumed max 0x1f-0x05 letters + 1 zero byte) */
|
||||
id3->album = p;
|
||||
buf[0x56+0x1f] = 0;
|
||||
p = iso_decode(&buf[0x5b], p, 0, strlen(&buf[0x5b])+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
|
||||
*
|
||||
* Note: the number of songs is a 16bit value at 0xE, so this code only
|
||||
* uses the lower 8 bits of the counter.
|
||||
*/
|
||||
id3->length = (buf[0xf]-1)*1000;
|
||||
id3->vbr = false;
|
||||
id3->filesize = filesize(fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
126
apps/metadata/spc.c
Normal file
126
apps/metadata/spc.c
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "debug.h"
|
||||
#include "atoi.h"
|
||||
#include "rbunicode.h"
|
||||
|
||||
bool get_spc_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char * buf = (unsigned char *)id3->path;
|
||||
int read_bytes;
|
||||
char * p;
|
||||
|
||||
unsigned long length;
|
||||
unsigned long fade;
|
||||
bool isbinary = true;
|
||||
int i;
|
||||
|
||||
/* try to get the ID666 tag */
|
||||
if ((lseek(fd, 0x2e, SEEK_SET) < 0)
|
||||
|| ((read_bytes = read(fd, buf, 0xD2)) < 0xD2))
|
||||
{
|
||||
DEBUGF("lseek or read failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
p = id3->id3v2buf;
|
||||
|
||||
id3->title = p;
|
||||
buf[31] = 0;
|
||||
p = iso_decode(buf, p, 0, 32);
|
||||
buf += 32;
|
||||
|
||||
id3->album = p;
|
||||
buf[31] = 0;
|
||||
p = iso_decode(buf, p, 0, 32);
|
||||
buf += 48;
|
||||
|
||||
id3->comment = p;
|
||||
buf[31] = 0;
|
||||
p = iso_decode(buf, p, 0, 32);
|
||||
buf += 32;
|
||||
|
||||
/* Date check */
|
||||
if(buf[2] == '/' && buf[5] == '/')
|
||||
isbinary = false;
|
||||
|
||||
/* Reserved bytes check */
|
||||
if(buf[0xD2 - 0x2E - 112] >= '0' &&
|
||||
buf[0xD2 - 0x2E - 112] <= '9' &&
|
||||
buf[0xD3 - 0x2E - 112] == 0x00)
|
||||
isbinary = false;
|
||||
|
||||
/* is length & fade only digits? */
|
||||
for (i=0;i<8 && (
|
||||
(buf[0xA9 - 0x2E - 112+i]>='0'&&buf[0xA9 - 0x2E - 112+i]<='9') ||
|
||||
buf[0xA9 - 0x2E - 112+i]=='\0');
|
||||
i++);
|
||||
if (i==8) isbinary = false;
|
||||
|
||||
if(isbinary) {
|
||||
id3->year = buf[0] | (buf[1]<<8);
|
||||
buf += 11;
|
||||
|
||||
length = (buf[0] | (buf[1]<<8) | (buf[2]<<16)) * 1000;
|
||||
buf += 3;
|
||||
|
||||
fade = (buf[0] | (buf[1]<<8) | (buf[2]<<16) | (buf[3]<<24));
|
||||
buf += 4;
|
||||
} else {
|
||||
char tbuf[6];
|
||||
|
||||
buf += 6;
|
||||
buf[4] = 0;
|
||||
id3->year = atoi(buf);
|
||||
buf += 5;
|
||||
|
||||
memcpy(tbuf, buf, 3);
|
||||
tbuf[3] = 0;
|
||||
length = atoi(tbuf) * 1000;
|
||||
buf += 3;
|
||||
|
||||
memcpy(tbuf, buf, 5);
|
||||
tbuf[5] = 0;
|
||||
fade = atoi(tbuf);
|
||||
buf += 5;
|
||||
}
|
||||
|
||||
id3->artist = p;
|
||||
buf[31] = 0;
|
||||
p = iso_decode(buf, p, 0, 32);
|
||||
|
||||
if (length==0) {
|
||||
length=3*60*1000; /* 3 minutes */
|
||||
fade=5*1000; /* 5 seconds */
|
||||
}
|
||||
|
||||
id3->length = length+fade;
|
||||
|
||||
return true;
|
||||
}
|
||||
212
apps/metadata/speex.c
Normal file
212
apps/metadata/speex.c
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "logf.h"
|
||||
|
||||
/* A simple parser to read vital metadata from an Ogg Speex file. Returns
|
||||
* false if metadata needed by the Speex codec couldn't be read.
|
||||
*/
|
||||
|
||||
bool get_speex_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 Speex has the following structure:
|
||||
* 1) Identification header (containing samplerate, numchannels, etc)
|
||||
Described in this page: (http://www.speex.org/manual2/node7.html)
|
||||
* 2) Comment header - containing the Vorbis Comments
|
||||
* 3) Many audio packets...
|
||||
*/
|
||||
|
||||
/* Use the path name of the id3 structure as a temporary buffer. */
|
||||
unsigned char* buf = (unsigned char*)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) < 33))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 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_le(&buf[14]);
|
||||
if ((lseek(fd, 33, SEEK_SET) < 0)||(read(fd, buf, 58) < 4))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
id3->frequency = get_slong(&buf[31]);
|
||||
last_serial = get_long_le(&buf[27]);/*temporary, header size*/
|
||||
id3->bitrate = get_long_le(&buf[47]);
|
||||
id3->vbr = get_long_le(&buf[55]);
|
||||
id3->filesize = filesize(fd);
|
||||
/* Comments are in second Ogg page */
|
||||
if (lseek(fd, 28+last_serial/*(temporary for header size)*/, 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;
|
||||
}
|
||||
}
|
||||
|
||||
comment_size = remaining;
|
||||
|
||||
/* 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_le(&buf[i + 6]);
|
||||
last_serial = get_long_le(&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 = (id3->samples / id3->frequency) * 1000;
|
||||
id3->bitrate = (((int64_t) id3->filesize - comment_size) * 8) / id3->length;
|
||||
return true;
|
||||
}
|
||||
330
apps/metadata/vorbis.c
Normal file
330
apps/metadata/vorbis.c
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
#include "metadata_parsers.h"
|
||||
#include "structec.h"
|
||||
#include "logf.h"
|
||||
|
||||
/* Read the items in a Vorbis comment packet. Returns true the items were
|
||||
* fully read, false otherwise.
|
||||
*/
|
||||
bool read_vorbis_tags(int fd, struct mp3entry *id3,
|
||||
long tag_remaining)
|
||||
{
|
||||
char *buf = id3->id3v2buf;
|
||||
int32_t comment_count;
|
||||
int32_t len;
|
||||
int buf_remaining = sizeof(id3->id3v2buf) + sizeof(id3->id3v1buf);
|
||||
int i;
|
||||
|
||||
if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((lseek(fd, len, SEEK_CUR) < 0)
|
||||
|| (ecread(fd, &comment_count, 1, "l", IS_BIG_ENDIAN)
|
||||
< (long) sizeof(comment_count)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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];
|
||||
int32_t read_len;
|
||||
|
||||
if (tag_remaining < 4)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (ecread(fd, &len, 1, "l", IS_BIG_ENDIAN) < (long) sizeof(len))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/* A simple parser to read vital metadata from an Ogg Vorbis file.
|
||||
* Calls get_speex_metadata if a speex file is identified. Returns
|
||||
* false if metadata needed by the Vorbis codec couldn't be read.
|
||||
*/
|
||||
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 = (unsigned char *)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))
|
||||
{
|
||||
if ((memcmp(buf, "OggS", 4) != 0) || (memcmp(&buf[28], "Speex", 5) != 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
id3->codectype = AFMT_SPEEX;
|
||||
return get_speex_metadata(fd, id3);
|
||||
}
|
||||
}
|
||||
|
||||
/* 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_le(&buf[14]);
|
||||
id3->frequency = get_long_le(&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_le(&buf[i + 6]);
|
||||
last_serial = get_long_le(&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;
|
||||
}
|
||||
|
||||
128
apps/metadata/wave.c
Normal file
128
apps/metadata/wave.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/***************************************************************************
|
||||
* __________ __ ___.
|
||||
* 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 "system.h"
|
||||
#include "id3.h"
|
||||
#include "metadata_common.h"
|
||||
|
||||
bool get_wave_metadata(int fd, struct mp3entry* id3)
|
||||
{
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
unsigned char* buf = (unsigned char *)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_le(&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_le(&buf[4]);
|
||||
/* dwAvgBytesPerSec */
|
||||
id3->bitrate = (get_long_le(&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, 4)) < 4)
|
||||
return false;
|
||||
|
||||
i -= 4;
|
||||
totalsamples = get_long_le(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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue