More flexible MP4 file metadata parser.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11189 a1c6a512-1295-4272-9138-f99709370657
This commit is contained in:
parent
d63d8fedaa
commit
406069467d
1 changed files with 417 additions and 286 deletions
703
apps/metadata.c
703
apps/metadata.c
|
@ -22,6 +22,7 @@
|
|||
#include <ctype.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "errno.h"
|
||||
#include "metadata.h"
|
||||
#include "mp3_playback.h"
|
||||
#include "logf.h"
|
||||
|
@ -40,6 +41,37 @@ enum tagtype { TAGTYPE_APE = 1, TAGTYPE_VORBIS };
|
|||
#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];
|
||||
|
@ -190,28 +222,31 @@ static void convert_endian(void *data, const char *format)
|
|||
}
|
||||
}
|
||||
|
||||
/* read_uint32be() - read an unsigned integer from a big-endian
|
||||
(e.g. Quicktime) file. This is used by the .m4a parser
|
||||
*/
|
||||
/* Read an unsigned 16-bit integer from a big-endian file. */
|
||||
#ifdef ROCKBOX_BIG_ENDIAN
|
||||
#define read_uint32be(fd,buf) read((fd),(buf),4)
|
||||
#define read_uint16be(fd, buf) read((fd), (buf), 2)
|
||||
#else
|
||||
int read_uint32be(int fd, unsigned int* buf) {
|
||||
char tmp;
|
||||
char* p=(char*)buf;
|
||||
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,p,4);
|
||||
if (n==4) {
|
||||
tmp=p[0];
|
||||
p[0]=p[3];
|
||||
p[3]=tmp;
|
||||
tmp=p[2];
|
||||
p[2]=p[1];
|
||||
p[1]=tmp;
|
||||
}
|
||||
|
||||
return(n);
|
||||
n = read(fd, (char*) buf, 4);
|
||||
*buf = betoh32(*buf);
|
||||
return n;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -921,292 +956,388 @@ static bool get_wave_metadata(int fd, struct mp3entry* id3)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool get_m4a_metadata(int fd, struct mp3entry* id3)
|
||||
/* 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 char* buf;
|
||||
unsigned long totalsamples;
|
||||
int i,j,k;
|
||||
size_t n;
|
||||
size_t bytes_remaining;
|
||||
char* id3buf;
|
||||
unsigned int compressedsize;
|
||||
unsigned int sample_count;
|
||||
unsigned int sample_duration;
|
||||
int numentries;
|
||||
int entry_size;
|
||||
int size_remaining;
|
||||
int chunk_len;
|
||||
unsigned char chunk_id[4];
|
||||
int sub_chunk_len;
|
||||
unsigned char sub_chunk_id[4];
|
||||
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;
|
||||
|
||||
/* A simple parser to read vital metadata from an ALAC file.
|
||||
This parser also works for AAC files - they are both stored in
|
||||
a Quicktime M4A container. */
|
||||
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;
|
||||
}
|
||||
|
||||
/* Use the trackname part of the id3 structure as a temporary buffer */
|
||||
buf=id3->path;
|
||||
/* 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;
|
||||
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
if (bytes_read)
|
||||
{
|
||||
(*buffer)[bytes_read] = 0;
|
||||
*dest = *buffer;
|
||||
length = strlen(*buffer) + 1;
|
||||
*buffer_left -= length;
|
||||
*buffer += length;
|
||||
}
|
||||
else
|
||||
{
|
||||
*dest = NULL;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
totalsamples=0;
|
||||
compressedsize=0;
|
||||
/* read the chunks - we stop when we find the mdat chunk and set compressedsize */
|
||||
while (compressedsize==0) {
|
||||
n=read_uint32be(fd,&chunk_len);
|
||||
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);
|
||||
|
||||
// This means it was a 64-bit file, so we have problems.
|
||||
if (chunk_len == 1) {
|
||||
logf("need 64bit support\n");
|
||||
return false;
|
||||
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;
|
||||
}
|
||||
|
||||
n=read(fd,&chunk_id,4);
|
||||
if (n < 4)
|
||||
return false;
|
||||
|
||||
if (memcmp(&chunk_id,"ftyp",4)==0) {
|
||||
/* Check for M4A type */
|
||||
n=read(fd,&chunk_id,4);
|
||||
if ((memcmp(&chunk_id,"M4A ",4)!=0) &&
|
||||
(memcmp(&chunk_id,"mp42",4)!=0)) {
|
||||
logf("Not an M4A file, aborting\n");
|
||||
return false;
|
||||
}
|
||||
/* Skip rest of chunk */
|
||||
lseek(fd, chunk_len - 8 - 4, SEEK_CUR); /* FIXME not 8 */
|
||||
} else if (memcmp(&chunk_id,"moov",4)==0) {
|
||||
size_remaining=chunk_len - 8; /* FIXME not 8 */
|
||||
|
||||
while (size_remaining > 0) {
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
if ((sub_chunk_len < 1) || (sub_chunk_len > size_remaining)) {
|
||||
logf("Strange sub_chunk_len value inside moov: %d (remaining: %d)\n",sub_chunk_len,size_remaining);
|
||||
return false;
|
||||
if (*size > 0)
|
||||
{
|
||||
if (*size > size_left)
|
||||
{
|
||||
size_left = 0;
|
||||
}
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
size_remaining-=8;
|
||||
else
|
||||
{
|
||||
size_left -= *size;
|
||||
}
|
||||
|
||||
*size -= 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
*size = size_left;
|
||||
size_left = 0;
|
||||
}
|
||||
|
||||
return size_left;
|
||||
}
|
||||
|
||||
if (memcmp(&sub_chunk_id,"mvhd",4)==0) {
|
||||
/* We don't need anything from here - skip */
|
||||
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
size_remaining-=(sub_chunk_len-8);
|
||||
} else if (memcmp(&sub_chunk_id,"udta",4)==0) {
|
||||
/* The udta chunk contains the metadata - track, artist, album etc.
|
||||
The format appears to be:
|
||||
udta
|
||||
meta
|
||||
hdlr
|
||||
ilst
|
||||
.nam
|
||||
[rest of tags]
|
||||
free
|
||||
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;
|
||||
|
||||
NOTE: This code was written by examination of some .m4a files
|
||||
produced by iTunes v4.9 - it may not therefore be 100%
|
||||
compliant with all streams. But it should fail gracefully.
|
||||
*/
|
||||
j=(sub_chunk_len-8);
|
||||
size_remaining-=j;
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
j-=8;
|
||||
if (memcmp(&sub_chunk_id,"meta",4)==0) {
|
||||
lseek(fd, 4, SEEK_CUR);
|
||||
j-=4;
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
j-=8;
|
||||
if (memcmp(&sub_chunk_id,"hdlr",4)==0) {
|
||||
lseek(fd, sub_chunk_len - 8, SEEK_CUR);
|
||||
j-=(sub_chunk_len - 8);
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
j-=8;
|
||||
if (memcmp(&sub_chunk_id,"ilst",4)==0) {
|
||||
/* Here are the actual tags. We use the id3v2 300-byte buffer
|
||||
to store the string data */
|
||||
bytes_remaining=sizeof(id3->id3v2buf);
|
||||
id3->genre=255; /* Not every track is the Blues */
|
||||
id3buf=id3->id3v2buf;
|
||||
k=sub_chunk_len-8;
|
||||
j-=k;
|
||||
while (k > 0) {
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
k-=8;
|
||||
if (memcmp(sub_chunk_id,"\251nam",4)==0) {
|
||||
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->title);
|
||||
} else if (memcmp(sub_chunk_id,"\251ART",4)==0) {
|
||||
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->artist);
|
||||
} else if (memcmp(sub_chunk_id,"\251alb",4)==0) {
|
||||
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->album);
|
||||
} else if (memcmp(sub_chunk_id,"\251gen",4)==0) {
|
||||
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->genre_string);
|
||||
} else if (memcmp(sub_chunk_id,"\251day",4)==0) {
|
||||
read_m4a_tag_string(fd,sub_chunk_len-8,&id3buf,&bytes_remaining,&id3->year_string);
|
||||
} else if (memcmp(sub_chunk_id,"trkn",4)==0) {
|
||||
if (sub_chunk_len==0x20) {
|
||||
read(fd,buf,sub_chunk_len-8);
|
||||
id3->tracknum=buf[19];
|
||||
} else {
|
||||
lseek(fd, sub_chunk_len-8,SEEK_CUR);
|
||||
}
|
||||
} else {
|
||||
lseek(fd, sub_chunk_len-8,SEEK_CUR);
|
||||
}
|
||||
k-=(sub_chunk_len-8);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
/* Skip any remaining data in udta chunk */
|
||||
lseek(fd, j, SEEK_CUR);
|
||||
} else if (memcmp(&sub_chunk_id,"trak",4)==0) {
|
||||
/* Format of trak chunk:
|
||||
tkhd
|
||||
mdia
|
||||
mdhd
|
||||
hdlr
|
||||
minf
|
||||
smhd
|
||||
dinf
|
||||
stbl
|
||||
stsd - Samplerate, Samplesize, Numchannels
|
||||
stts - time_to_sample array - RLE'd table containing duration of each block
|
||||
stsz - sample_byte_size array - ?Size in bytes of each compressed block
|
||||
stsc - Seek table related?
|
||||
stco - Seek table related?
|
||||
*/
|
||||
break;
|
||||
|
||||
/* Skip tkhd - not needed */
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
if (memcmp(&sub_chunk_id,"tkhd",4)!=0) {
|
||||
logf("Expecting tkhd\n");
|
||||
return false;
|
||||
}
|
||||
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
size_remaining-=sub_chunk_len;
|
||||
case MP4_trkn:
|
||||
{
|
||||
unsigned short n[2];
|
||||
|
||||
read_mp4_tag(fd, size, (char*) &n, sizeof(n));
|
||||
id3->tracknum = betoh16(n[1]);
|
||||
}
|
||||
break;
|
||||
|
||||
/* Process mdia - skipping possible edts */
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
if (memcmp(&sub_chunk_id,"edts",4)==0) {
|
||||
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
size_remaining-=sub_chunk_len;
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
}
|
||||
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;
|
||||
|
||||
if (memcmp(&sub_chunk_id,"mdia",4)!=0) {
|
||||
logf("Expecting mdia\n");
|
||||
return false;
|
||||
}
|
||||
size_remaining-=sub_chunk_len;
|
||||
j=sub_chunk_len-8;
|
||||
|
||||
while (j > 0) {
|
||||
n=read_uint32be(fd,&sub_chunk_len);
|
||||
n=read(fd,&sub_chunk_id,4);
|
||||
j-=4;
|
||||
if (memcmp(&sub_chunk_id,"minf",4)==0) {
|
||||
j=sub_chunk_len-8;
|
||||
} else if (memcmp(&sub_chunk_id,"stbl",4)==0) {
|
||||
j=sub_chunk_len-8;
|
||||
} else if (memcmp(&sub_chunk_id,"stsd",4)==0) {
|
||||
n=read(fd,buf,sub_chunk_len-8);
|
||||
j-=sub_chunk_len;
|
||||
i=0;
|
||||
/* Skip version and flags */
|
||||
i+=4;
|
||||
|
||||
numentries=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
|
||||
i+=4;
|
||||
if (numentries!=1) {
|
||||
logf("ERROR: Expecting only one entry in stsd\n");
|
||||
}
|
||||
|
||||
entry_size=(buf[i]<<24)|(buf[i+1]<<16)|(buf[i+2]<<8)|buf[i+3];
|
||||
i+=4;
|
||||
|
||||
if (memcmp(&buf[i],"alac",4)==0) {
|
||||
id3->codectype=AFMT_ALAC;
|
||||
} else if (memcmp(&buf[i],"mp4a",4)==0) {
|
||||
id3->codectype=AFMT_AAC;
|
||||
} else {
|
||||
logf("Not an ALAC or AAC file\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
//numchannels=(buf[i+20]<<8)|buf[i+21]; /* Not used - assume Stereo */
|
||||
//samplesize=(buf[i+22]<<8)|buf[i+23]; /* Not used - assume 16-bit */
|
||||
|
||||
/* Samplerate is 32-bit fixed point, but this works for < 65536 Hz */
|
||||
id3->frequency=(buf[i+28]<<8)|buf[i+29];
|
||||
} else if (memcmp(&sub_chunk_id,"stts",4)==0) {
|
||||
j-=sub_chunk_len;
|
||||
i=8;
|
||||
n=read(fd,buf,8);
|
||||
i+=8;
|
||||
numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
|
||||
for (k=0;k<numentries;k++) {
|
||||
n=read_uint32be(fd,&sample_count);
|
||||
n=read_uint32be(fd,&sample_duration);
|
||||
totalsamples+=sample_count*sample_duration;
|
||||
i+=8;
|
||||
}
|
||||
if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
|
||||
} else if (memcmp(&sub_chunk_id,"stsz",4)==0) {
|
||||
j-=sub_chunk_len;
|
||||
i=8;
|
||||
n=read(fd,buf,8);
|
||||
i+=8;
|
||||
numentries=(buf[4]<<24)|(buf[5]<<16)|(buf[6]<<8)|buf[7];
|
||||
for (k=0;k<numentries;k++) {
|
||||
n=read_uint32be(fd,&sample_count);
|
||||
n=read_uint32be(fd,&sample_duration);
|
||||
totalsamples+=sample_count*sample_duration;
|
||||
i+=8;
|
||||
}
|
||||
if (i > 0) lseek(fd, sub_chunk_len - i, SEEK_CUR);
|
||||
} else {
|
||||
lseek(fd, sub_chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
j-=sub_chunk_len;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logf("Unexpected sub_chunk_id inside moov: %c%c%c%c\n",
|
||||
sub_chunk_id[0],sub_chunk_id[1],sub_chunk_id[2],sub_chunk_id[3]);
|
||||
return false;
|
||||
parse_replaygain(tag_name, buffer, id3, buffer,
|
||||
buffer_left);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
lseek(fd, size, SEEK_CUR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (memcmp(&chunk_id,"mdat",4)==0) {
|
||||
/* once we hit mdat we stop reading and return.
|
||||
* this is on the assumption that there is no furhter interesting
|
||||
* stuff in the stream. if there is stuff will fail (:()).
|
||||
* But we need the read pointer to be at the mdat stuff
|
||||
* for the decoder. And we don't want to rely on fseek/ftell,
|
||||
* as they may not always be avilable */
|
||||
lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
compressedsize=chunk_len-8;
|
||||
} else if (memcmp(&chunk_id,"free",4)==0) {
|
||||
/* these following atoms can be skipped !!!! */
|
||||
lseek(fd, chunk_len - 8, SEEK_CUR); /* FIXME not 8 */
|
||||
} else {
|
||||
logf("(top) unknown chunk id: %c%c%c%c\n", chunk_id[0],chunk_id[1],chunk_id[2],chunk_id[3]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
while ((size_left > 0) && (errno == 0));
|
||||
|
||||
id3->vbr=true; /* All ALAC files are VBR */
|
||||
id3->filesize=filesize(fd);
|
||||
id3->samples=totalsamples;
|
||||
id3->length=(10*totalsamples)/(id3->frequency/100);
|
||||
id3->bitrate=(compressedsize*8)/id3->length;;
|
||||
return true;
|
||||
}
|
||||
|
||||
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 = (id3->samples / id3->frequency) * 1000;
|
||||
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)
|
||||
{
|
||||
|
@ -1718,7 +1849,7 @@ bool get_metadata(struct track_info* track, int fd, const char* trackname,
|
|||
|
||||
case AFMT_ALAC:
|
||||
case AFMT_AAC:
|
||||
if (!get_m4a_metadata(fd, &(track->id3)))
|
||||
if (!get_mp4_metadata(fd, &(track->id3)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue