Id3Scan.cxx 9.17 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 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 "config.h"
21
#include "Id3Scan.hxx"
22
#include "Id3Load.hxx"
23 24 25
#include "Handler.hxx"
#include "Table.hxx"
#include "Builder.hxx"
26
#include "Tag.hxx"
27
#include "Id3MusicBrainz.hxx"
28
#include "util/Alloc.hxx"
29
#include "util/ScopeExit.hxx"
30
#include "util/StringStrip.hxx"
31
#include "Log.hxx"
Max Kellermann's avatar
Max Kellermann committed
32

33 34
#include <id3tag.h>

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

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

41 42 43 44 45 46 47
#  ifndef ID3_FRAME_COMPOSER
#    define ID3_FRAME_COMPOSER "TCOM"
#  endif
#  ifndef ID3_FRAME_DISC
#    define ID3_FRAME_DISC "TPOS"
#  endif

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

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

72
	const id3_ucs4_t *ucs4 = id3_field_getstring(field);
Max Kellermann's avatar
Max Kellermann committed
73 74
	if (ucs4 == nullptr)
		return nullptr;
75 76 77 78

	return id3_ucs4_utf8duplicate(ucs4);
}

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

88
	AtScopeExit(utf8) { free(utf8); };
89

90
	return (id3_utf8_t *)xstrdup(Strip((char *)utf8));
91 92
}

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

	/* check the encoding field */

110
	const id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
111
	if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
112
		return;
113

114 115 116
	/* process the value(s) */

	field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
117
	if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
118 119 120
		return;

	/* Get the number of strings available */
121 122 123
	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
124
		if (ucs4 == nullptr)
125 126
			continue;

127
		if (type == TAG_GENRE)
128 129
			ucs4 = id3_genre_name(ucs4);

130
		id3_utf8_t *utf8 = import_id3_string(ucs4);
Max Kellermann's avatar
Max Kellermann committed
131
		if (utf8 == nullptr)
132 133
			continue;

134 135
		AtScopeExit(utf8) { free(utf8); };

136 137
		tag_handler_invoke_tag(handler, handler_ctx,
				       type, (const char *)utf8);
138
	}
139 140
}

141 142 143 144 145
/**
 * 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
146
tag_id3_import_text(struct id3_tag *tag, const char *id, TagType type,
147
		    const TagHandler &handler, void *handler_ctx)
148 149 150
{
	const struct id3_frame *frame;
	for (unsigned i = 0;
Max Kellermann's avatar
Max Kellermann committed
151
	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
152
		tag_id3_import_text_frame(frame, type,
153
					  handler, handler_ctx);
154 155
}

156 157 158 159 160 161 162 163 164 165
/**
 * 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
166
tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type,
167
			     const TagHandler &handler,
168
			     void *handler_ctx)
169
{
170
	if (frame->nfields != 4)
171 172 173
		return;

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

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

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

186 187
	AtScopeExit(utf8) { free(utf8); };

188
	tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
189 190
}

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

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

	return tag_table_lookup(musicbrainz_txxx_tags, name);
216 217 218 219 220 221
}

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

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

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

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

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

243 244 245 246
		tag_handler_invoke_pair(handler, handler_ctx,
					(const char *)name,
					(const char *)value);

247
		TagType type = tag_id3_parse_txxx_name((const char*)name);
248 249 250 251

		if (type != TAG_NUM_OF_ITEM_TYPES)
			tag_handler_invoke_tag(handler, handler_ctx,
					       type, (const char*)value);
252 253 254
	}
}

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

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

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

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

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

286
		std::string p((const char *)value, length);
287
		tag_handler_invoke_tag(handler, handler_ctx,
288
				       TAG_MUSICBRAINZ_TRACKID, p.c_str());
289 290 291
	}
}

292
void
293
scan_id3_tag(struct id3_tag *tag,
294
	     const TagHandler &handler, void *handler_ctx)
295 296 297 298 299 300 301
{
	tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
			    handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
			    TAG_ALBUM_ARTIST, handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
			    TAG_ARTIST_SORT, handler, handler_ctx);
302 303 304

	tag_id3_import_text(tag, "TSOA", TAG_ALBUM_SORT, handler, handler_ctx);

305 306 307 308 309 310 311 312 313 314
	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
			    TAG_ALBUM_ARTIST_SORT, handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
			    handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
			    handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
			    handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
			    handler, handler_ctx);
315 316
	tag_id3_import_text(tag, ID3_FRAME_ORIGINAL_RELEASE_DATE, TAG_ORIGINAL_DATE,
			    handler, handler_ctx);
317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
			    handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
			    handler, handler_ctx);
	tag_id3_import_text(tag, "TPE3", TAG_PERFORMER,
			    handler, handler_ctx);
	tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler, handler_ctx);
	tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
			       handler, handler_ctx);
	tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
			    handler, handler_ctx);

	tag_id3_import_musicbrainz(tag, handler, handler_ctx);
	tag_id3_import_ufid(tag, handler, handler_ctx);
}

333
std::unique_ptr<Tag>
Max Kellermann's avatar
Max Kellermann committed
334
tag_id3_import(struct id3_tag *tag)
335
{
336
	TagBuilder tag_builder;
337
	scan_id3_tag(tag, add_tag_handler, &tag_builder);
338
	return tag_builder.empty()
339
		? nullptr
340
		: tag_builder.CommitNew();
341 342
}

343 344 345 346 347 348 349 350
bool
tag_id3_scan(InputStream &is,
	     const TagHandler &handler, void *handler_ctx)
{
	UniqueId3Tag tag;

	try {
		tag = tag_id3_load(is);
351 352
		if (!tag)
			return false;
353 354
	} catch (...) {
		LogError(std::current_exception());
355 356 357 358 359 360
		return false;
	}

	scan_id3_tag(tag.get(), handler, handler_ctx);
	return true;
}