Fold the mp4 scanner into the aac scanner, fix the playlist issue, and the aac metadata parsing issue
This commit is contained in:
parent
d18632b255
commit
4966f4213e
|
@ -65,7 +65,7 @@ mt_daapd_SOURCES = main.c daapd.h rend.h webserver.c \
|
|||
webserver.h configfile.c configfile.h err.c err.h restart.c restart.h \
|
||||
mp3-scanner.h mp3-scanner.c rend-unix.h \
|
||||
db-generic.c db-generic.h ff-plugins.c ff-plugins.h \
|
||||
rxml.c rxml.h redblack.c redblack.h scan-mp3.c scan-mp4.c scan-aif.c \
|
||||
rxml.c rxml.h redblack.c redblack.h scan-mp3.c scan-aif.c \
|
||||
scan-xml.c scan-wma.c scan-aac.c scan-aac.h scan-wav.c scan-url.c \
|
||||
smart-parser.c smart-parser.h xml-rpc.c xml-rpc.h \
|
||||
os.h ll.c ll.h conf.c conf.h compat.c compat.h util.c util.h \
|
||||
|
|
15
src/io.c
15
src/io.c
|
@ -916,17 +916,22 @@ int io_readline_timeout(IO_PRIVHANDLE *phandle, unsigned char *buf,
|
|||
return TRUE;
|
||||
}
|
||||
if((!ascii) || (to_read != '\r')) {
|
||||
numread += to_read;
|
||||
if(buf[numread-1] == '\n') {
|
||||
buf[numread] = '\0'; /* retain the CR */
|
||||
*len = numread+1;
|
||||
if(buf[numread] == '\n') {
|
||||
buf[numread+1] = '\0'; /* retain the CR */
|
||||
*len = numread + 1;
|
||||
return TRUE;
|
||||
}
|
||||
numread++;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
buf[numread-1] = '\0';
|
||||
*len = numread-1;
|
||||
|
||||
io_err_printf(IO_LOG_LOG,"Buffer too small in io_readline_timeout()\n");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -64,6 +64,7 @@ typedef struct {
|
|||
int (*scanner)(char* file, MP3FILE* pmp3);
|
||||
char *type; /* daap.songformat */
|
||||
char *codectype; /* song.codectype */
|
||||
int has_video; /* hack hack hack */
|
||||
char *description; /* daap.songdescription */
|
||||
} TAGHANDLER;
|
||||
|
||||
|
@ -107,7 +108,6 @@ extern int scan_get_aacinfo(char *filename, MP3FILE *pmp3);
|
|||
extern int scan_get_wavinfo(char *filename, MP3FILE *pmp3);
|
||||
extern int scan_get_urlinfo(char *filename, MP3FILE *pmp3);
|
||||
extern int scan_get_mp3info(char *filename, MP3FILE *pmp3);
|
||||
extern int scan_get_mp4info(char *filename, MP3FILE *pmp3);
|
||||
extern int scan_get_aifinfo(char *filename, MP3FILE *pmp3);
|
||||
|
||||
/* playlist scanners */
|
||||
|
@ -136,31 +136,33 @@ static int scan_static_playlist(char *path);
|
|||
* This system is broken, and won't work with something like a .cue file
|
||||
*/
|
||||
static TAGHANDLER taghandlers[] = {
|
||||
{ "aac", scan_get_aacinfo, "m4a", "mp4a", "AAC audio file" },
|
||||
{ "mp4", scan_get_aacinfo, "m4a", "mp4a", "AAC audio file" },
|
||||
{ "m4b", scan_get_aacinfo, "m4a", "mp4a", "Protected AAC audio file" },
|
||||
{ "m4a", scan_get_aacinfo, "m4a", "mp4a", "AAC audio file" },
|
||||
{ "m4p", scan_get_aacinfo, "m4p", "mp4a", "AAC audio file" },
|
||||
{ "mp3", scan_get_mp3info, "mp3", "mpeg", "MPEG audio file" },
|
||||
{ "wav", scan_get_wavinfo, "wav", "wav", "WAV audio file" },
|
||||
{ "aif", scan_get_aifinfo, "aif", "aif", "AIFF audio file" },
|
||||
{ "aiff",scan_get_aifinfo, "aif", "aif", "AIFF audio file" },
|
||||
{ "wma", scan_get_wmainfo, "wma", "wma", "WMA audio file" },
|
||||
{ "url", scan_get_urlinfo, "pls", NULL, "Playlist URL" },
|
||||
{ "pls", scan_get_urlinfo, "pls", NULL, "Playlist URL" },
|
||||
{ "m4v", scan_get_mp4info, "m4v", "mp4v", "MPEG-4 video file" },
|
||||
{ "mp4", scan_get_mp4info, "m4v", "mp4v", "MPEG-4 video file" },
|
||||
{ "aac", scan_get_aacinfo, "m4a", "mp4a", 0, "AAC audio file" },
|
||||
{ "mp4", scan_get_aacinfo, "m4a", "mp4a", 0, "AAC audio file" },
|
||||
{ "m4b", scan_get_aacinfo, "m4a", "mp4a", 0, "Protected AAC audio file" },
|
||||
{ "m4a", scan_get_aacinfo, "m4a", "mp4a", 0, "AAC audio file" },
|
||||
{ "m4p", scan_get_aacinfo, "m4p", "mp4a", 0, "AAC audio file" },
|
||||
{ "mp3", scan_get_mp3info, "mp3", "mpeg", 0, "MPEG audio file" },
|
||||
{ "wav", scan_get_wavinfo, "wav", "wav", 0, "WAV audio file" },
|
||||
{ "aif", scan_get_aifinfo, "aif", "aif", 0, "AIFF audio file" },
|
||||
{ "aiff",scan_get_aifinfo, "aif", "aif", 0, "AIFF audio file" },
|
||||
{ "wma", scan_get_wmainfo, "wma", "wma", 0, "WMA audio file" },
|
||||
{ "url", scan_get_urlinfo, "pls", NULL, 0, "Playlist URL" },
|
||||
{ "pls", scan_get_urlinfo, "pls", NULL, 0, "Playlist URL" },
|
||||
{ "m4v", scan_get_aacinfo, "m4v", "mp4v", 1, "MPEG-4 video file" },
|
||||
{ "mp4", scan_get_aacinfo, "m4v", "mp4v", 1, "MPEG-4 video file" },
|
||||
{ "mov", scan_get_aacinfo, "m4v", "mp4v", 1, "MPEG-4 video file" },
|
||||
{ "mpeg4", scan_get_aacinfo, "m4v", "mp4v", 1, "MPEG-4 video file" },
|
||||
#ifdef OGGVORBIS
|
||||
{ "ogg", scan_get_ogginfo, "ogg", "ogg", "Ogg Vorbis audio file" },
|
||||
{ "ogg", scan_get_ogginfo, "ogg", "ogg", 0, "Ogg Vorbis audio file" },
|
||||
#endif
|
||||
#ifdef FLAC
|
||||
{ "flac", scan_get_flacinfo, "flac","flac", "FLAC audio file" },
|
||||
{ "fla", scan_get_flacinfo, "flac","flac", "FLAC audio file" },
|
||||
{ "flac", scan_get_flacinfo, "flac","flac", 0, "FLAC audio file" },
|
||||
{ "fla", scan_get_flacinfo, "flac","flac", 0, "FLAC audio file" },
|
||||
#endif
|
||||
#ifdef MUSEPACK
|
||||
{ "mpc", scan_get_mpcinfo, "mpc", "mpc", "Musepack audio file" },
|
||||
{ "mpp", scan_get_mpcinfo, "mpc", "mpc", "Musepack audio file" },
|
||||
{ "mp+", scan_get_mpcinfo, "mpc", "mpc", "Musepack audio file" },
|
||||
{ "mpc", scan_get_mpcinfo, "mpc", "mpc", 0, "Musepack audio file" },
|
||||
{ "mpp", scan_get_mpcinfo, "mpc", "mpc", 0, "Musepack audio file" },
|
||||
{ "mp+", scan_get_mpcinfo, "mpc", "mpc", 0, "Musepack audio file" },
|
||||
#endif
|
||||
{ NULL, NULL, NULL, NULL, NULL }
|
||||
};
|
||||
|
@ -512,8 +514,6 @@ int scan_static_playlist(char *path) {
|
|||
continue;
|
||||
}
|
||||
|
||||
// FIXME - should chomp trailing comments
|
||||
|
||||
ptr = linebuffer;
|
||||
while(*ptr) {
|
||||
if((*ptr == '/') || (*ptr == '\\'))
|
||||
|
@ -522,6 +522,7 @@ int scan_static_playlist(char *path) {
|
|||
}
|
||||
|
||||
// otherwise, assume it is a path
|
||||
// FIXME: Fixups for an absolute path without a drive letter
|
||||
if((linebuffer[0] == PATHSEP) || (linebuffer[1] == ':')) {
|
||||
strcpy(file_path,linebuffer);
|
||||
} else {
|
||||
|
@ -544,8 +545,12 @@ int scan_static_playlist(char *path) {
|
|||
free(perr);
|
||||
}
|
||||
|
||||
len = strlen(linebuffer);
|
||||
len = sizeof(linebuffer);
|
||||
}
|
||||
if(!len)
|
||||
DPRINTF(L_SCAN,E_LOG,"Error reading playlist: %s\n",io_errstr(hfile));
|
||||
else
|
||||
DPRINTF(L_SCAN,E_LOG,"Finished processing playlist. Len: %d\n",len);
|
||||
io_close(hfile);
|
||||
}
|
||||
|
||||
|
@ -762,18 +767,30 @@ int scan_freetags(MP3FILE *pmp3) {
|
|||
|
||||
|
||||
/**
|
||||
* Dispatch to actual file info handlers
|
||||
* Dispatch to actual file info handlers. In addition (and this
|
||||
* is kinda hackish, it pokes has_video into the file if it's marked
|
||||
* as having video in the taghandler. This should really be done in
|
||||
* the metainfo parser, but it's easier to hack m4v up here and leverage
|
||||
* the existing aac parser than add video handling to the parser.
|
||||
* anyone know an easy way to tell if a mpeg4 file has a video stream
|
||||
* or not?
|
||||
*
|
||||
* @param file file to read file metainfo for
|
||||
* @param pmp3 struct to stuff with info gleaned
|
||||
*/
|
||||
int scan_get_info(char *file, MP3FILE *pmp3) {
|
||||
TAGHANDLER *hdl;
|
||||
int retval;
|
||||
|
||||
/* dispatch to appropriate tag handler */
|
||||
hdl = scan_gethandler(pmp3->type);
|
||||
if(hdl && hdl->scanner)
|
||||
return hdl->scanner(file,pmp3);
|
||||
if(hdl && hdl->scanner) {
|
||||
retval = hdl->scanner(file,pmp3);
|
||||
if(retval && hdl->has_video) {
|
||||
pmp3->has_video = 1;
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
|
|
@ -470,7 +470,7 @@ void *os_loadlib(char **pe, char *path) {
|
|||
void *os_libfunc(char **pe, void *handle, char *function) {
|
||||
void *retval;
|
||||
|
||||
if(!(retval = dlsym(handle,function)))
|
||||
if((!(retval = dlsym(handle,function))) && (pe))
|
||||
*pe = strdup(dlerror());
|
||||
|
||||
return retval;
|
||||
|
|
128
src/scan-aac.c
128
src/scan-aac.c
|
@ -79,7 +79,9 @@ uint64_t scan_aac_drilltoatom(IOHANDLE hfile,char *atom_path,
|
|||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Searching for %s\n",atom_path);
|
||||
|
||||
// FIXME: cache io_size for static files
|
||||
io_size(hfile, &file_size);
|
||||
io_setpos(hfile,0,SEEK_SET);
|
||||
|
||||
end_p = atom_path;
|
||||
while (*end_p != '\0') {
|
||||
|
@ -98,7 +100,7 @@ uint64_t scan_aac_drilltoatom(IOHANDLE hfile,char *atom_path,
|
|||
if (atom_offset == -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
io_getpos(hfile,&pos);
|
||||
DPRINTF(E_SPAM,L_SCAN,"Found %s atom at off %lld.\n",
|
||||
atom_name, pos - 8);
|
||||
|
@ -137,25 +139,32 @@ uint64_t scan_aac_drilltoatom(IOHANDLE hfile,char *atom_path,
|
|||
* @param atom_size this will hold the size of the atom found
|
||||
*/
|
||||
uint64_t scan_aac_findatom(IOHANDLE hfile, uint64_t max_offset,
|
||||
char *which_atom, unsigned int *atom_size) {
|
||||
char *which_atom, unsigned int *atom_size) {
|
||||
uint64_t current_offset=0;
|
||||
int size;
|
||||
uint32_t size;
|
||||
char atom[4];
|
||||
uint32_t bytes_read;
|
||||
|
||||
while(current_offset < max_offset) {
|
||||
bytes_read = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)&size,&bytes_read) || (!bytes_read))
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)&size,&bytes_read) || (!bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error parsing file: %s\n",io_errstr(hfile));
|
||||
return -1;
|
||||
|
||||
}
|
||||
|
||||
size=ntohl(size);
|
||||
|
||||
if(size <= 7) /* something not right */
|
||||
if(size <= 7) { /* something not right */
|
||||
DPRINTF(E_LOG,L_SCAN,"Bad aac file: atom length too short searching for %s\n",
|
||||
which_atom);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bytes_read = 4;
|
||||
if(!io_read(hfile,(unsigned char *)atom,&bytes_read) || (!bytes_read))
|
||||
if(!io_read(hfile,(unsigned char *)atom,&bytes_read) || (!bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error parsing file: %s\n",io_errstr(hfile));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(strncasecmp(atom,which_atom,4) == 0) {
|
||||
*atom_size=size;
|
||||
|
@ -166,6 +175,7 @@ uint64_t scan_aac_findatom(IOHANDLE hfile, uint64_t max_offset,
|
|||
current_offset+=size;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Couldn't find atom %s as requested\n",which_atom);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -183,19 +193,19 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
unsigned int atom_length;
|
||||
|
||||
long current_offset=0;
|
||||
int current_size;
|
||||
uint32_t current_size;
|
||||
char current_atom[4];
|
||||
char *current_data;
|
||||
unsigned short us_data;
|
||||
int genre;
|
||||
int len;
|
||||
|
||||
int sample_size;
|
||||
int samples;
|
||||
unsigned int bit_rate;
|
||||
uint32_t sample_size;
|
||||
uint32_t samples;
|
||||
uint32_t bit_rate;
|
||||
int ms;
|
||||
unsigned char buffer[2];
|
||||
int time = 0;
|
||||
uint32_t time = 0;
|
||||
|
||||
|
||||
hfile = io_new();
|
||||
|
@ -213,20 +223,30 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
if(atom_offset != -1) {
|
||||
/* found the tag section - need to walk through now */
|
||||
while(current_offset < (uint64_t)atom_length) {
|
||||
bytes_read = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)¤t_size,&bytes_read) || !bytes_read)
|
||||
break;
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)¤t_size,&bytes_read) || !bytes_read) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading mp4 atoms: %s\n",io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
current_size=ntohl(current_size);
|
||||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Current size: %d\n",current_size);
|
||||
|
||||
if(current_size <= 7) /* something not right */
|
||||
break;
|
||||
if(current_size <= 7) { /* something not right */
|
||||
DPRINTF(E_LOG,L_SCAN,"mp4 atom too small. Bad aac tags?\n");
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
bytes_read = 4;
|
||||
if(!io_read(hfile,(unsigned char *)current_atom,&bytes_read) || !bytes_read)
|
||||
break;
|
||||
if(!io_read(hfile,(unsigned char *)current_atom,&bytes_read) || !bytes_read) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading mp4 atoms: %s\n",io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Current Atom: %c%c%c%c\n",
|
||||
current_atom[0],current_atom[1],current_atom[2],
|
||||
|
@ -235,17 +255,23 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
if(current_size > 4096) { /* Does this break anything? */
|
||||
/* too big! cover art, maybe? */
|
||||
io_setpos(hfile,current_size - 8, SEEK_CUR);
|
||||
DPRINTF(E_SPAM,L_SCAN,"Atom too big... skipping\n");
|
||||
} else {
|
||||
len=current_size-7; /* for ill-formed too-short tags */
|
||||
if(len < 22)
|
||||
if(len < 22) {
|
||||
len=22;
|
||||
}
|
||||
|
||||
current_data=(char*)malloc(len); /* extra byte */
|
||||
memset(current_data,0x00,len);
|
||||
|
||||
bytes_read = current_size - 8;
|
||||
if(!io_read(hfile,(unsigned char *)current_data,&bytes_read) || (!bytes_read))
|
||||
break;
|
||||
if(!io_read(hfile,(unsigned char *)current_data,&bytes_read) || (!bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading mp4 data: %s\n",io_errstr(hfile));
|
||||
free(current_data);
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!memcmp(current_atom,"\xA9" "nam",4)) { /* Song name */
|
||||
pmp3->title=strdup((char*)¤t_data[16]);
|
||||
|
@ -303,8 +329,8 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
}
|
||||
|
||||
free(current_data);
|
||||
current_offset+=current_size;
|
||||
}
|
||||
current_offset+=current_size;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -314,21 +340,43 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
io_setpos(hfile,4,SEEK_CUR);
|
||||
|
||||
/* FIXME: error handling */
|
||||
bytes_read = sizeof(int);
|
||||
io_read(hfile,(unsigned char *)&time, &bytes_read);
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)&time, &bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading time from moov:mvhd: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
time = ntohl(time);
|
||||
pmp3->time_added = (int)scan_aac_mac_to_unix_time(time);
|
||||
|
||||
bytes_read = sizeof(int);
|
||||
io_read(hfile,(unsigned char *)&time, &bytes_read);
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)&time, &bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading time from moov:mvhd: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
time = ntohl(time);
|
||||
pmp3->time_modified = (int)scan_aac_mac_to_unix_time(time);
|
||||
|
||||
bytes_read = sizeof(int);
|
||||
io_read(hfile,(unsigned char *)&sample_size,&bytes_read);
|
||||
bytes_read = sizeof(int);
|
||||
io_read(hfile,(unsigned char*)&samples, &bytes_read);
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)&sample_size,&bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading sample_size from moov:mvhd: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
bytes_read = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char*)&samples, &bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading samples from moov:mvhd: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sample_size=ntohl(sample_size);
|
||||
samples=ntohl(samples);
|
||||
|
@ -341,7 +389,7 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
}
|
||||
|
||||
/* DWB: use ms time instead of sec */
|
||||
pmp3->song_length=(int)((samples * ms) / sample_size);
|
||||
pmp3->song_length=(uint32_t)((samples * ms) / sample_size);
|
||||
DPRINTF(E_DBG,L_SCAN,"Song length: %d seconds\n",
|
||||
pmp3->song_length / 1000);
|
||||
}
|
||||
|
@ -380,7 +428,12 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
* "reserved") though the timescale in the 'mdhd' atom is 4. Not sure
|
||||
* how this is dealt with when sample rate goes higher than 64K. */
|
||||
bytes_read = 2;
|
||||
io_read(hfile, (unsigned char *)buffer, &bytes_read);
|
||||
if(!io_read(hfile, (unsigned char *)buffer, &bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading timescale from drms atom: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pmp3->samplerate = (buffer[0] << 8) | (buffer[1]);
|
||||
|
||||
|
@ -401,7 +454,12 @@ int scan_get_aacinfo(char *filename, MP3FILE *pmp3) {
|
|||
io_setpos(hfile, atom_offset + 22, SEEK_CUR);
|
||||
|
||||
bytes_read = sizeof(unsigned int);
|
||||
io_read(hfile, (unsigned char *)&bit_rate, &bytes_read);
|
||||
if(!io_read(hfile, (unsigned char *)&bit_rate, &bytes_read)) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Error reading bitrate from esds: %s\n",
|
||||
io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pmp3->bitrate = ntohl(bit_rate) / 1000;
|
||||
DPRINTF(E_DBG,L_SCAN,"esds bitrate: %d\n",pmp3->bitrate);
|
||||
|
|
332
src/scan-mp4.c
332
src/scan-mp4.c
|
@ -1,332 +0,0 @@
|
|||
/*
|
||||
* $Id$
|
||||
*
|
||||
* Copyright (C) 2003 Ron Pedde (ron@pedde.com)
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#ifdef HAVE_CONFIG_H
|
||||
# include "config.h"
|
||||
#endif
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#ifdef HAVE_SYS_TIME_H
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
|
||||
#ifndef WIN32
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
|
||||
#include "daapd.h"
|
||||
#include "err.h"
|
||||
#include "io.h"
|
||||
#include "mp3-scanner.h"
|
||||
#include "scan-aac.h"
|
||||
|
||||
/* Forwards */
|
||||
extern time_t scan_aac_mac_to_unix_time(int t);
|
||||
|
||||
|
||||
/* FIXME: This is really just a copy of scan-aac.c
|
||||
* there should really be some mpeg4-specific stuff in here,
|
||||
* but this seems to do a fair job. Should check it against
|
||||
* .mov files.
|
||||
*/
|
||||
|
||||
/**
|
||||
* main mp4 scanning routing.
|
||||
*
|
||||
* @param filename file to scan
|
||||
* @param pmp3 pointer to the MP3FILE to fill with data
|
||||
* @returns FALSE if file should not be added to database, TRUE otherwise
|
||||
*/
|
||||
int scan_get_mp4info(char *filename, MP3FILE *pmp3) {
|
||||
IOHANDLE hfile;
|
||||
uint64_t atom_offset;
|
||||
unsigned int atom_length;
|
||||
|
||||
uint64_t current_offset=0;
|
||||
uint64_t file_pos;
|
||||
uint32_t read_size;
|
||||
|
||||
uint32_t current_size;
|
||||
char current_atom[4];
|
||||
char *current_data;
|
||||
unsigned short us_data;
|
||||
int genre;
|
||||
int len;
|
||||
|
||||
int sample_size;
|
||||
int samples;
|
||||
unsigned int bit_rate;
|
||||
int ms;
|
||||
unsigned char buffer[2];
|
||||
int time = 0;
|
||||
|
||||
|
||||
if(!(hfile = io_new())) {
|
||||
DPRINTF(E_LOG,L_SCAN,"Cannot create file handle\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!io_open(hfile,"file://%U",filename)) {
|
||||
DPRINTF(E_INF,L_SCAN,"Cannot open file %s for reading: %s\n",
|
||||
filename,io_errstr(hfile));
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
atom_offset=scan_aac_drilltoatom(hfile, "moov:udta:meta:ilst", &atom_length);
|
||||
if(atom_offset != -1) {
|
||||
/* found the tag section - need to walk through now */
|
||||
|
||||
while(current_offset < (long) atom_length) {
|
||||
read_size = sizeof(uint32_t);
|
||||
if(!io_read(hfile,(unsigned char *)¤t_size,&read_size) || (read_size != sizeof(uint32_t))) {
|
||||
io_close(hfile);
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Current size: %d\n",current_size);
|
||||
|
||||
current_size=ntohl(current_size);
|
||||
|
||||
if(current_size <= 7) /* something not right */
|
||||
break;
|
||||
|
||||
read_size = 4;
|
||||
if(!io_read(hfile,(unsigned char *)¤t_atom,&read_size) || (read_size != 4)) {
|
||||
io_close(hfile);
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
DPRINTF(E_SPAM,L_SCAN,"Current Atom: %c%c%c%c\n",
|
||||
current_atom[0],current_atom[1],current_atom[2],
|
||||
current_atom[3]);
|
||||
|
||||
if(current_size > 4096) { /* Does this break anything? */
|
||||
/* too big! cover art, maybe? */
|
||||
io_setpos(hfile, current_size - 8, SEEK_CUR);
|
||||
} else {
|
||||
len=current_size-7; /* for ill-formed too-short tags */
|
||||
if(len < 22)
|
||||
len=22;
|
||||
|
||||
current_data=(char*)malloc(len); /* extra byte */
|
||||
memset(current_data,0x00,len);
|
||||
|
||||
read_size = current_size - 8;
|
||||
if(!io_read(hfile,(unsigned char *)current_data,&read_size) || (current_size != 8)) {
|
||||
io_close(hfile);
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(!memcmp(current_atom,"\xA9" "nam",4)) { /* Song name */
|
||||
pmp3->title=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "ART",4)) {
|
||||
pmp3->artist=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "alb",4)) {
|
||||
pmp3->album=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "cmt",4)) {
|
||||
pmp3->comment=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "wrt",4)) {
|
||||
pmp3->composer=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "grp",4)) {
|
||||
pmp3->grouping=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"\xA9" "gen",4)) {
|
||||
/* can this be a winamp genre??? */
|
||||
pmp3->genre=strdup((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"tmpo",4)) {
|
||||
us_data=*((unsigned short *)¤t_data[16]);
|
||||
us_data=ntohs(us_data);
|
||||
pmp3->bpm=us_data;
|
||||
} else if(!memcmp(current_atom,"trkn",4)) {
|
||||
us_data=*((unsigned short *)¤t_data[18]);
|
||||
us_data=ntohs(us_data);
|
||||
|
||||
pmp3->track=us_data;
|
||||
|
||||
us_data=*((unsigned short *)¤t_data[20]);
|
||||
us_data=ntohs(us_data);
|
||||
|
||||
pmp3->total_tracks=us_data;
|
||||
} else if(!memcmp(current_atom,"disk",4)) {
|
||||
us_data=*((unsigned short *)¤t_data[18]);
|
||||
us_data=ntohs(us_data);
|
||||
|
||||
pmp3->disc=us_data;
|
||||
|
||||
us_data=*((unsigned short *)¤t_data[20]);
|
||||
us_data=ntohs(us_data);
|
||||
|
||||
pmp3->total_discs=us_data;
|
||||
} else if(!memcmp(current_atom,"\xA9" "day",4)) {
|
||||
pmp3->year=atoi((char*)¤t_data[16]);
|
||||
} else if(!memcmp(current_atom,"gnre",4)) {
|
||||
genre=(int)(*((char*)¤t_data[17]));
|
||||
genre--;
|
||||
|
||||
if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
|
||||
genre=WINAMP_GENRE_UNKNOWN;
|
||||
|
||||
pmp3->genre=strdup(scan_winamp_genre[genre]);
|
||||
} else if (!memcmp(current_atom, "cpil", 4)) {
|
||||
pmp3->compilation = current_data[16];
|
||||
}
|
||||
|
||||
free(current_data);
|
||||
current_offset+=current_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* got the tag info, now let's get bitrate, etc */
|
||||
atom_offset = scan_aac_drilltoatom(hfile, "moov:mvhd", &atom_length);
|
||||
if(atom_offset != -1) {
|
||||
io_setpos(hfile,4,SEEK_CUR);
|
||||
|
||||
read_size = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)&time, &read_size) || (read_size != sizeof(int))) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
time = ntohl(time);
|
||||
pmp3->time_added = (int) scan_aac_mac_to_unix_time(time);
|
||||
|
||||
read_size = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)&time, &read_size) || (read_size != sizeof(int))) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
time = ntohl(time);
|
||||
pmp3->time_modified = (int) scan_aac_mac_to_unix_time(time);
|
||||
|
||||
read_size = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)&sample_size, &read_size) || (read_size != sizeof(int))) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
read_size = sizeof(int);
|
||||
if(!io_read(hfile,(unsigned char *)&samples, &read_size) || (read_size != sizeof(int))) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
sample_size=ntohl(sample_size);
|
||||
samples=ntohl(samples);
|
||||
|
||||
/* avoid overflowing on large sample_sizes (90000) */
|
||||
ms=1000;
|
||||
while((ms > 9) && (!(sample_size % 10))) {
|
||||
sample_size /= 10;
|
||||
ms /= 10;
|
||||
}
|
||||
|
||||
/* DWB: use ms time instead of sec */
|
||||
pmp3->song_length=(int)((samples * ms) / sample_size);
|
||||
DPRINTF(E_DBG,L_SCAN,"Song length: %d seconds\n",
|
||||
pmp3->song_length / 1000);
|
||||
}
|
||||
|
||||
pmp3->bitrate = 0;
|
||||
|
||||
/* Get the sample rate from the 'mp4a' atom (timescale). This is also
|
||||
found in the 'mdhd' atom which is a bit closer but we need to
|
||||
navigate to the 'mp4a' atom anyways to get to the 'esds' atom. */
|
||||
atom_offset=scan_aac_drilltoatom(hfile,
|
||||
"moov:trak:mdia:minf:stbl:stsd:mp4a",
|
||||
&atom_length);
|
||||
if(atom_offset == -1) {
|
||||
atom_offset=scan_aac_drilltoatom(hfile,
|
||||
"moov:trak:mdia:minf:stbl:stsd:drms",
|
||||
&atom_length);
|
||||
}
|
||||
|
||||
if (atom_offset != -1) {
|
||||
io_setpos(hfile, atom_offset + 32, SEEK_SET);
|
||||
|
||||
/* Timescale here seems to be 2 bytes here (the 2 bytes before it are
|
||||
* "reserved") though the timescale in the 'mdhd' atom is 4. Not sure
|
||||
* how this is dealt with when sample rate goes higher than 64K. */
|
||||
read_size = 2;
|
||||
if(!io_read(hfile,(unsigned char *)&buffer,&read_size) || (read_size != 2)) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pmp3->samplerate = (buffer[0] << 8) | (buffer[1]);
|
||||
|
||||
/* Seek to end of atom. */
|
||||
io_setpos(hfile,2,SEEK_CUR);
|
||||
|
||||
/* Get the bit rate from the 'esds' atom. We are already positioned
|
||||
in the parent atom so just scan ahead. */
|
||||
io_getpos(hfile,&file_pos);
|
||||
atom_offset = scan_aac_findatom(hfile,
|
||||
atom_length-(file_pos-atom_offset),
|
||||
"esds", &atom_length);
|
||||
|
||||
if (atom_offset != -1) {
|
||||
/* Roku Soundbridge seems to believe anything above 320K is
|
||||
* an ALAC encoded m4a. We'll lie on their behalf.
|
||||
*/
|
||||
io_setpos(hfile, atom_offset + 22, SEEK_CUR);
|
||||
|
||||
read_size = sizeof(unsigned int);
|
||||
if(!io_read(hfile,(unsigned char *)&bit_rate, &read_size) || (read_size != sizeof(unsigned int))) {
|
||||
io_dispose(hfile);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
pmp3->bitrate = ntohl(bit_rate) / 1000;
|
||||
DPRINTF(E_DBG,L_SCAN,"esds bitrate: %d\n",pmp3->bitrate);
|
||||
|
||||
if(pmp3->bitrate > 320) {
|
||||
pmp3->bitrate = 320;
|
||||
}
|
||||
} else {
|
||||
DPRINTF(E_DBG,L_SCAN, "Couldn't find 'esds' atom for bit rate.\n");
|
||||
}
|
||||
} else {
|
||||
DPRINTF(E_DBG,L_SCAN, "Couldn't find 'mp4a' atom for sample rate.\n");
|
||||
}
|
||||
|
||||
/* Fallback if we can't find the info in the atoms. */
|
||||
if (pmp3->bitrate == 0) {
|
||||
/* calculate bitrate from song length... Kinda cheesy */
|
||||
DPRINTF(E_DBG,L_SCAN, "Guesstimating bit rate.\n");
|
||||
atom_offset=scan_aac_drilltoatom(hfile,"mdat",&atom_length);
|
||||
if ((atom_offset != -1) && (pmp3->song_length > 1000)) {
|
||||
pmp3->bitrate = atom_length / ((pmp3->song_length / 1000) * 128);
|
||||
}
|
||||
}
|
||||
|
||||
io_dispose(hfile);
|
||||
|
||||
pmp3->has_video=1;
|
||||
|
||||
return TRUE; /* we'll return as much as we got. */
|
||||
}
|
Loading…
Reference in New Issue