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