Id3Scan.cxx 9.14 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 "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 <stdexcept>
36

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

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

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

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

#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif

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

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

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

	return id3_ucs4_utf8duplicate(ucs4);
}

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

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

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

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

	/* check the encoding field */

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

304 305 306 307 308 309 310 311 312 313
	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);
314 315
	tag_id3_import_text(tag, ID3_FRAME_ORIGINAL_RELEASE_DATE, TAG_ORIGINAL_DATE,
			    handler, handler_ctx);
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
	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
332 333
Tag *
tag_id3_import(struct id3_tag *tag)
334
{
335
	TagBuilder tag_builder;
336
	scan_id3_tag(tag, add_tag_handler, &tag_builder);
337
	return tag_builder.empty()
338
		? nullptr
339
		: tag_builder.CommitNew();
340 341
}

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

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

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