* Use internal music metadata functions intead of taglib.
1) Taglib does not support MP4 or WMA/ASF without hacking it in there. 2) Taglib is C++, so it's nice to remove that dependency. * Use embedded album art where available.
This commit is contained in:
368
tagutils/tagutils-aac.c
Normal file
368
tagutils/tagutils-aac.c
Normal file
@ -0,0 +1,368 @@
|
||||
//=========================================================================
|
||||
// FILENAME : tagutils-aac.c
|
||||
// DESCRIPTION : AAC metadata reader
|
||||
//=========================================================================
|
||||
// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
|
||||
//=========================================================================
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is derived from mt-daap project.
|
||||
*/
|
||||
|
||||
// _mac_to_unix_time
|
||||
static time_t
|
||||
_mac_to_unix_time(int t)
|
||||
{
|
||||
struct timeval tv;
|
||||
struct timezone tz;
|
||||
|
||||
gettimeofday(&tv, &tz);
|
||||
|
||||
return (t - (365L * 66L * 24L * 60L * 60L + 17L * 60L * 60L * 24L) +
|
||||
(tz.tz_minuteswest * 60));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// _aac_findatom:
|
||||
static long
|
||||
_aac_findatom(FILE *fin, long max_offset, char *which_atom, int *atom_size)
|
||||
{
|
||||
long current_offset = 0;
|
||||
int size;
|
||||
char atom[4];
|
||||
|
||||
while(current_offset < max_offset)
|
||||
{
|
||||
if(fread((void*)&size, 1, sizeof(int), fin) != sizeof(int))
|
||||
return -1;
|
||||
|
||||
size = ntohl(size);
|
||||
|
||||
if(size <= 7)
|
||||
return -1;
|
||||
|
||||
if(fread(atom, 1, 4, fin) != 4)
|
||||
return -1;
|
||||
|
||||
if(strncasecmp(atom, which_atom, 4) == 0)
|
||||
{
|
||||
*atom_size = size;
|
||||
return current_offset;
|
||||
}
|
||||
|
||||
fseek(fin, size - 8, SEEK_CUR);
|
||||
current_offset += size;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// _get_aactags
|
||||
static int
|
||||
_get_aactags(char *file, struct song_metadata *psong)
|
||||
{
|
||||
FILE *fin;
|
||||
long atom_offset;
|
||||
unsigned int atom_length;
|
||||
|
||||
long current_offset = 0;
|
||||
int current_size;
|
||||
char current_atom[4];
|
||||
char *current_data;
|
||||
int genre;
|
||||
int len;
|
||||
|
||||
if(!(fin = fopen(file, "rb")))
|
||||
{
|
||||
DPRINTF(E_ERROR, L_SCANNER, "Cannot open file %s for reading\n", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(fin, 0, SEEK_SET);
|
||||
|
||||
atom_offset = _aac_lookforatom(fin, "moov:udta:meta:ilst", &atom_length);
|
||||
if(atom_offset != -1)
|
||||
{
|
||||
while(current_offset < atom_length)
|
||||
{
|
||||
if(fread((void*)¤t_size, 1, sizeof(int), fin) != sizeof(int))
|
||||
break;
|
||||
|
||||
current_size = ntohl(current_size);
|
||||
|
||||
if(current_size <= 7) // something not right
|
||||
break;
|
||||
|
||||
if(fread(current_atom, 1, 4, fin) != 4)
|
||||
break;
|
||||
|
||||
len = current_size - 7; // too short
|
||||
if(len < 22)
|
||||
len = 22;
|
||||
|
||||
current_data = (char*)malloc(len); // extra byte
|
||||
memset(current_data, 0x00, len);
|
||||
|
||||
if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
|
||||
break;
|
||||
|
||||
if(!memcmp(current_atom, "\xA9" "nam", 4))
|
||||
psong->title = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
|
||||
!memcmp(current_atom, "\xA9" "art", 4))
|
||||
psong->contributor[ROLE_ARTIST] = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "alb", 4))
|
||||
psong->album = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "cmt", 4))
|
||||
psong->comment = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "dir", 4))
|
||||
psong->contributor[ROLE_CONDUCTOR] = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "wrt", 4))
|
||||
psong->contributor[ROLE_COMPOSER] = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "grp", 4))
|
||||
psong->grouping = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "gen", 4))
|
||||
psong->genre = strdup((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "\xA9" "day", 4))
|
||||
psong->year = atoi((char*)¤t_data[16]);
|
||||
else if(!memcmp(current_atom, "tmpo", 4))
|
||||
psong->bpm = (current_data[16] << 8) | current_data[17];
|
||||
else if(!memcmp(current_atom, "trkn", 4))
|
||||
{
|
||||
psong->track = (current_data[18] << 8) | current_data[19];
|
||||
psong->total_tracks = (current_data[20] << 8) | current_data[21];
|
||||
}
|
||||
else if(!memcmp(current_atom, "disk", 4))
|
||||
{
|
||||
psong->disc = (current_data[18] << 8) | current_data[19];
|
||||
psong->total_discs = (current_data[20] << 8) | current_data[21];
|
||||
}
|
||||
else if(!memcmp(current_atom, "gnre", 4))
|
||||
{
|
||||
genre = current_data[17] - 1;
|
||||
if((genre < 0) || (genre > WINAMP_GENRE_UNKNOWN))
|
||||
genre = WINAMP_GENRE_UNKNOWN;
|
||||
psong->genre = strdup(winamp_genre[genre]);
|
||||
}
|
||||
else if(!memcmp(current_atom, "cpil", 4))
|
||||
{
|
||||
psong->compilation = current_data[16];
|
||||
}
|
||||
|
||||
free(current_data);
|
||||
current_offset += current_size;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fin);
|
||||
|
||||
if(atom_offset == -1)
|
||||
return -1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// aac_lookforatom
|
||||
static off_t
|
||||
_aac_lookforatom(FILE *aac_fp, char *atom_path, unsigned int *atom_length)
|
||||
{
|
||||
long atom_offset;
|
||||
off_t file_size;
|
||||
char *cur_p, *end_p;
|
||||
char atom_name[5];
|
||||
|
||||
fseek(aac_fp, 0, SEEK_END);
|
||||
file_size = ftell(aac_fp);
|
||||
rewind(aac_fp);
|
||||
|
||||
end_p = atom_path;
|
||||
while(*end_p != '\0')
|
||||
{
|
||||
end_p++;
|
||||
}
|
||||
atom_name[4] = '\0';
|
||||
cur_p = atom_path;
|
||||
|
||||
while(cur_p)
|
||||
{
|
||||
if((end_p - cur_p) < 4)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
strncpy(atom_name, cur_p, 4);
|
||||
atom_offset = _aac_findatom(aac_fp, file_size, atom_name, (int*)atom_length);
|
||||
if(atom_offset == -1)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
cur_p = strchr(cur_p, ':');
|
||||
if(cur_p != NULL)
|
||||
{
|
||||
cur_p++;
|
||||
|
||||
if(!strcmp(atom_name, "meta"))
|
||||
{
|
||||
fseek(aac_fp, 4, SEEK_CUR);
|
||||
}
|
||||
else if(!strcmp(atom_name, "stsd"))
|
||||
{
|
||||
fseek(aac_fp, 8, SEEK_CUR);
|
||||
}
|
||||
else if(!strcmp(atom_name, "mp4a"))
|
||||
{
|
||||
fseek(aac_fp, 28, SEEK_CUR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return position of 'size:atom'
|
||||
return ftell(aac_fp) - 8;
|
||||
}
|
||||
|
||||
// _get_aacfileinfo
|
||||
int
|
||||
_get_aacfileinfo(char *file, struct song_metadata *psong)
|
||||
{
|
||||
FILE *infile;
|
||||
long atom_offset;
|
||||
int atom_length;
|
||||
int sample_size;
|
||||
int samples;
|
||||
unsigned int bitrate;
|
||||
off_t file_size;
|
||||
int ms;
|
||||
unsigned char buffer[2];
|
||||
int time = 0;
|
||||
aac_object_type_t profile_id = 0;
|
||||
|
||||
psong->vbr_scale = -1;
|
||||
|
||||
if(!(infile = fopen(file, "rb")))
|
||||
{
|
||||
DPRINTF(E_ERROR, L_SCANNER, "Could not open %s for reading\n", file);
|
||||
return -1;
|
||||
}
|
||||
|
||||
fseek(infile, 0, SEEK_END);
|
||||
file_size = ftell(infile);
|
||||
fseek(infile, 0, SEEK_SET);
|
||||
|
||||
// move to 'mvhd' atom
|
||||
atom_offset = _aac_lookforatom(infile, "moov:mvhd", (unsigned int*)&atom_length);
|
||||
if(atom_offset != -1)
|
||||
{
|
||||
fseek(infile, 8, SEEK_CUR);
|
||||
fread((void *)&time, sizeof(int), 1, infile);
|
||||
time = ntohl(time);
|
||||
// slimserver prefer to use filesystem time
|
||||
//psong->time_modified = _mac_to_unix_time(time);
|
||||
fread((void*)&sample_size, 1, sizeof(int), infile);
|
||||
fread((void*)&samples, 1, sizeof(int), infile);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// unit = ms
|
||||
psong->song_length = (int)((samples * ms) / sample_size);
|
||||
}
|
||||
|
||||
psong->bitrate = 0;
|
||||
|
||||
// get samplerate from 'mp4a' (not from 'mdhd')
|
||||
atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:mp4a", (unsigned int*)&atom_length);
|
||||
if(atom_offset != -1)
|
||||
{
|
||||
fseek(infile, atom_offset + 32, SEEK_SET);
|
||||
|
||||
fread(buffer, sizeof(unsigned char), 2, infile);
|
||||
|
||||
psong->samplerate = (buffer[0] << 8) | (buffer[1]);
|
||||
|
||||
fseek(infile, 2, SEEK_CUR);
|
||||
|
||||
// get bitrate fomr 'esds'
|
||||
atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
|
||||
|
||||
if(atom_offset != -1)
|
||||
{
|
||||
fseek(infile, atom_offset + 26, SEEK_CUR); // +22 is max bitrate, +26 is average bitrate
|
||||
fread((void *)&bitrate, sizeof(unsigned int), 1, infile);
|
||||
psong->bitrate = ntohl(bitrate);
|
||||
|
||||
fseek(infile, 5, SEEK_CUR); // 5 bytes past bitrate is setup data
|
||||
fread((void *)&buffer, 2, 1, infile);
|
||||
profile_id = (buffer[0] >> 3); // first 5 bits of setup data is the Audo Profile ID
|
||||
/* Frequency index: (((buffer[0] & 0x7) << 1) | (buffer[1] >> 7))) */
|
||||
samples = ((buffer[1] >> 3) & 0xF);
|
||||
psong->channels = (samples == 7 ? 8 : samples);
|
||||
}
|
||||
}
|
||||
|
||||
atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
|
||||
psong->audio_size = atom_length - 8;
|
||||
psong->audio_offset = atom_offset;
|
||||
|
||||
if(!psong->bitrate)
|
||||
{
|
||||
DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate.\n");
|
||||
if((atom_offset != -1) && (psong->song_length))
|
||||
{
|
||||
psong->bitrate = atom_length * 1000 / psong->song_length / 128;
|
||||
}
|
||||
}
|
||||
|
||||
//DPRINTF(E_DEBUG, L_METADATA, "Profile ID: %u\n", profile_id);
|
||||
switch( profile_id )
|
||||
{
|
||||
case AAC_LC:
|
||||
case AAC_LC_ER:
|
||||
if( psong->samplerate < 8000 || psong->samplerate > 48000 )
|
||||
{
|
||||
DPRINTF(E_DEBUG, L_METADATA, "Unsupported AAC: sample rate is not 8000 < %d < 48000\n",
|
||||
psong->samplerate);
|
||||
break;
|
||||
}
|
||||
/* AAC @ Level 1/2 */
|
||||
if( psong->channels <= 2 && psong->bitrate <= 320000 )
|
||||
asprintf(&(psong->dlna_pn), "AAC_ISO_320");
|
||||
else if( psong->channels <= 2 && psong->bitrate <= 576000 )
|
||||
asprintf(&(psong->dlna_pn), "AAC_ISO");
|
||||
else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
|
||||
asprintf(&(psong->dlna_pn), "AAC_MULT5_ISO");
|
||||
else
|
||||
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC: %d channels, %d bitrate\n",
|
||||
psong->channels, psong->bitrate);
|
||||
break;
|
||||
default:
|
||||
DPRINTF(E_DEBUG, L_METADATA, "Unhandled AAC type [%d]\n", profile_id);
|
||||
break;
|
||||
}
|
||||
|
||||
fclose(infile);
|
||||
return 0;
|
||||
}
|
Reference in New Issue
Block a user