TagId3.cxx 9.23 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"
Max Kellermann's avatar
Max Kellermann committed
21
#include "TagId3.hxx"
22
#include "Id3Load.hxx"
23
#include "TagHandler.hxx"
24
#include "TagTable.hxx"
25
#include "TagBuilder.hxx"
26
#include "util/Alloc.hxx"
27
#include "util/StringUtil.hxx"
28
#include "Log.hxx"
Max Kellermann's avatar
Max Kellermann committed
29

30 31
#include <id3tag.h>

32
#include <string>
33
#include <stdexcept>
34

35
#include <string.h>
36
#include <stdlib.h>
37

38 39 40 41 42 43 44
#  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
45 46 47 48
#ifndef ID3_FRAME_ARTIST_SORT
#define ID3_FRAME_ARTIST_SORT "TSOP"
#endif

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

#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif

57
gcc_pure
58 59 60
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
61
	id3_field *field = id3_frame_field(frame, i);
Max Kellermann's avatar
Max Kellermann committed
62 63
	if (field == nullptr)
		return nullptr;
64

65
	const id3_ucs4_t *ucs4 = id3_field_getstring(field);
Max Kellermann's avatar
Max Kellermann committed
66 67
	if (ucs4 == nullptr)
		return nullptr;
68 69 70 71

	return id3_ucs4_utf8duplicate(ucs4);
}

72 73
/* This will try to convert a string to utf-8,
 */
Max Kellermann's avatar
Max Kellermann committed
74
static id3_utf8_t *
75
import_id3_string(const id3_ucs4_t *ucs4)
76
{
77 78 79
	id3_utf8_t *utf8 = id3_ucs4_utf8duplicate(ucs4);
	if (gcc_unlikely(utf8 == nullptr))
		return nullptr;
80

81
	id3_utf8_t *utf8_stripped = (id3_utf8_t *)
82
		xstrdup(Strip((char *)utf8));
83
	free(utf8);
84 85

	return utf8_stripped;
86 87
}

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

	/* check the encoding field */

105
	const id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
106
	if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
107
		return;
108

109 110 111
	/* process the value(s) */

	field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
112
	if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
113 114 115
		return;

	/* Get the number of strings available */
116 117 118
	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
119
		if (ucs4 == nullptr)
120 121
			continue;

122
		if (type == TAG_GENRE)
123 124
			ucs4 = id3_genre_name(ucs4);

125
		id3_utf8_t *utf8 = import_id3_string(ucs4);
Max Kellermann's avatar
Max Kellermann committed
126
		if (utf8 == nullptr)
127 128
			continue;

129 130
		tag_handler_invoke_tag(handler, handler_ctx,
				       type, (const char *)utf8);
131
		free(utf8);
132
	}
133 134
}

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

150 151 152 153 154 155 156 157 158 159
/**
 * 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
160
tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type,
161
			     const TagHandler &handler,
162
			     void *handler_ctx)
163
{
164
	if (frame->nfields != 4)
165 166 167
		return;

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

172
	const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
Max Kellermann's avatar
Max Kellermann committed
173
	if (ucs4 == nullptr)
174 175
		return;

176
	id3_utf8_t *utf8 = import_id3_string(ucs4);
Max Kellermann's avatar
Max Kellermann committed
177
	if (utf8 == nullptr)
178 179
		return;

180
	tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
181
	free(utf8);
182 183
}

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

199
/**
200
 * Parse a TXXX name, and convert it to a TagType enum value.
201 202
 * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
 */
203
gcc_pure
204
static TagType
205 206
tag_id3_parse_txxx_name(const char *name)
{
207
	static constexpr struct tag_table txxx_tags[] = {
208 209 210 211 212 213
		{ "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT },
		{ "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID },
		{ "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID },
		{ "MusicBrainz Album Artist Id",
		  TAG_MUSICBRAINZ_ALBUMARTISTID },
		{ "MusicBrainz Track Id", TAG_MUSICBRAINZ_TRACKID },
214 215
		{ "MusicBrainz Release Track Id",
		  TAG_MUSICBRAINZ_RELEASETRACKID },
Max Kellermann's avatar
Max Kellermann committed
216
		{ nullptr, TAG_NUM_OF_ITEM_TYPES }
217 218
	};

219
	return tag_table_lookup(txxx_tags, name);
220 221 222 223 224 225
}

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

235
		id3_utf8_t *name = tag_id3_getstring(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
236
		if (name == nullptr)
237 238
			continue;

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

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 252 253
		free(name);

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

254 255 256 257
		free(value);
	}
}

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

270
		id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
271
		if (field == nullptr)
272 273
			continue;

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

		field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
280
		if (field == nullptr)
281 282
			continue;

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

289
		std::string p((const char *)value, length);
290
		tag_handler_invoke_tag(handler, handler_ctx,
291
				       TAG_MUSICBRAINZ_TRACKID, p.c_str());
292 293 294
	}
}

295
void
296
scan_id3_tag(struct id3_tag *tag,
297
	     const TagHandler &handler, void *handler_ctx)
298 299 300 301 302 303 304
{
	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);
305 306 307

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

308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333
	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);
	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);
}

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

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

	try {
		tag = tag_id3_load(is);
352 353
		if (!tag)
			return false;
354 355 356 357 358 359 360 361
	} catch (const std::runtime_error &e) {
		LogError(e);
		return false;
	}

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