TagId3.cxx 13.7 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2014 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 "TagHandler.hxx"
23
#include "TagTable.hxx"
24
#include "TagBuilder.hxx"
25
#include "util/Alloc.hxx"
26
#include "util/StringUtil.hxx"
27
#include "util/Error.hxx"
28 29
#include "util/Domain.hxx"
#include "Log.hxx"
30
#include "config/ConfigGlobal.hxx"
31 32
#include "Riff.hxx"
#include "Aiff.hxx"
33 34
#include "fs/Path.hxx"
#include "fs/FileSystem.hxx"
Max Kellermann's avatar
Max Kellermann committed
35

36
#include <glib.h>
37 38
#include <id3tag.h>

39 40
#include <string>

41
#include <stdio.h>
42
#include <stdlib.h>
43 44
#include <string.h>

45 46 47 48 49 50 51
#  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
52 53 54 55
#ifndef ID3_FRAME_ARTIST_SORT
#define ID3_FRAME_ARTIST_SORT "TSOP"
#endif

56
#ifndef ID3_FRAME_ALBUM_ARTIST_SORT
Bart Nagel's avatar
Bart Nagel committed
57
#define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
58 59 60 61 62 63
#endif

#ifndef ID3_FRAME_ALBUM_ARTIST
#define ID3_FRAME_ALBUM_ARTIST "TPE2"
#endif

64 65
static constexpr Domain id3_domain("id3");

66 67 68 69 70 71
static inline bool
tag_is_id3v1(struct id3_tag *tag)
{
	return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0;
}

72 73 74
static id3_utf8_t *
tag_id3_getstring(const struct id3_frame *frame, unsigned i)
{
75
	id3_field *field = id3_frame_field(frame, i);
Max Kellermann's avatar
Max Kellermann committed
76 77
	if (field == nullptr)
		return nullptr;
78

79
	const id3_ucs4_t *ucs4 = id3_field_getstring(field);
Max Kellermann's avatar
Max Kellermann committed
80 81
	if (ucs4 == nullptr)
		return nullptr;
82 83 84 85

	return id3_ucs4_utf8duplicate(ucs4);
}

86 87
/* This will try to convert a string to utf-8,
 */
Max Kellermann's avatar
Max Kellermann committed
88
static id3_utf8_t *
89
import_id3_string(bool is_id3v1, const id3_ucs4_t *ucs4)
90
{
91
	id3_utf8_t *utf8;
92

93
	/* use encoding field here? */
94
	const char *encoding;
95
	if (is_id3v1 &&
Max Kellermann's avatar
Max Kellermann committed
96
	    (encoding = config_get_string(CONF_ID3V1_ENCODING, nullptr)) != nullptr) {
97
		id3_latin1_t *isostr = id3_ucs4_latin1duplicate(ucs4);
98
		if (gcc_unlikely(isostr == nullptr))
Max Kellermann's avatar
Max Kellermann committed
99
			return nullptr;
100 101 102

		utf8 = (id3_utf8_t *)
			g_convert_with_fallback((const char*)isostr, -1,
103
						"utf-8", encoding,
Max Kellermann's avatar
Max Kellermann committed
104 105 106
						nullptr, nullptr,
						nullptr, nullptr);
		if (utf8 == nullptr) {
107 108 109
			FormatWarning(id3_domain,
				      "Unable to convert %s string to UTF-8: '%s'",
				      encoding, isostr);
110
			free(isostr);
Max Kellermann's avatar
Max Kellermann committed
111
			return nullptr;
112
		}
113
		free(isostr);
114 115
	} else {
		utf8 = id3_ucs4_utf8duplicate(ucs4);
116
		if (gcc_unlikely(utf8 == nullptr))
Max Kellermann's avatar
Max Kellermann committed
117
			return nullptr;
118
	}
119

120
	id3_utf8_t *utf8_stripped = (id3_utf8_t *)
121
		xstrdup(Strip((char *)utf8));
122
	free(utf8);
123 124

	return utf8_stripped;
125 126
}

127 128 129 130 131 132 133
/**
 * Import a "Text information frame" (ID3v2.4.0 section 4.2).  It
 * contains 2 fields:
 *
 * - encoding
 * - string list
 */
134
static void
135
tag_id3_import_text_frame(struct id3_tag *tag, const struct id3_frame *frame,
136
			  TagType type,
137
			  const struct tag_handler *handler, void *handler_ctx)
138
{
139
	if (frame->nfields != 2)
140
		return;
141 142 143

	/* check the encoding field */

144
	const id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
145
	if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
146
		return;
147

148 149 150
	/* process the value(s) */

	field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
151
	if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
152 153 154
		return;

	/* Get the number of strings available */
155 156 157
	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
158
		if (ucs4 == nullptr)
159 160
			continue;

161
		if (type == TAG_GENRE)
162 163
			ucs4 = id3_genre_name(ucs4);

164
		id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
Max Kellermann's avatar
Max Kellermann committed
165
		if (utf8 == nullptr)
166 167
			continue;

168 169
		tag_handler_invoke_tag(handler, handler_ctx,
				       type, (const char *)utf8);
170
		free(utf8);
171
	}
172 173
}

174 175 176 177 178
/**
 * 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
179
tag_id3_import_text(struct id3_tag *tag, const char *id, TagType type,
180
		    const struct tag_handler *handler, void *handler_ctx)
181 182 183
{
	const struct id3_frame *frame;
	for (unsigned i = 0;
Max Kellermann's avatar
Max Kellermann committed
184
	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
185 186
		tag_id3_import_text_frame(tag, frame, type,
					  handler, handler_ctx);
187 188
}

189 190 191 192 193 194 195 196 197 198
/**
 * 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
199
tag_id3_import_comment_frame(struct id3_tag *tag,
200
			     const struct id3_frame *frame, TagType type,
201 202
			     const struct tag_handler *handler,
			     void *handler_ctx)
203
{
204
	if (frame->nfields != 4)
205 206 207
		return;

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

212
	const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
Max Kellermann's avatar
Max Kellermann committed
213
	if (ucs4 == nullptr)
214 215
		return;

216
	id3_utf8_t *utf8 = import_id3_string(tag_is_id3v1(tag), ucs4);
Max Kellermann's avatar
Max Kellermann committed
217
	if (utf8 == nullptr)
218 219
		return;

220
	tag_handler_invoke_tag(handler, handler_ctx, type, (const char *)utf8);
221
	free(utf8);
222 223
}

224 225 226 227 228
/**
 * Import all comment frames (ID3v2.4.0 section 4.10).  This is a
 * wrapper for tag_id3_import_comment_frame().
 */
static void
229
tag_id3_import_comment(struct id3_tag *tag, const char *id, TagType type,
230
		       const struct tag_handler *handler, void *handler_ctx)
231 232 233
{
	const struct id3_frame *frame;
	for (unsigned i = 0;
Max Kellermann's avatar
Max Kellermann committed
234
	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
235 236
		tag_id3_import_comment_frame(tag, frame, type,
					     handler, handler_ctx);
237 238
}

239
/**
240
 * Parse a TXXX name, and convert it to a TagType enum value.
241 242
 * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
 */
243
static TagType
244 245
tag_id3_parse_txxx_name(const char *name)
{
246 247 248 249 250 251 252
	static const struct tag_table txxx_tags[] = {
		{ "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 },
Max Kellermann's avatar
Max Kellermann committed
253
		{ nullptr, TAG_NUM_OF_ITEM_TYPES }
254 255
	};

256
	return tag_table_lookup(txxx_tags, name);
257 258 259 260 261 262
}

/**
 * Import all known MusicBrainz tags from TXXX frames.
 */
static void
263 264 265
tag_id3_import_musicbrainz(struct id3_tag *id3_tag,
			   const struct tag_handler *handler,
			   void *handler_ctx)
266 267
{
	for (unsigned i = 0;; ++i) {
268
		const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i);
Max Kellermann's avatar
Max Kellermann committed
269
		if (frame == nullptr)
270 271
			break;

272
		id3_utf8_t *name = tag_id3_getstring(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
273
		if (name == nullptr)
274 275
			continue;

276
		id3_utf8_t *value = tag_id3_getstring(frame, 2);
Max Kellermann's avatar
Max Kellermann committed
277
		if (value == nullptr)
278 279
			continue;

280 281 282 283
		tag_handler_invoke_pair(handler, handler_ctx,
					(const char *)name,
					(const char *)value);

284
		TagType type = tag_id3_parse_txxx_name((const char*)name);
285 286 287 288 289 290
		free(name);

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

291 292 293 294
		free(value);
	}
}

295 296 297 298
/**
 * Imports the MusicBrainz TrackId from the UFID tag.
 */
static void
299 300
tag_id3_import_ufid(struct id3_tag *id3_tag,
		    const struct tag_handler *handler, void *handler_ctx)
301 302
{
	for (unsigned i = 0;; ++i) {
303
		const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i);
Max Kellermann's avatar
Max Kellermann committed
304
		if (frame == nullptr)
305 306
			break;

307
		id3_field *field = id3_frame_field(frame, 0);
Max Kellermann's avatar
Max Kellermann committed
308
		if (field == nullptr)
309 310
			continue;

311
		const id3_latin1_t *name = id3_field_getlatin1(field);
Max Kellermann's avatar
Max Kellermann committed
312
		if (name == nullptr ||
313 314 315 316
		    strcmp((const char *)name, "http://musicbrainz.org") != 0)
			continue;

		field = id3_frame_field(frame, 1);
Max Kellermann's avatar
Max Kellermann committed
317
		if (field == nullptr)
318 319
			continue;

320 321 322
		id3_length_t length;
		const id3_byte_t *value =
			id3_field_getbinarydata(field, &length);
Max Kellermann's avatar
Max Kellermann committed
323
		if (value == nullptr || length == 0)
324 325
			continue;

326
		std::string p((const char *)value, length);
327
		tag_handler_invoke_tag(handler, handler_ctx,
328
				       TAG_MUSICBRAINZ_TRACKID, p.c_str());
329 330 331
	}
}

332
void
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
scan_id3_tag(struct id3_tag *tag,
	     const struct tag_handler *handler, void *handler_ctx)
{
	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);
	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
368 369
Tag *
tag_id3_import(struct id3_tag *tag)
370
{
371 372 373 374
	TagBuilder tag_builder;
	scan_id3_tag(tag, &add_tag_handler, &tag_builder);
	return tag_builder.IsEmpty()
		? nullptr
375
		: tag_builder.CommitNew();
376 377
}

378
static size_t
Max Kellermann's avatar
Max Kellermann committed
379
fill_buffer(void *buf, size_t size, FILE *stream, long offset, int whence)
380 381 382 383 384
{
	if (fseek(stream, offset, whence) != 0) return 0;
	return fread(buf, 1, size, stream);
}

385
static long
Max Kellermann's avatar
Max Kellermann committed
386
get_id3v2_footer_size(FILE *stream, long offset, int whence)
387 388
{
	id3_byte_t buf[ID3_TAG_QUERYSIZE];
389 390
	size_t bufsize = fill_buffer(buf, ID3_TAG_QUERYSIZE, stream, offset, whence);
	if (bufsize == 0) return 0;
391 392 393
	return id3_tag_query(buf, bufsize);
}

Max Kellermann's avatar
Max Kellermann committed
394 395
static struct id3_tag *
tag_id3_read(FILE *stream, long offset, int whence)
396 397
{
	/* It's ok if we get less than we asked for */
398 399 400
	id3_byte_t query_buffer[ID3_TAG_QUERYSIZE];
	size_t query_buffer_size = fill_buffer(query_buffer, ID3_TAG_QUERYSIZE,
					       stream, offset, whence);
Max Kellermann's avatar
Max Kellermann committed
401 402
	if (query_buffer_size <= 0)
		return nullptr;
403 404

	/* Look for a tag header */
405
	long tag_size = id3_tag_query(query_buffer, query_buffer_size);
Max Kellermann's avatar
Max Kellermann committed
406
	if (tag_size <= 0) return nullptr;
407 408

	/* Found a tag.  Allocate a buffer and read it in. */
409
	id3_byte_t *tag_buffer = new id3_byte_t[tag_size];
Max Kellermann's avatar
Max Kellermann committed
410 411
	int tag_buffer_size = fill_buffer(tag_buffer, tag_size,
					  stream, offset, whence);
Max Kellermann's avatar
Max Kellermann committed
412
	if (tag_buffer_size < tag_size) {
413
		delete[] tag_buffer;
Max Kellermann's avatar
Max Kellermann committed
414
		return nullptr;
415 416
	}

417
	id3_tag *tag = id3_tag_parse(tag_buffer, tag_buffer_size);
418
	delete[] tag_buffer;
419 420 421
	return tag;
}

Max Kellermann's avatar
Max Kellermann committed
422 423
static struct id3_tag *
tag_id3_find_from_beginning(FILE *stream)
424
{
425
	id3_tag *tag = tag_id3_read(stream, 0, SEEK_SET);
426
	if (!tag) {
Max Kellermann's avatar
Max Kellermann committed
427
		return nullptr;
Max Kellermann's avatar
Max Kellermann committed
428
	} else if (tag_is_id3v1(tag)) {
429 430
		/* id3v1 tags don't belong here */
		id3_tag_delete(tag);
Max Kellermann's avatar
Max Kellermann committed
431
		return nullptr;
432 433 434
	}

	/* We have an id3v2 tag, so let's look for SEEK frames */
435
	id3_frame *frame;
436 437
	while ((frame = id3_tag_findframe(tag, "SEEK", 0))) {
		/* Found a SEEK frame, get it's value */
438
		int seek = id3_field_getint(id3_frame_field(frame, 0));
439 440 441 442
		if (seek < 0)
			break;

		/* Get the tag specified by the SEEK frame */
443
		id3_tag *seektag = tag_id3_read(stream, seek, SEEK_CUR);
Max Kellermann's avatar
Max Kellermann committed
444
		if (!seektag || tag_is_id3v1(seektag))
445 446 447 448 449 450 451 452 453 454
			break;

		/* Replace the old tag with the new one */
		id3_tag_delete(tag);
		tag = seektag;
	}

	return tag;
}

Max Kellermann's avatar
Max Kellermann committed
455 456
static struct id3_tag *
tag_id3_find_from_end(FILE *stream)
457 458
{
	/* Get an id3v1 tag from the end of file for later use */
459
	id3_tag *v1tag = tag_id3_read(stream, -128, SEEK_END);
460 461

	/* Get the id3v2 tag size from the footer (located before v1tag) */
462
	int tagsize = get_id3v2_footer_size(stream, (v1tag ? -128 : 0) - 10, SEEK_END);
463 464 465 466
	if (tagsize >= 0)
		return v1tag;

	/* Get the tag which the footer belongs to */
467
	id3_tag *tag = tag_id3_read(stream, tagsize, SEEK_CUR);
468 469 470 471 472 473 474 475 476
	if (!tag)
		return v1tag;

	/* We have an id3v2 tag, so ditch v1tag */
	id3_tag_delete(v1tag);

	return tag;
}

477
static struct id3_tag *
478
tag_id3_riff_aiff_load(FILE *file)
479
{
Max Kellermann's avatar
Max Kellermann committed
480
	size_t size = riff_seek_id3(file);
481 482
	if (size == 0)
		size = aiff_seek_id3(file);
483
	if (size == 0)
Max Kellermann's avatar
Max Kellermann committed
484
		return nullptr;
485

486
	if (size > 4 * 1024 * 1024)
487
		/* too large, don't allocate so much memory */
Max Kellermann's avatar
Max Kellermann committed
488
		return nullptr;
489

490
	id3_byte_t *buffer = new id3_byte_t[size];
Max Kellermann's avatar
Max Kellermann committed
491
	size_t ret = fread(buffer, size, 1, file);
492
	if (ret != 1) {
493
		LogWarning(id3_domain, "Failed to read RIFF chunk");
494
		delete[] buffer;
Max Kellermann's avatar
Max Kellermann committed
495
		return nullptr;
496 497
	}

Max Kellermann's avatar
Max Kellermann committed
498
	struct id3_tag *tag = id3_tag_parse(buffer, size);
499
	delete[] buffer;
500 501 502
	return tag;
}

503
struct id3_tag *
504
tag_id3_load(Path path_fs, Error &error)
505
{
506
	FILE *file = FOpen(path_fs, "rb");
Max Kellermann's avatar
Max Kellermann committed
507
	if (file == nullptr) {
508
		error.FormatErrno("Failed to open file %s", path_fs);
Max Kellermann's avatar
Max Kellermann committed
509
		return nullptr;
510
	}
511

512
	struct id3_tag *tag = tag_id3_find_from_beginning(file);
Max Kellermann's avatar
Max Kellermann committed
513
	if (tag == nullptr) {
514
		tag = tag_id3_riff_aiff_load(file);
Max Kellermann's avatar
Max Kellermann committed
515
		if (tag == nullptr)
516
			tag = tag_id3_find_from_end(file);
517 518
	}

519 520 521
	fclose(file);
	return tag;
}
522

523
bool
524
tag_id3_scan(Path path_fs,
525 526
	     const struct tag_handler *handler, void *handler_ctx)
{
527 528
	Error error;
	struct id3_tag *tag = tag_id3_load(path_fs, error);
Max Kellermann's avatar
Max Kellermann committed
529
	if (tag == nullptr) {
530
		if (error.IsDefined())
531
			LogError(error);
532 533

		return false;
534
	}
535 536 537 538 539

	scan_id3_tag(tag, handler, handler_ctx);
	id3_tag_delete(tag);
	return true;
}