1//=========================================================================
2// FILENAME	: tagutils.c
3// DESCRIPTION	: MP3/MP4/Ogg/FLAC metadata reader
4//=========================================================================
5// Copyright (c) 2008- NETGEAR, Inc. All Rights Reserved.
6//=========================================================================
7
8/* This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21 */
22
23/* This file is derived from mt-daapd project */
24
25#include <ctype.h>
26#include <errno.h>
27#include <id3tag.h>
28#include <stdlib.h>
29#include <stddef.h>
30#include <string.h>
31#include <fcntl.h>
32#include <unistd.h>
33#include <time.h>
34#include <sys/time.h>
35#include <netinet/in.h>
36#include <ogg/ogg.h>
37#include <vorbis/codec.h>
38#include <FLAC/metadata.h>
39
40#include "../config.h"
41#ifdef HAVE_ICONV_H
42#include <iconv.h>
43#endif
44
45#include <sqlite3.h>
46#include "tagutils.h"
47#include "misc.h"
48#include "textutils.h"
49#include "../metadata.h"
50#include "../log.h"
51
52struct id3header {
53	unsigned char id[3];
54	unsigned char version[2];
55	unsigned char flags;
56	unsigned char size[4];
57} __attribute((packed));
58
59char *winamp_genre[] = {
60	/*00*/ "Blues",             "Classic Rock",     "Country",           "Dance",
61	       "Disco",             "Funk",             "Grunge",            "Hip-Hop",
62	/*08*/ "Jazz",              "Metal",            "New Age",           "Oldies",
63	       "Other",             "Pop",              "R&B",               "Rap",
64	/*10*/ "Reggae",            "Rock",             "Techno",            "Industrial",
65	       "Alternative",       "Ska",              "Death Metal",       "Pranks",
66	/*18*/ "Soundtrack",        "Euro-Techno",	"Ambient",           "Trip-Hop",
67	       "Vocal",             "Jazz+Funk",        "Fusion",            "Trance",
68	/*20*/ "Classical",         "Instrumental",     "Acid",              "House",
69	       "Game",              "Sound Clip",       "Gospel",            "Noise",
70	/*28*/ "AlternRock",        "Bass",             "Soul",              "Punk",
71	       "Space",             "Meditative",       "Instrumental Pop",  "Instrumental Rock",
72	/*30*/ "Ethnic",            "Gothic",		"Darkwave",          "Techno-Industrial",
73	       "Electronic",        "Pop-Folk",         "Eurodance",         "Dream",
74	/*38*/ "Southern Rock",     "Comedy",           "Cult",              "Gangsta",
75	       "Top 40",            "Christian Rap",    "Pop/Funk",          "Jungle",
76	/*40*/ "Native American",   "Cabaret",          "New Wave",          "Psychadelic",
77	       "Rave",              "Showtunes",        "Trailer",           "Lo-Fi",
78	/*48*/ "Tribal",            "Acid Punk",        "Acid Jazz",         "Polka",
79	       "Retro",             "Musical",          "Rock & Roll",       "Hard Rock",
80	/*50*/ "Folk",              "Folk/Rock",        "National folk",     "Swing",
81	       "Fast-fusion",       "Bebob",            "Latin",             "Revival",
82	/*58*/ "Celtic",            "Bluegrass",        "Avantgarde",        "Gothic Rock",
83	       "Progressive Rock",  "Psychedelic Rock", "Symphonic Rock",    "Slow Rock",
84	/*60*/ "Big Band",          "Chorus",           "Easy Listening",    "Acoustic",
85	       "Humour",            "Speech",           "Chanson",           "Opera",
86	/*68*/ "Chamber Music",     "Sonata",           "Symphony",          "Booty Bass",
87	       "Primus",            "Porn Groove",      "Satire",            "Slow Jam",
88	/*70*/ "Club",              "Tango",            "Samba",             "Folklore",
89	       "Ballad",            "Powder Ballad",    "Rhythmic Soul",     "Freestyle",
90	/*78*/ "Duet",              "Punk Rock",        "Drum Solo",         "A Capella",
91	       "Euro-House",        "Dance Hall",       "Goa",               "Drum & Bass",
92	/*80*/ "Club House",        "Hardcore",         "Terror",            "Indie",
93	       "BritPop",           "NegerPunk",        "Polsk Punk",        "Beat",
94	/*88*/ "Christian Gangsta", "Heavy Metal",      "Black Metal",       "Crossover",
95	       "Contemporary C",    "Christian Rock",   "Merengue",          "Salsa",
96	/*90*/ "Thrash Metal",      "Anime",            "JPop",              "SynthPop",
97	       "Unknown"
98};
99
100#define WINAMP_GENRE_UNKNOWN ((sizeof(winamp_genre) / sizeof(winamp_genre[0])) - 1)
101
102
103/*
104 * Prototype
105 */
106#include "tagutils-mp3.h"
107#include "tagutils-aac.h"
108#include "tagutils-ogg.h"
109#include "tagutils-flc.h"
110#include "tagutils-asf.h"
111#include "tagutils-wav.h"
112#include "tagutils-pcm.h"
113
114static int _get_tags(char *file, struct song_metadata *psong);
115static int _get_fileinfo(char *file, struct song_metadata *psong);
116
117
118/*
119 * Typedefs
120 */
121
122typedef struct {
123	char* type;
124	int (*get_tags)(char* file, struct song_metadata* psong);
125	int (*get_fileinfo)(char* file, struct song_metadata* psong);
126} taghandler;
127
128static taghandler taghandlers[] = {
129	{ "aac", _get_aactags, _get_aacfileinfo                                  },
130	{ "mp3", _get_mp3tags, _get_mp3fileinfo                                  },
131	{ "flc", _get_flctags, _get_flcfileinfo                                  },
132	{ "ogg", 0,            _get_oggfileinfo                                  },
133	{ "asf", 0,            _get_asffileinfo                                  },
134	{ "wav", _get_wavtags, _get_wavfileinfo                                  },
135	{ "pcm", 0,            _get_pcmfileinfo                                  },
136	{ NULL,  0 }
137};
138
139
140
141//*********************************************************************************
142#include "tagutils-misc.c"
143#include "tagutils-mp3.c"
144#include "tagutils-aac.c"
145#include "tagutils-ogg.c"
146#include "tagutils-flc.c"
147#include "tagutils-asf.c"
148#include "tagutils-wav.c"
149#include "tagutils-pcm.c"
150#include "tagutils-plist.c"
151
152//*********************************************************************************
153// freetags()
154#define MAYBEFREE(a) { if((a)) free((a)); };
155void
156freetags(struct song_metadata *psong)
157{
158	int role;
159
160	MAYBEFREE(psong->path);
161	MAYBEFREE(psong->image);
162	MAYBEFREE(psong->title);
163	MAYBEFREE(psong->album);
164	MAYBEFREE(psong->genre);
165	MAYBEFREE(psong->comment);
166	for(role = ROLE_START; role <= ROLE_LAST; role++)
167	{
168		MAYBEFREE(psong->contributor[role]);
169		MAYBEFREE(psong->contributor_sort[role]);
170	}
171	MAYBEFREE(psong->grouping);
172	MAYBEFREE(psong->mime);
173	MAYBEFREE(psong->dlna_pn);
174	MAYBEFREE(psong->tagversion);
175	MAYBEFREE(psong->musicbrainz_albumid);
176	MAYBEFREE(psong->musicbrainz_trackid);
177	MAYBEFREE(psong->musicbrainz_artistid);
178	MAYBEFREE(psong->musicbrainz_albumartistid);
179}
180
181// _get_fileinfo
182static int
183_get_fileinfo(char *file, struct song_metadata *psong)
184{
185	taghandler *hdl;
186
187	// dispatch to appropriate tag handler
188	for(hdl = taghandlers; hdl->type; ++hdl)
189		if(!strcmp(hdl->type, psong->type))
190			break;
191
192	if(hdl->get_fileinfo)
193		return hdl->get_fileinfo(file, psong);
194
195	return 0;
196}
197
198
199static void
200_make_composite_tags(struct song_metadata *psong)
201{
202	int len;
203
204	len = 1;
205
206	if(!psong->contributor[ROLE_ARTIST] &&
207	   (psong->contributor[ROLE_BAND] || psong->contributor[ROLE_CONDUCTOR]))
208	{
209		if(psong->contributor[ROLE_BAND])
210			len += strlen(psong->contributor[ROLE_BAND]);
211		if(psong->contributor[ROLE_CONDUCTOR])
212			len += strlen(psong->contributor[ROLE_CONDUCTOR]);
213
214		len += 3;
215
216		psong->contributor[ROLE_ARTIST] = (char*)calloc(len, 1);
217		if(psong->contributor[ROLE_ARTIST])
218		{
219			if(psong->contributor[ROLE_BAND])
220				strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_BAND]);
221
222			if(psong->contributor[ROLE_BAND] && psong->contributor[ROLE_CONDUCTOR])
223				strcat(psong->contributor[ROLE_ARTIST], " - ");
224
225			if(psong->contributor[ROLE_CONDUCTOR])
226				strcat(psong->contributor[ROLE_ARTIST], psong->contributor[ROLE_CONDUCTOR]);
227		}
228	}
229
230#if 0 // already taken care of by scanner.c
231	if(!psong->title)
232	{
233		char *suffix;
234		psong->title = strdup(psong->basename);
235		suffix = strrchr(psong->title, '.');
236		if(suffix) *suffix = '\0';
237	}
238#endif
239}
240
241
242/*****************************************************************************/
243// _get_tags
244static int
245_get_tags(char *file, struct song_metadata *psong)
246{
247	taghandler *hdl;
248
249	// dispatch
250	for(hdl = taghandlers ; hdl->type ; ++hdl)
251		if(!strcasecmp(hdl->type, psong->type))
252			break;
253
254	if(hdl->get_tags)
255	{
256		return hdl->get_tags(file, psong);
257	}
258
259	return 0;
260}
261
262/*****************************************************************************/
263// readtags
264int
265readtags(char *path, struct song_metadata *psong, struct stat *stat, char *lang, char *type)
266{
267	char *fname;
268
269	if(lang_index == -1)
270		lang_index = _lang2cp(lang);
271
272	memset((void*)psong, 0, sizeof(struct song_metadata));
273	psong->path = strdup(path);
274	psong->type = type;
275
276	fname = strrchr(psong->path, '/');
277	psong->basename = fname ? fname + 1 : psong->path;
278
279	if(stat)
280	{
281		if(!psong->time_modified)
282			psong->time_modified = stat->st_mtime;
283		psong->file_size = stat->st_size;
284	}
285
286	// get tag
287	if( _get_tags(path, psong) == 0 )
288	{
289		_make_composite_tags(psong);
290	}
291
292	// get fileinfo
293	return _get_fileinfo(path, psong);
294}
295