mirror of
https://git.code.sf.net/p/minidlna/git
synced 2025-01-25 01:26:48 +00:00
306 lines
9.3 KiB
C
306 lines
9.3 KiB
C
//=========================================================================
|
|
// FILENAME : tagutils.c
|
|
// DESCRIPTION : MP3/MP4/Ogg/FLAC 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-daapd project */
|
|
|
|
#include "config.h"
|
|
#include <ctype.h>
|
|
#include <errno.h>
|
|
#include <id3tag.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <netinet/in.h>
|
|
#ifdef HAVE_VORBISFILE
|
|
#include <ogg/ogg.h>
|
|
#include <vorbis/codec.h>
|
|
#endif
|
|
#include <FLAC/metadata.h>
|
|
|
|
#ifdef HAVE_ICONV
|
|
#include <iconv.h>
|
|
#endif
|
|
#include <sqlite3.h>
|
|
#include "tagutils.h"
|
|
#include "../metadata.h"
|
|
#include "../utils.h"
|
|
#include "../log.h"
|
|
|
|
struct id3header {
|
|
unsigned char id[3];
|
|
unsigned char version[2];
|
|
unsigned char flags;
|
|
unsigned char size[4];
|
|
} __attribute((packed));
|
|
|
|
char *winamp_genre[] = {
|
|
/*00*/ "Blues", "Classic Rock", "Country", "Dance",
|
|
"Disco", "Funk", "Grunge", "Hip-Hop",
|
|
/*08*/ "Jazz", "Metal", "New Age", "Oldies",
|
|
"Other", "Pop", "R&B", "Rap",
|
|
/*10*/ "Reggae", "Rock", "Techno", "Industrial",
|
|
"Alternative", "Ska", "Death Metal", "Pranks",
|
|
/*18*/ "Soundtrack", "Euro-Techno", "Ambient", "Trip-Hop",
|
|
"Vocal", "Jazz+Funk", "Fusion", "Trance",
|
|
/*20*/ "Classical", "Instrumental", "Acid", "House",
|
|
"Game", "Sound Clip", "Gospel", "Noise",
|
|
/*28*/ "AlternRock", "Bass", "Soul", "Punk",
|
|
"Space", "Meditative", "Instrumental Pop", "Instrumental Rock",
|
|
/*30*/ "Ethnic", "Gothic", "Darkwave", "Techno-Industrial",
|
|
"Electronic", "Pop-Folk", "Eurodance", "Dream",
|
|
/*38*/ "Southern Rock", "Comedy", "Cult", "Gangsta",
|
|
"Top 40", "Christian Rap", "Pop/Funk", "Jungle",
|
|
/*40*/ "Native American", "Cabaret", "New Wave", "Psychedelic",
|
|
"Rave", "Showtunes", "Trailer", "Lo-Fi",
|
|
/*48*/ "Tribal", "Acid Punk", "Acid Jazz", "Polka",
|
|
"Retro", "Musical", "Rock & Roll", "Hard Rock",
|
|
/*50*/ "Folk", "Folk/Rock", "National folk", "Swing",
|
|
"Fast-fusion", "Bebob", "Latin", "Revival",
|
|
/*58*/ "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock",
|
|
"Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
|
|
/*60*/ "Big Band", "Chorus", "Easy Listening", "Acoustic",
|
|
"Humour", "Speech", "Chanson", "Opera",
|
|
/*68*/ "Chamber Music", "Sonata", "Symphony", "Booty Bass",
|
|
"Primus", "Porn Groove", "Satire", "Slow Jam",
|
|
/*70*/ "Club", "Tango", "Samba", "Folklore",
|
|
"Ballad", "Powder Ballad", "Rhythmic Soul", "Freestyle",
|
|
/*78*/ "Duet", "Punk Rock", "Drum Solo", "A Capella",
|
|
"Euro-House", "Dance Hall", "Goa", "Drum & Bass",
|
|
/*80*/ "Club House", "Hardcore", "Terror", "Indie",
|
|
"BritPop", "NegerPunk", "Polsk Punk", "Beat",
|
|
/*88*/ "Christian Gangsta", "Heavy Metal", "Black Metal", "Crossover",
|
|
"Contemporary C", "Christian Rock", "Merengue", "Salsa",
|
|
/*90*/ "Thrash Metal", "Anime", "JPop", "SynthPop",
|
|
"Unknown"
|
|
};
|
|
|
|
#define WINAMP_GENRE_UNKNOWN ((sizeof(winamp_genre) / sizeof(winamp_genre[0])) - 1)
|
|
|
|
|
|
/*
|
|
* Prototype
|
|
*/
|
|
#include "tagutils-mp3.h"
|
|
#include "tagutils-aac.h"
|
|
#ifdef HAVE_VORBISFILE
|
|
#include "tagutils-ogg.h"
|
|
#endif
|
|
#include "tagutils-flc.h"
|
|
#include "tagutils-asf.h"
|
|
#include "tagutils-wav.h"
|
|
#include "tagutils-pcm.h"
|
|
#include "tagutils-dsf.h"
|
|
#include "tagutils-dff.h"
|
|
|
|
static int _get_tags(char *file, struct song_metadata *psong);
|
|
static int _get_fileinfo(char *file, struct song_metadata *psong);
|
|
|
|
|
|
/*
|
|
* Typedefs
|
|
*/
|
|
|
|
typedef struct {
|
|
char* type;
|
|
int (*get_tags)(char* file, struct song_metadata* psong);
|
|
int (*get_fileinfo)(char* file, struct song_metadata* psong);
|
|
} taghandler;
|
|
|
|
static taghandler taghandlers[] = {
|
|
{ "aac", _get_aactags, _get_aacfileinfo },
|
|
{ "mp3", _get_mp3tags, _get_mp3fileinfo },
|
|
{ "flc", _get_flctags, _get_flcfileinfo },
|
|
#ifdef HAVE_VORBISFILE
|
|
{ "ogg", NULL, _get_oggfileinfo },
|
|
#endif
|
|
{ "asf", NULL, _get_asffileinfo },
|
|
{ "wav", _get_wavtags, _get_wavfileinfo },
|
|
{ "pcm", NULL, _get_pcmfileinfo },
|
|
{ "dsf", _get_dsftags, _get_dsffileinfo },
|
|
{ "dff", NULL, _get_dfffileinfo },
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
|
|
|
|
|
|
//*********************************************************************************
|
|
#include "tagutils-misc.c"
|
|
#include "tagutils-mp3.c"
|
|
#include "tagutils-aac.c"
|
|
#ifdef HAVE_VORBISFILE
|
|
#include "tagutils-ogg.c"
|
|
#endif
|
|
#include "tagutils-flc.c"
|
|
#include "tagutils-asf.c"
|
|
#include "tagutils-wav.c"
|
|
#include "tagutils-pcm.c"
|
|
#include "tagutils-plist.c"
|
|
#include "tagutils-dsf.c"
|
|
#include "tagutils-dff.c"
|
|
|
|
//*********************************************************************************
|
|
// freetags()
|
|
#define MAYBEFREE(a) { if((a)) free((a)); };
|
|
void
|
|
freetags(struct song_metadata *psong)
|
|
{
|
|
int role;
|
|
|
|
MAYBEFREE(psong->path);
|
|
MAYBEFREE(psong->image);
|
|
MAYBEFREE(psong->title);
|
|
MAYBEFREE(psong->album);
|
|
MAYBEFREE(psong->genre);
|
|
MAYBEFREE(psong->comment);
|
|
for(role = ROLE_START; role <= ROLE_LAST; role++)
|
|
{
|
|
MAYBEFREE(psong->contributor[role]);
|
|
MAYBEFREE(psong->contributor_sort[role]);
|
|
}
|
|
MAYBEFREE(psong->grouping);
|
|
MAYBEFREE(psong->mime);
|
|
MAYBEFREE(psong->dlna_pn);
|
|
MAYBEFREE(psong->tagversion);
|
|
MAYBEFREE(psong->musicbrainz_albumid);
|
|
MAYBEFREE(psong->musicbrainz_trackid);
|
|
MAYBEFREE(psong->musicbrainz_artistid);
|
|
MAYBEFREE(psong->musicbrainz_albumartistid);
|
|
}
|
|
|
|
// _get_fileinfo
|
|
static int
|
|
_get_fileinfo(char *file, struct song_metadata *psong)
|
|
{
|
|
taghandler *hdl;
|
|
|
|
// dispatch to appropriate tag handler
|
|
for(hdl = taghandlers; hdl->type; ++hdl)
|
|
if(!strcmp(hdl->type, psong->type))
|
|
break;
|
|
|
|
if(hdl->get_fileinfo)
|
|
return hdl->get_fileinfo(file, psong);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void
|
|
_make_composite_tags(struct song_metadata *psong)
|
|
{
|
|
int len;
|
|
|
|
len = 1;
|
|
|
|
if(!psong->contributor[ROLE_ARTIST] &&
|
|
(psong->contributor[ROLE_BAND] || psong->contributor[ROLE_CONDUCTOR]))
|
|
{
|
|
if(psong->contributor[ROLE_BAND])
|
|
len += strlen(psong->contributor[ROLE_BAND]);
|
|
if(psong->contributor[ROLE_CONDUCTOR])
|
|
len += strlen(psong->contributor[ROLE_CONDUCTOR]);
|
|
|
|
len += 3;
|
|
|
|
psong->contributor[ROLE_ARTIST] = (char*)calloc(len, 1);
|
|
if(psong->contributor[ROLE_ARTIST])
|
|
{
|
|
if(psong->contributor[ROLE_BAND])
|
|
strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_BAND]);
|
|
|
|
if(psong->contributor[ROLE_BAND] && psong->contributor[ROLE_CONDUCTOR])
|
|
strcat(psong->contributor[ROLE_ARTIST], " - ");
|
|
|
|
if(psong->contributor[ROLE_CONDUCTOR])
|
|
strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_CONDUCTOR]);
|
|
}
|
|
}
|
|
|
|
#if 0 // already taken care of by scanner.c
|
|
if(!psong->title)
|
|
{
|
|
char *suffix;
|
|
psong->title = strdup(psong->basename);
|
|
suffix = strrchr(psong->title, '.');
|
|
if(suffix) *suffix = '\0';
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
// _get_tags
|
|
static int
|
|
_get_tags(char *file, struct song_metadata *psong)
|
|
{
|
|
taghandler *hdl;
|
|
|
|
// dispatch
|
|
for(hdl = taghandlers ; hdl->type ; ++hdl)
|
|
if(!strcasecmp(hdl->type, psong->type))
|
|
break;
|
|
|
|
if(hdl->get_tags)
|
|
{
|
|
return hdl->get_tags(file, psong);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*****************************************************************************/
|
|
// readtags
|
|
int
|
|
readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type)
|
|
{
|
|
char *fname;
|
|
|
|
if(lang_index == -1)
|
|
lang_index = _lang2cp(lang);
|
|
|
|
memset((void*)psong, 0, sizeof(struct song_metadata));
|
|
psong->path = strdup(path);
|
|
psong->type = type;
|
|
|
|
fname = strrchr(psong->path, '/');
|
|
psong->basename = fname ? fname + 1 : psong->path;
|
|
|
|
if(stat)
|
|
{
|
|
if(!psong->time_modified)
|
|
psong->time_modified = stat->st_mtime;
|
|
psong->file_size = stat->st_size;
|
|
}
|
|
|
|
// get tag
|
|
if( _get_tags(path, psong) == 0 )
|
|
{
|
|
_make_composite_tags(psong);
|
|
}
|
|
|
|
// get fileinfo
|
|
return _get_fileinfo(path, psong);
|
|
}
|