Id3Scan.cxx 8.65 KB
Newer Older
1
/*
skidoo23's avatar
skidoo23 committed
2
 * Copyright 2003-2018 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * 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.
14 15 16 17
 *
 * 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.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "Id3Scan.hxx"
21
#include "Id3Load.hxx"
22 23 24
#include "Handler.hxx"
#include "Table.hxx"
#include "Builder.hxx"
25
#include "Tag.hxx"
26
#include "Id3MusicBrainz.hxx"
27
#include "util/Alloc.hxx"
28
#include "util/ScopeExit.hxx"
29
#include "util/StringStrip.hxx"
30
#include "Log.hxx"
Max Kellermann's avatar
Max Kellermann committed
31

32 33
#include <id3tag.h>

34
#include <string>
35
#include <exception>
36

37
#include <string.h>
38
#include <stdlib.h>
39

skidoo23's avatar
skidoo23 committed
40 41 42 43 44 45 46
#ifndef ID3_FRAME_COMPOSER
#define ID3_FRAME_COMPOSER "TCOM"
#endif

#ifndef ID3_FRAME_DISC
#define ID3_FRAME_DISC "TPOS"
#endif
47

Bart Nagel's avatar
Bart Nagel committed
48 49 50 51
#ifndef ID3_FRAME_ARTIST_SORT
#define ID3_FRAME_ARTIST_SORT "TSOP"
#endif

52
#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
Bart Nagel's avatar
Bart Nagel committed
53
#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
54 55 56 57 58 59
#endif

#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif

60 61 62 63
#ifndef ID3_FRAME_ORIGINAL_RELEASE_DATE
#define ID3_FRAME_ORIGINAL_RELEASE_DATE "TDOR"
#endif

skidoo23's avatar
skidoo23 committed
64 65 66 67
#ifndef ID3_FRAME_LABEL
#define ID3_FRAME_LABEL "TPUB"
#endif

68
gcc_pure
69
static id3_utf8_t *
70
tag_id3_getstring(const struct id3_frame *frame, unsigned i) noexcept
71
{
72
	id3_field *field = id3_frame_field(frame, i);
Max Kellermann's avatar
Max Kellermann committed
73 74
	if (field == nullptr)
		return nullptr;
75

76
	const id3_ucs4_t *ucs4 = id3_field_getstring(field);
Max Kellermann's avatar
Max Kellermann committed
77 78
	if (ucs4 == nullptr)
		return nullptr;
79 80 81 82

	return id3_ucs4_utf8duplicate(ucs4);
}

83 84
/* This will try to convert a string to utf-8,
 */
Max Kellermann's avatar
Max Kellermann committed
85
static id3_utf8_t *
86
import_id3_string(const id3_ucs4_t *ucs4)
87
{
88 89 90
	id3_utf8_t *utf8 = id3_ucs4_utf8duplicate(ucs4);
	if (gcc_unlikely(utf8 == nullptr))
		return nullptr;
91

92
	AtScopeExit(utf8) { free(utf8); };
93

94
	return (id3_utf8_t *)xstrdup(Strip((char *)utf8));
95 96
}

97 98 99 100 101 102 103
/**
 * Import a "Text information frame" (ID3v2.4.0 section 4.2).  It
 * contains 2 fields:
 *
 * - encoding
 * - string list
 */
104
static void
105
tag_id3_import_text_frame(const struct id3_frame *frame,
106
			  TagType type,
107
			  TagHandler &handler) noexcept
108
{
109
	if (frame->nfields != 2)
110
		return;
111 112 113

	/* check the encoding field */

114
	const id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
115
	if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
116
		return;
117

118 119 120
	/* process the value(s) */

	field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
121
	if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
122 123 124
		return;

	/* Get the number of strings available */
125 126 127
	const unsigned nstrings = id3_field_getnstrings(field);
	for (unsigned i = 0; i < nstrings; i++) {
		const id3_ucs4_t *ucs4 = id3_field_getstrings(field, i);
Max Kellermann's avatar
Max Kellermann committed
128
		if (ucs4 == nullptr)
129 130
			continue;

131
		if (type == TAG_GENRE)
132 133
			ucs4 = id3_genre_name(ucs4);

134
		id3_utf8_t *utf8 = import_id3_string(ucs4);
Max Kellermann's avatar
Max Kellermann committed
135
		if (utf8 == nullptr)
136 137
			continue;

138 139
		AtScopeExit(utf8) { free(utf8); };

140
		handler.OnTag(type, (const char *)utf8);
141
	}
142 143
}

144 145 146 147 148
/**
 * Import all text frames with the specified id (ID3v2.4.0 section
 * 4.2).  This is a wrapper for tag_id3_import_text_frame().
 */
static void
149
tag_id3_import_text(const struct id3_tag *tag, const char *id, TagType type,
150
		    TagHandler &handler) noexcept
151 152 153
{
	const struct id3_frame *frame;
	for (unsigned i = 0;
Max Kellermann's avatar
Max Kellermann committed
154
	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
155
		tag_id3_import_text_frame(frame, type,
156
					  handler);
157 158
}

159 160 161 162 163 164 165 166 167 168
/**
 * Import a "Comment frame" (ID3v2.4.0 section 4.10).  It
 * contains 4 fields:
 *
 * - encoding
 * - language
 * - string
 * - full string (we use this one)
 */
static void
169
tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type,
170
			     TagHandler &handler) noexcept
171
{
172
	if (frame->nfields != 4)
173 174 175
		return;

	/* for now I only read the 4th field, with the fullstring */
176
	const id3_field *field = id3_frame_field(frame, 3);
Max Kellermann's avatar
Max Kellermann committed
177
	if (field == nullptr)
178 179
		return;

180
	const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
Max Kellermann's avatar
Max Kellermann committed
181
	if (ucs4 == nullptr)
182 183
		return;

184
	id3_utf8_t *utf8 = import_id3_string(ucs4);
Max Kellermann's avatar
Max Kellermann committed
185
	if (utf8 == nullptr)
186 187
		return;

188 189
	AtScopeExit(utf8) { free(utf8); };

190
	handler.OnTag(type, (const char *)utf8);
191 192
}

193 194 195 196 197
/**
 * Import all comment frames (ID3v2.4.0 section 4.10).  This is a
 * wrapper for tag_id3_import_comment_frame().
 */
static void
198
tag_id3_import_comment(const struct id3_tag *tag, const char *id, TagType type,
199
		       TagHandler &handler) noexcept
200 201 202
{
	const struct id3_frame *frame;
	for (unsigned i = 0;
Max Kellermann's avatar
Max Kellermann committed
203
	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
204
		tag_id3_import_comment_frame(frame, type,
205
					     handler);
206 207
}

208
/**
209
 * Parse a TXXX name, and convert it to a TagType enum value.
210 211
 * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
 */
212
gcc_pure
213
static TagType
214
tag_id3_parse_txxx_name(const char *name) noexcept
215
{
216 217

	return tag_table_lookup(musicbrainz_txxx_tags, name);
218 219 220 221 222 223
}

/**
 * Import all known MusicBrainz tags from TXXX frames.
 */
static void
224
tag_id3_import_musicbrainz(const struct id3_tag *id3_tag,
225
			   TagHandler &handler) noexcept
226 227
{
	for (unsigned i = 0;; ++i) {
228
		const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i);
Max Kellermann's avatar
Max Kellermann committed
229
		if (frame == nullptr)
230 231
			break;

232
		id3_utf8_t *name = tag_id3_getstring(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
233
		if (name == nullptr)
234 235
			continue;

236 237
		AtScopeExit(name) { free(name); };

238
		id3_utf8_t *value = tag_id3_getstring(frame, 2);
Max Kellermann's avatar
Max Kellermann committed
239
		if (value == nullptr)
240 241
			continue;

242 243
		AtScopeExit(value) { free(value); };

244
		handler.OnPair((const char *)name, (const char *)value);
245

246
		TagType type = tag_id3_parse_txxx_name((const char*)name);
247 248

		if (type != TAG_NUM_OF_ITEM_TYPES)
249
			handler.OnTag(type, (const char*)value);
250 251 252
	}
}

253 254 255 256
/**
 * Imports the MusicBrainz TrackId from the UFID tag.
 */
static void
257
tag_id3_import_ufid(const struct id3_tag *id3_tag,
258
		    TagHandler &handler) noexcept
259 260
{
	for (unsigned i = 0;; ++i) {
261
		const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i);
Max Kellermann's avatar
Max Kellermann committed
262
		if (frame == nullptr)
263 264
			break;

265
		id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
266
		if (field == nullptr)
267 268
			continue;

269
		const id3_latin1_t *name = id3_field_getlatin1(field);
Max Kellermann's avatar
Max Kellermann committed
270
		if (name == nullptr ||
271 272 273 274
		    strcmp((const char *)name, "http://musicbrainz.org") != 0)
			continue;

		field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
275
		if (field == nullptr)
276 277
			continue;

278 279 280
		id3_length_t length;
		const id3_byte_t *value =
			id3_field_getbinarydata(field, &length);
Max Kellermann's avatar
Max Kellermann committed
281
		if (value == nullptr || length == 0)
282 283
			continue;

284
		std::string p((const char *)value, length);
285
		handler.OnTag(TAG_MUSICBRAINZ_TRACKID, p.c_str());
286 287 288
	}
}

289
void
290
scan_id3_tag(const struct id3_tag *tag, TagHandler &handler) noexcept
291 292
{
	tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
293
			    handler);
294
	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
295
			    TAG_ALBUM_ARTIST, handler);
296
	tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
297
			    TAG_ARTIST_SORT, handler);
298

299
	tag_id3_import_text(tag, "TSOA", TAG_ALBUM_SORT, handler);
300

301
	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
302
			    TAG_ALBUM_ARTIST_SORT, handler);
303
	tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
304
			    handler);
305
	tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
306
			    handler);
307
	tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
308
			    handler);
309
	tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
310
			    handler);
311
	tag_id3_import_text(tag, ID3_FRAME_ORIGINAL_RELEASE_DATE, TAG_ORIGINAL_DATE,
312
			    handler);
313
	tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
314
			    handler);
315
	tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
316
			    handler);
317
	tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
318 319
			    handler);
	tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler);
320
	tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
321
			       handler);
322
	tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
323
			    handler);
skidoo23's avatar
skidoo23 committed
324 325
	tag_id3_import_text(tag, ID3_FRAME_LABEL, TAG_LABEL,
			    handler);
326

327 328
	tag_id3_import_musicbrainz(tag, handler);
	tag_id3_import_ufid(tag, handler);
329 330
}

331
Tag
332
tag_id3_import(const struct id3_tag *tag) noexcept
333
{
334
	TagBuilder tag_builder;
335 336
	AddTagHandler h(tag_builder);
	scan_id3_tag(tag, h);
337
	return tag_builder.Commit();
338 339
}

340
bool
341
tag_id3_scan(InputStream &is, TagHandler &handler) noexcept
342 343 344 345 346
{
	UniqueId3Tag tag;

	try {
		tag = tag_id3_load(is);
347 348
		if (!tag)
			return false;
349 350
	} catch (...) {
		LogError(std::current_exception());
351 352 353
		return false;
	}

354
	scan_id3_tag(tag.get(), handler);
355 356
	return true;
}