1
0
mirror of https://git.code.sf.net/p/minidlna/git synced 2025-01-25 01:26:48 +00:00
minidlna/tagutils/tagutils-aac.c
Justin Maggard 24fb139678 metadata: Use "Album Artist" tag from AAC files.
Add Album Artist AAC metadata parsing contributed by SF user knono549.
Then, change music metadata gathering code to use either Album Artist
or Band as upnp:artist.
2015-02-03 17:22:32 -08:00

414 lines
11 KiB
C

//=========================================================================
// 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, see <http://www.gnu.org/licenses/>.
*/
/*
* This file is derived from mt-daap project.
*/
// _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 = NULL;
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*)&current_size, 1, sizeof(int), fin) != sizeof(int))
break;
current_size = ntohl(current_size);
if(current_size <= 7 || current_size > 1<<24) // 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
if(fread(current_data, 1, current_size - 8, fin) != current_size - 8)
break;
current_data[current_size - 8] = '\0';
if(!memcmp(current_atom, "\xA9" "nam", 4))
psong->title = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "ART", 4) ||
!memcmp(current_atom, "\xA9" "art", 4))
psong->contributor[ROLE_ARTIST] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "alb", 4))
psong->album = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "cmt", 4))
psong->comment = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "aART", 4) ||
!memcmp(current_atom, "aart", 4))
psong->contributor[ROLE_ALBUMARTIST] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "dir", 4))
psong->contributor[ROLE_CONDUCTOR] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "wrt", 4))
psong->contributor[ROLE_COMPOSER] = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "grp", 4))
psong->grouping = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "gen", 4))
psong->genre = strdup((char*)&current_data[16]);
else if(!memcmp(current_atom, "\xA9" "day", 4))
psong->year = atoi((char*)&current_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];
}
else if(!memcmp(current_atom, "covr", 4))
{
psong->image_size = current_size - 8 - 16;
if((psong->image = malloc(psong->image_size)))
memcpy(psong->image, current_data+16, psong->image_size);
else
DPRINTF(E_ERROR, L_SCANNER, "Out of memory [%s]\n", file);
}
free(current_data);
current_data = NULL;
current_offset += current_size;
}
}
fclose(fin);
free(current_data);
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;
}
int
_aac_check_extended_descriptor(FILE *infile)
{
short int i;
unsigned char buf[3];
if( fread((void *)&buf, 1, 3, infile) < 3 )
return -1;
for( i=0; i<3; i++ )
{
if( (buf[i] != 0x80) &&
(buf[i] != 0x81) &&
(buf[i] != 0xFE) )
{
fseek(infile, -3, SEEK_CUR);
return 0;
}
}
return 0;
}
// _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];
aac_object_type_t profile_id = 0;
psong->vbr_scale = -1;
psong->channels = 2; // A "normal" default in case we can't find this information
infile = fopen(file, "rb");
if(!infile)
{
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, 12, SEEK_CUR);
if(fread((void*)&sample_size, 1, sizeof(int), infile) != sizeof(int) ||
fread((void*)&samples, 1, sizeof(int), infile) != sizeof(int))
{
fclose(infile);
return -1;
}
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;
// see if it is aac or alac
atom_offset = _aac_lookforatom(infile, "moov:trak:mdia:minf:stbl:stsd:alac", (unsigned int*)&atom_length);
if(atom_offset != -1) {
fseek(infile, atom_offset + 32, SEEK_SET);
if (fread(buffer, sizeof(unsigned char), 2, infile) == 2)
psong->samplerate = (buffer[0] << 8) | (buffer[1]);
goto bad_esds;
}
// 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);
if(fread(buffer, sizeof(unsigned char), 2, infile) == 2)
psong->samplerate = (buffer[0] << 8) | (buffer[1]);
fseek(infile, 2, SEEK_CUR);
// get bitrate from 'esds'
atom_offset = _aac_findatom(infile, atom_length - (ftell(infile) - atom_offset), "esds", &atom_length);
if(atom_offset != -1)
{
// skip the version number
fseek(infile, atom_offset + 4, SEEK_CUR);
// should be 0x03, to signify the descriptor type (section)
if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x03) || (_aac_check_extended_descriptor(infile) != 0) )
goto bad_esds;
fseek(infile, 4, SEEK_CUR);
if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x04) || (_aac_check_extended_descriptor(infile) != 0) )
goto bad_esds;
fseek(infile, 10, SEEK_CUR); // 10 bytes into section 4 should be average bitrate. max bitrate is 6 bytes in.
if(fread((void *)&bitrate, sizeof(unsigned int), 1, infile))
psong->bitrate = ntohl(bitrate);
if( !fread((void *)&buffer, 1, 1, infile) || (buffer[0] != 0x05) || (_aac_check_extended_descriptor(infile) != 0) )
goto bad_esds;
fseek(infile, 1, SEEK_CUR); // 1 bytes into section 5 should be the setup data
if(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);
}
}
}
bad_esds:
atom_offset = _aac_lookforatom(infile, "mdat", (unsigned int*)&atom_length);
psong->audio_size = atom_length - 8;
psong->audio_offset = atom_offset;
if(!psong->bitrate)
{
/* Dont' scare people with this for now. Could be Apple Lossless?
DPRINTF(E_DEBUG, L_SCANNER, "No 'esds' atom. Guess bitrate. [%s]\n", basename(file)); */
if((atom_offset != -1) && (psong->song_length))
{
psong->bitrate = atom_length * 1000 / psong->song_length / 128;
}
/* If this is an obviously wrong bitrate, try something different */
if((psong->bitrate < 16000) && (psong->song_length > 1000))
{
psong->bitrate = (file_size * 8) / (psong->song_length / 1000);
}
}
//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 )
xasprintf(&(psong->dlna_pn), "AAC_ISO_320");
else if( psong->channels <= 2 && psong->bitrate <= 576000 )
xasprintf(&(psong->dlna_pn), "AAC_ISO");
else if( psong->channels <= 6 && psong->bitrate <= 1440000 )
xasprintf(&(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 [%s]\n", profile_id, basename(file));
break;
}
fclose(infile);
return 0;
}