1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "Id3Scan.hxx"
21 #include "Id3String.hxx"
22 #include "Id3Load.hxx"
23 #include "Handler.hxx"
24 #include "Table.hxx"
25 #include "Builder.hxx"
26 #include "Tag.hxx"
27 #include "Id3MusicBrainz.hxx"
28 #include "util/StringView.hxx"
29 
30 #include <id3tag.h>
31 
32 #include <string.h>
33 #include <stdlib.h>
34 
35 #ifndef ID3_FRAME_COMPOSER
36 #define ID3_FRAME_COMPOSER "TCOM"
37 #endif
38 
39 #ifndef ID3_FRAME_DISC
40 #define ID3_FRAME_DISC "TPOS"
41 #endif
42 
43 #ifndef ID3_FRAME_ARTIST_SORT
44 #define ID3_FRAME_ARTIST_SORT "TSOP"
45 #endif
46 
47 #ifndef ID3_FRAME_ALBUM_ARTIST_SORT
48 #define ID3_FRAME_ALBUM_ARTIST_SORT "TSO2" /* this one is unofficial, introduced by Itunes */
49 #endif
50 
51 #ifndef ID3_FRAME_ALBUM_ARTIST
52 #define ID3_FRAME_ALBUM_ARTIST "TPE2"
53 #endif
54 
55 #ifndef ID3_FRAME_ORIGINAL_RELEASE_DATE
56 #define ID3_FRAME_ORIGINAL_RELEASE_DATE "TDOR"
57 #endif
58 
59 #ifndef ID3_FRAME_LABEL
60 #define ID3_FRAME_LABEL "TPUB"
61 #endif
62 
63 gcc_pure
64 static Id3String
tag_id3_getstring(const struct id3_frame * frame,unsigned i)65 tag_id3_getstring(const struct id3_frame *frame, unsigned i) noexcept
66 {
67 	id3_field *field = id3_frame_field(frame, i);
68 	if (field == nullptr)
69 		return {};
70 
71 	const id3_ucs4_t *ucs4 = id3_field_getstring(field);
72 	if (ucs4 == nullptr)
73 		return {};
74 
75 	return Id3String::FromUCS4(ucs4);
76 }
77 
78 static void
InvokeOnTag(TagHandler & handler,TagType type,const id3_ucs4_t * ucs4)79 InvokeOnTag(TagHandler &handler, TagType type, const id3_ucs4_t *ucs4) noexcept
80 {
81 	assert(type < TAG_NUM_OF_ITEM_TYPES);
82 	assert(ucs4 != nullptr);
83 
84 	const auto utf8 = Id3String::FromUCS4(ucs4);
85 	if (!utf8)
86 		return;
87 
88 	StringView s{utf8.c_str()};
89 	s.Strip();
90 
91 	handler.OnTag(type, s);
92 }
93 
94 /**
95  * Import a "Text information frame" (ID3v2.4.0 section 4.2).  It
96  * contains 2 fields:
97  *
98  * - encoding
99  * - string list
100  */
101 static void
tag_id3_import_text_frame(const struct id3_frame * frame,TagType type,TagHandler & handler)102 tag_id3_import_text_frame(const struct id3_frame *frame,
103 			  TagType type,
104 			  TagHandler &handler) noexcept
105 {
106 	if (frame->nfields != 2)
107 		return;
108 
109 	/* check the encoding field */
110 
111 	const id3_field *field = id3_frame_field(frame, 0);
112 	if (field == nullptr || field->type != ID3_FIELD_TYPE_TEXTENCODING)
113 		return;
114 
115 	/* process the value(s) */
116 
117 	field = id3_frame_field(frame, 1);
118 	if (field == nullptr || field->type != ID3_FIELD_TYPE_STRINGLIST)
119 		return;
120 
121 	/* Get the number of strings available */
122 	const unsigned nstrings = id3_field_getnstrings(field);
123 	for (unsigned i = 0; i < nstrings; i++) {
124 		const id3_ucs4_t *ucs4 = id3_field_getstrings(field, i);
125 		if (ucs4 == nullptr)
126 			continue;
127 
128 		if (type == TAG_GENRE)
129 			ucs4 = id3_genre_name(ucs4);
130 
131 		InvokeOnTag(handler, type, ucs4);
132 	}
133 }
134 
135 /**
136  * Import all text frames with the specified id (ID3v2.4.0 section
137  * 4.2).  This is a wrapper for tag_id3_import_text_frame().
138  */
139 static void
tag_id3_import_text(const struct id3_tag * tag,const char * id,TagType type,TagHandler & handler)140 tag_id3_import_text(const struct id3_tag *tag, const char *id, TagType type,
141 		    TagHandler &handler) noexcept
142 {
143 	const struct id3_frame *frame;
144 	for (unsigned i = 0;
145 	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
146 		tag_id3_import_text_frame(frame, type,
147 					  handler);
148 }
149 
150 /**
151  * Import a "Comment frame" (ID3v2.4.0 section 4.10).  It
152  * contains 4 fields:
153  *
154  * - encoding
155  * - language
156  * - string
157  * - full string (we use this one)
158  */
159 static void
tag_id3_import_comment_frame(const struct id3_frame * frame,TagType type,TagHandler & handler)160 tag_id3_import_comment_frame(const struct id3_frame *frame, TagType type,
161 			     TagHandler &handler) noexcept
162 {
163 	if (frame->nfields != 4)
164 		return;
165 
166 	/* for now I only read the 4th field, with the fullstring */
167 	const id3_field *field = id3_frame_field(frame, 3);
168 	if (field == nullptr)
169 		return;
170 
171 	const id3_ucs4_t *ucs4 = id3_field_getfullstring(field);
172 	if (ucs4 == nullptr)
173 		return;
174 
175 	InvokeOnTag(handler, type, ucs4);
176 }
177 
178 /**
179  * Import all comment frames (ID3v2.4.0 section 4.10).  This is a
180  * wrapper for tag_id3_import_comment_frame().
181  */
182 static void
tag_id3_import_comment(const struct id3_tag * tag,const char * id,TagType type,TagHandler & handler)183 tag_id3_import_comment(const struct id3_tag *tag, const char *id, TagType type,
184 		       TagHandler &handler) noexcept
185 {
186 	const struct id3_frame *frame;
187 	for (unsigned i = 0;
188 	     (frame = id3_tag_findframe(tag, id, i)) != nullptr; ++i)
189 		tag_id3_import_comment_frame(frame, type,
190 					     handler);
191 }
192 
193 /**
194  * Parse a TXXX name, and convert it to a TagType enum value.
195  * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood.
196  */
197 gcc_pure
198 static TagType
tag_id3_parse_txxx_name(const char * name)199 tag_id3_parse_txxx_name(const char *name) noexcept
200 {
201 
202 	return tag_table_lookup(musicbrainz_txxx_tags, name);
203 }
204 
205 /**
206  * Import all known MusicBrainz tags from TXXX frames.
207  */
208 static void
tag_id3_import_musicbrainz(const struct id3_tag * id3_tag,TagHandler & handler)209 tag_id3_import_musicbrainz(const struct id3_tag *id3_tag,
210 			   TagHandler &handler) noexcept
211 {
212 	for (unsigned i = 0;; ++i) {
213 		const id3_frame *frame = id3_tag_findframe(id3_tag, "TXXX", i);
214 		if (frame == nullptr)
215 			break;
216 
217 		const auto name = tag_id3_getstring(frame, 1);
218 		if (!name)
219 			continue;
220 
221 		const auto value = tag_id3_getstring(frame, 2);
222 		if (!value)
223 			continue;
224 
225 		handler.OnPair(name.c_str(), value.c_str());
226 
227 		TagType type = tag_id3_parse_txxx_name(name.c_str());
228 
229 		if (type != TAG_NUM_OF_ITEM_TYPES)
230 			handler.OnTag(type, value.c_str());
231 	}
232 }
233 
234 /**
235  * Imports the MusicBrainz TrackId from the UFID tag.
236  */
237 static void
tag_id3_import_ufid(const struct id3_tag * id3_tag,TagHandler & handler)238 tag_id3_import_ufid(const struct id3_tag *id3_tag,
239 		    TagHandler &handler) noexcept
240 {
241 	for (unsigned i = 0;; ++i) {
242 		const id3_frame *frame = id3_tag_findframe(id3_tag, "UFID", i);
243 		if (frame == nullptr)
244 			break;
245 
246 		id3_field *field = id3_frame_field(frame, 0);
247 		if (field == nullptr)
248 			continue;
249 
250 		const id3_latin1_t *name = id3_field_getlatin1(field);
251 		if (name == nullptr ||
252 		    strcmp((const char *)name, "http://musicbrainz.org") != 0)
253 			continue;
254 
255 		field = id3_frame_field(frame, 1);
256 		if (field == nullptr)
257 			continue;
258 
259 		id3_length_t length;
260 		const id3_byte_t *value =
261 			id3_field_getbinarydata(field, &length);
262 		if (value == nullptr || length == 0)
263 			continue;
264 
265 		handler.OnTag(TAG_MUSICBRAINZ_TRACKID,
266 			      {(const char *)value, length});
267 	}
268 }
269 
270 /**
271  * Handle "APIC" ("attached picture") tags.
272  */
273 static void
tag_id3_handle_apic(const struct id3_tag * id3_tag,TagHandler & handler)274 tag_id3_handle_apic(const struct id3_tag *id3_tag,
275 		    TagHandler &handler) noexcept
276 {
277 	if (!handler.WantPicture())
278 		return;
279 
280 	for (unsigned i = 0;; ++i) {
281 		const id3_frame *frame = id3_tag_findframe(id3_tag, "APIC", i);
282 		if (frame == nullptr)
283 			break;
284 
285 		id3_field *mime_type_field = id3_frame_field(frame, 1);
286 		if (mime_type_field == nullptr)
287 			continue;
288 
289 		const char *mime_type = (const char *)
290 			id3_field_getlatin1(mime_type_field);
291 		if (mime_type != nullptr &&
292 		    StringIsEqual(mime_type, "-->"))
293 			/* this is a URL, not image data */
294 			continue;
295 
296 		id3_field *data_field = id3_frame_field(frame, 4);
297 		if (data_field == nullptr ||
298 		    data_field->type != ID3_FIELD_TYPE_BINARYDATA)
299 			continue;
300 
301 		id3_length_t size;
302 		const id3_byte_t *data =
303 			id3_field_getbinarydata(data_field, &size);
304 		if (data == nullptr || size == 0)
305 			continue;
306 
307 		handler.OnPicture(mime_type, {data, size});
308 	}
309 }
310 
311 void
scan_id3_tag(const struct id3_tag * tag,TagHandler & handler)312 scan_id3_tag(const struct id3_tag *tag, TagHandler &handler) noexcept
313 {
314 	tag_id3_import_text(tag, ID3_FRAME_ARTIST, TAG_ARTIST,
315 			    handler);
316 	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST,
317 			    TAG_ALBUM_ARTIST, handler);
318 	tag_id3_import_text(tag, ID3_FRAME_ARTIST_SORT,
319 			    TAG_ARTIST_SORT, handler);
320 
321 	tag_id3_import_text(tag, "TSOA", TAG_ALBUM_SORT, handler);
322 
323 	tag_id3_import_text(tag, ID3_FRAME_ALBUM_ARTIST_SORT,
324 			    TAG_ALBUM_ARTIST_SORT, handler);
325 	tag_id3_import_text(tag, ID3_FRAME_TITLE, TAG_TITLE,
326 			    handler);
327 	tag_id3_import_text(tag, ID3_FRAME_ALBUM, TAG_ALBUM,
328 			    handler);
329 	tag_id3_import_text(tag, ID3_FRAME_TRACK, TAG_TRACK,
330 			    handler);
331 	tag_id3_import_text(tag, ID3_FRAME_YEAR, TAG_DATE,
332 			    handler);
333 	tag_id3_import_text(tag, ID3_FRAME_ORIGINAL_RELEASE_DATE, TAG_ORIGINAL_DATE,
334 			    handler);
335 	tag_id3_import_text(tag, ID3_FRAME_GENRE, TAG_GENRE,
336 			    handler);
337 	tag_id3_import_text(tag, ID3_FRAME_COMPOSER, TAG_COMPOSER,
338 			    handler);
339 	tag_id3_import_text(tag, "TPE3", TAG_CONDUCTOR,
340 			    handler);
341 	tag_id3_import_text(tag, "TPE4", TAG_PERFORMER, handler);
342 	tag_id3_import_text(tag, "TIT1", TAG_GROUPING, handler);
343 	tag_id3_import_comment(tag, ID3_FRAME_COMMENT, TAG_COMMENT,
344 			       handler);
345 	tag_id3_import_text(tag, ID3_FRAME_DISC, TAG_DISC,
346 			    handler);
347 	tag_id3_import_text(tag, ID3_FRAME_LABEL, TAG_LABEL,
348 			    handler);
349 
350 	tag_id3_import_musicbrainz(tag, handler);
351 	tag_id3_import_ufid(tag, handler);
352 	tag_id3_handle_apic(tag, handler);
353 }
354 
355 Tag
tag_id3_import(const struct id3_tag * tag)356 tag_id3_import(const struct id3_tag *tag) noexcept
357 {
358 	TagBuilder tag_builder;
359 	AddTagHandler h(tag_builder);
360 	scan_id3_tag(tag, h);
361 	return tag_builder.Commit();
362 }
363 
364 bool
tag_id3_scan(InputStream & is,TagHandler & handler)365 tag_id3_scan(InputStream &is, TagHandler &handler)
366 {
367 	auto tag = tag_id3_load(is);
368 	if (!tag)
369 		return false;
370 
371 	scan_id3_tag(tag.get(), handler);
372 	return true;
373 }
374