|
1 /* GStreamer |
|
2 * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de> |
|
3 * |
|
4 * gstid3tag.c: plugin for reading / modifying id3 tags |
|
5 * |
|
6 * This library is free software; you can redistribute it and/or |
|
7 * modify it under the terms of the GNU Library General Public |
|
8 * License as published by the Free Software Foundation; either |
|
9 * version 2 of the License, or (at your option) any later version. |
|
10 * |
|
11 * This library is distributed in the hope that it will be useful, |
|
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
14 * Library General Public License for more details. |
|
15 * |
|
16 * You should have received a copy of the GNU Library General Public |
|
17 * License along with this library; if not, write to the |
|
18 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, |
|
19 * Boston, MA 02111-1307, USA. |
|
20 */ |
|
21 |
|
22 /** |
|
23 * SECTION:gsttagid3 |
|
24 * @short_description: tag mappings and support functions for plugins |
|
25 * dealing with ID3v1 and ID3v2 tags |
|
26 * @see_also: #GstTagList |
|
27 * |
|
28 * <refsect2> |
|
29 * <para> |
|
30 * Contains various utility functions for plugins to parse or create |
|
31 * ID3 tags and map ID3v2 identifiers to and from GStreamer identifiers. |
|
32 * </para> |
|
33 * </refsect2> |
|
34 */ |
|
35 |
|
36 #ifdef HAVE_CONFIG_H |
|
37 #include "config.h" |
|
38 #endif |
|
39 |
|
40 #include "gsttageditingprivate.h" |
|
41 #include <stdlib.h> |
|
42 #include <string.h> |
|
43 |
|
44 static const gchar *genres[] = { |
|
45 "Blues", |
|
46 "Classic Rock", |
|
47 "Country", |
|
48 "Dance", |
|
49 "Disco", |
|
50 "Funk", |
|
51 "Grunge", |
|
52 "Hip-Hop", |
|
53 "Jazz", |
|
54 "Metal", |
|
55 "New Age", |
|
56 "Oldies", |
|
57 "Other", |
|
58 "Pop", |
|
59 "R&B", |
|
60 "Rap", |
|
61 "Reggae", |
|
62 "Rock", |
|
63 "Techno", |
|
64 "Industrial", |
|
65 "Alternative", |
|
66 "Ska", |
|
67 "Death Metal", |
|
68 "Pranks", |
|
69 "Soundtrack", |
|
70 "Euro-Techno", |
|
71 "Ambient", |
|
72 "Trip-Hop", |
|
73 "Vocal", |
|
74 "Jazz+Funk", |
|
75 "Fusion", |
|
76 "Trance", |
|
77 "Classical", |
|
78 "Instrumental", |
|
79 "Acid", |
|
80 "House", |
|
81 "Game", |
|
82 "Sound Clip", |
|
83 "Gospel", |
|
84 "Noise", |
|
85 "Alternative Rock", |
|
86 "Bass", |
|
87 "Soul", |
|
88 "Punk", |
|
89 "Space", |
|
90 "Meditative", |
|
91 "Instrumental Pop", |
|
92 "Instrumental Rock", |
|
93 "Ethnic", |
|
94 "Gothic", |
|
95 "Darkwave", |
|
96 "Techno-Industrial", |
|
97 "Electronic", |
|
98 "Pop-Folk", |
|
99 "Eurodance", |
|
100 "Dream", |
|
101 "Southern Rock", |
|
102 "Comedy", |
|
103 "Cult", |
|
104 "Gangsta", |
|
105 "Top 40", |
|
106 "Christian Rap", |
|
107 "Pop/Funk", |
|
108 "Jungle", |
|
109 "Native American", |
|
110 "Cabaret", |
|
111 "New Wave", |
|
112 "Psychadelic", |
|
113 "Rave", |
|
114 "Showtunes", |
|
115 "Trailer", |
|
116 "Lo-Fi", |
|
117 "Tribal", |
|
118 "Acid Punk", |
|
119 "Acid Jazz", |
|
120 "Polka", |
|
121 "Retro", |
|
122 "Musical", |
|
123 "Rock & Roll", |
|
124 "Hard Rock", |
|
125 "Folk", |
|
126 "Folk/Rock", |
|
127 "National Folk", |
|
128 "Swing", |
|
129 "Fusion", |
|
130 "Bebob", |
|
131 "Latin", |
|
132 "Revival", |
|
133 "Celtic", |
|
134 "Bluegrass", |
|
135 "Avantgarde", |
|
136 "Gothic Rock", |
|
137 "Progressive Rock", |
|
138 "Psychadelic Rock", |
|
139 "Symphonic Rock", |
|
140 "Slow Rock", |
|
141 "Big Band", |
|
142 "Chorus", |
|
143 "Easy Listening", |
|
144 "Acoustic", |
|
145 "Humour", |
|
146 "Speech", |
|
147 "Chanson", |
|
148 "Opera", |
|
149 "Chamber Music", |
|
150 "Sonata", |
|
151 "Symphony", |
|
152 "Booty Bass", |
|
153 "Primus", |
|
154 "Porn Groove", |
|
155 "Satire", |
|
156 "Slow Jam", |
|
157 "Club", |
|
158 "Tango", |
|
159 "Samba", |
|
160 "Folklore", |
|
161 "Ballad", |
|
162 "Power Ballad", |
|
163 "Rhythmic Soul", |
|
164 "Freestyle", |
|
165 "Duet", |
|
166 "Punk Rock", |
|
167 "Drum Solo", |
|
168 "A Capella", |
|
169 "Euro-House", |
|
170 "Dance Hall", |
|
171 "Goa", |
|
172 "Drum & Bass", |
|
173 "Club-House", |
|
174 "Hardcore", |
|
175 "Terror", |
|
176 "Indie", |
|
177 "BritPop", |
|
178 "Negerpunk", |
|
179 "Polsk Punk", |
|
180 "Beat", |
|
181 "Christian Gangsta Rap", |
|
182 "Heavy Metal", |
|
183 "Black Metal", |
|
184 "Crossover", |
|
185 "Contemporary Christian", |
|
186 "Christian Rock", |
|
187 "Merengue", |
|
188 "Salsa", |
|
189 "Thrash Metal", |
|
190 "Anime", |
|
191 "Jpop", |
|
192 "Synthpop" |
|
193 }; |
|
194 |
|
195 static const GstTagEntryMatch tag_matches[] = { |
|
196 {GST_TAG_TITLE, "TIT2"}, |
|
197 {GST_TAG_ALBUM, "TALB"}, |
|
198 {GST_TAG_TRACK_NUMBER, "TRCK"}, |
|
199 {GST_TAG_ARTIST, "TPE1"}, |
|
200 {GST_TAG_COMPOSER, "TCOM"}, |
|
201 {GST_TAG_COPYRIGHT, "TCOP"}, |
|
202 {GST_TAG_COPYRIGHT_URI, "WCOP"}, |
|
203 {GST_TAG_GENRE, "TCON"}, |
|
204 {GST_TAG_DATE, "TDRC"}, |
|
205 {GST_TAG_COMMENT, "COMM"}, |
|
206 {GST_TAG_ALBUM_VOLUME_NUMBER, "TPOS"}, |
|
207 {GST_TAG_DURATION, "TLEN"}, |
|
208 {GST_TAG_ISRC, "TSRC"}, |
|
209 {GST_TAG_IMAGE, "APIC"}, |
|
210 {GST_TAG_ENCODER, "TSSE"}, |
|
211 {GST_TAG_BEATS_PER_MINUTE, "TBPM"}, |
|
212 {GST_TAG_ARTIST_SORTNAME, "TSOP"}, |
|
213 {GST_TAG_ALBUM_SORTNAME, "TSOA"}, |
|
214 {GST_TAG_TITLE_SORTNAME, "TSOT"}, |
|
215 {NULL, NULL} |
|
216 }; |
|
217 |
|
218 /** |
|
219 * gst_tag_from_id3_tag: |
|
220 * @id3_tag: ID3v2 tag to convert to GStreamer tag |
|
221 * |
|
222 * Looks up the GStreamer tag for a ID3v2 tag. |
|
223 * |
|
224 * Returns: The corresponding GStreamer tag or NULL if none exists. |
|
225 */ |
|
226 #ifdef __SYMBIAN32__ |
|
227 EXPORT_C |
|
228 #endif |
|
229 |
|
230 G_CONST_RETURN gchar * |
|
231 gst_tag_from_id3_tag (const gchar * id3_tag) |
|
232 { |
|
233 int i = 0; |
|
234 |
|
235 g_return_val_if_fail (id3_tag != NULL, NULL); |
|
236 |
|
237 while (tag_matches[i].gstreamer_tag != NULL) { |
|
238 if (strncmp (id3_tag, tag_matches[i].original_tag, 5) == 0) { |
|
239 return tag_matches[i].gstreamer_tag; |
|
240 } |
|
241 i++; |
|
242 } |
|
243 |
|
244 GST_INFO ("Cannot map ID3v2 tag '%c%c%c%c' to GStreamer tag", |
|
245 id3_tag[0], id3_tag[1], id3_tag[2], id3_tag[3]); |
|
246 |
|
247 return NULL; |
|
248 } |
|
249 |
|
250 static const GstTagEntryMatch user_tag_matches[] = { |
|
251 /* musicbrainz identifiers being used in the real world (foobar2000) */ |
|
252 {GST_TAG_MUSICBRAINZ_ARTISTID, "TXXX|musicbrainz_artistid"}, |
|
253 {GST_TAG_MUSICBRAINZ_ALBUMID, "TXXX|musicbrainz_albumid"}, |
|
254 {GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "TXXX|musicbrainz_albumartistid"}, |
|
255 {GST_TAG_MUSICBRAINZ_TRMID, "TXXX|musicbrainz_trmid"}, |
|
256 {GST_TAG_CDDA_MUSICBRAINZ_DISCID, "TXXX|musicbrainz_discid"}, |
|
257 /* musicbrainz identifiers according to spec no one pays |
|
258 * attention to (http://musicbrainz.org/docs/specs/metadata_tags.html) */ |
|
259 {GST_TAG_MUSICBRAINZ_ARTISTID, "TXXX|MusicBrainz Artist Id"}, |
|
260 {GST_TAG_MUSICBRAINZ_ALBUMID, "TXXX|MusicBrainz Album Id"}, |
|
261 {GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "TXXX|MusicBrainz Album Artist Id"}, |
|
262 {GST_TAG_MUSICBRAINZ_TRMID, "TXXX|MusicBrainz TRM Id"}, |
|
263 /* according to: http://wiki.musicbrainz.org/MusicBrainzTag (yes, no space |
|
264 * before 'ID' and not 'Id' either this time, yay for consistency) */ |
|
265 {GST_TAG_CDDA_MUSICBRAINZ_DISCID, "TXXX|MusicBrainz DiscID"}, |
|
266 /* foobar2000 uses these identifiers to store gain/peak information in |
|
267 * ID3v2 tags <= v2.3.0. In v2.4.0 there's the RVA2 frame for that */ |
|
268 {GST_TAG_TRACK_GAIN, "TXXX|replaygain_track_gain"}, |
|
269 {GST_TAG_TRACK_PEAK, "TXXX|replaygain_track_peak"}, |
|
270 {GST_TAG_ALBUM_GAIN, "TXXX|replaygain_album_gain"}, |
|
271 {GST_TAG_ALBUM_PEAK, "TXXX|replaygain_album_peak"}, |
|
272 /* the following two are more or less made up, there seems to be little |
|
273 * evidence that any popular application is actually putting this info |
|
274 * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed |
|
275 * tags' page, the second one is analogue to the vorbis/ape/flac tag. */ |
|
276 {GST_TAG_CDDA_CDDB_DISCID, "TXXX|discid"}, |
|
277 {GST_TAG_CDDA_CDDB_DISCID, "TXXX|CDDB DiscID"} |
|
278 }; |
|
279 |
|
280 /** |
|
281 * gst_tag_from_id3_user_tag: |
|
282 * @type: the type of ID3v2 user tag (e.g. "TXXX" or "UDIF") |
|
283 * @id3_user_tag: ID3v2 user tag to convert to GStreamer tag |
|
284 * |
|
285 * Looks up the GStreamer tag for an ID3v2 user tag (e.g. description in |
|
286 * TXXX frame or owner in UFID frame). |
|
287 * |
|
288 * Returns: The corresponding GStreamer tag or NULL if none exists. |
|
289 */ |
|
290 #ifdef __SYMBIAN32__ |
|
291 EXPORT_C |
|
292 #endif |
|
293 |
|
294 G_CONST_RETURN gchar * |
|
295 gst_tag_from_id3_user_tag (const gchar * type, const gchar * id3_user_tag) |
|
296 { |
|
297 int i = 0; |
|
298 |
|
299 g_return_val_if_fail (type != NULL && strlen (type) == 4, NULL); |
|
300 g_return_val_if_fail (id3_user_tag != NULL, NULL); |
|
301 |
|
302 for (i = 0; i < G_N_ELEMENTS (user_tag_matches); ++i) { |
|
303 if (strncmp (type, user_tag_matches[i].original_tag, 4) == 0 && |
|
304 g_ascii_strcasecmp (id3_user_tag, |
|
305 user_tag_matches[i].original_tag + 5) == 0) { |
|
306 GST_LOG ("Mapped ID3v2 user tag '%s' to GStreamer tag '%s'", |
|
307 user_tag_matches[i].original_tag, user_tag_matches[i].gstreamer_tag); |
|
308 return user_tag_matches[i].gstreamer_tag; |
|
309 } |
|
310 } |
|
311 |
|
312 GST_INFO ("Cannot map ID3v2 user tag '%s' of type '%s' to GStreamer tag", |
|
313 id3_user_tag, type); |
|
314 |
|
315 return NULL; |
|
316 } |
|
317 |
|
318 /** |
|
319 * gst_tag_to_id3_tag: |
|
320 * @gst_tag: GStreamer tag to convert to vorbiscomment tag |
|
321 * |
|
322 * Looks up the ID3v2 tag for a GStreamer tag. |
|
323 * |
|
324 * Returns: The corresponding ID3v2 tag or NULL if none exists. |
|
325 */ |
|
326 #ifdef __SYMBIAN32__ |
|
327 EXPORT_C |
|
328 #endif |
|
329 |
|
330 G_CONST_RETURN gchar * |
|
331 gst_tag_to_id3_tag (const gchar * gst_tag) |
|
332 { |
|
333 int i = 0; |
|
334 |
|
335 g_return_val_if_fail (gst_tag != NULL, NULL); |
|
336 |
|
337 while (tag_matches[i].gstreamer_tag != NULL) { |
|
338 if (strcmp (gst_tag, tag_matches[i].gstreamer_tag) == 0) { |
|
339 return tag_matches[i].original_tag; |
|
340 } |
|
341 i++; |
|
342 } |
|
343 return NULL; |
|
344 } |
|
345 |
|
346 static void |
|
347 gst_tag_extract_id3v1_string (GstTagList * list, const gchar * tag, |
|
348 const gchar * start, const guint size) |
|
349 { |
|
350 const gchar *env_vars[] = { "GST_ID3V1_TAG_ENCODING", |
|
351 "GST_ID3_TAG_ENCODING", "GST_TAG_ENCODING", NULL |
|
352 }; |
|
353 gchar *utf8; |
|
354 |
|
355 utf8 = gst_tag_freeform_string_to_utf8 (start, size, env_vars); |
|
356 |
|
357 if (utf8 && *utf8 != '\0') { |
|
358 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, utf8, NULL); |
|
359 } |
|
360 |
|
361 g_free (utf8); |
|
362 } |
|
363 |
|
364 /** |
|
365 * gst_tag_list_new_from_id3v1: |
|
366 * @data: 128 bytes of data containing the ID3v1 tag |
|
367 * |
|
368 * Parses the data containing an ID3v1 tag and returns a #GstTagList from the |
|
369 * parsed data. |
|
370 * |
|
371 * Returns: A new tag list or NULL if the data was not an ID3v1 tag. |
|
372 */ |
|
373 #ifdef __SYMBIAN32__ |
|
374 EXPORT_C |
|
375 #endif |
|
376 |
|
377 GstTagList * |
|
378 gst_tag_list_new_from_id3v1 (const guint8 * data) |
|
379 { |
|
380 guint year; |
|
381 gchar *ystr; |
|
382 GstTagList *list; |
|
383 |
|
384 g_return_val_if_fail (data != NULL, NULL); |
|
385 |
|
386 if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G') |
|
387 return NULL; |
|
388 list = gst_tag_list_new (); |
|
389 gst_tag_extract_id3v1_string (list, GST_TAG_TITLE, (gchar *) & data[3], 30); |
|
390 gst_tag_extract_id3v1_string (list, GST_TAG_ARTIST, (gchar *) & data[33], 30); |
|
391 gst_tag_extract_id3v1_string (list, GST_TAG_ALBUM, (gchar *) & data[63], 30); |
|
392 ystr = g_strndup ((gchar *) & data[93], 4); |
|
393 year = strtoul (ystr, NULL, 10); |
|
394 g_free (ystr); |
|
395 if (year > 0) { |
|
396 GDate *date = g_date_new_dmy (1, 1, year); |
|
397 |
|
398 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date, NULL); |
|
399 g_date_free (date); |
|
400 } |
|
401 if (data[125] == 0) { |
|
402 gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97], |
|
403 28); |
|
404 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_NUMBER, |
|
405 (guint) data[126], NULL); |
|
406 } else { |
|
407 gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97], |
|
408 30); |
|
409 } |
|
410 if (data[127] < gst_tag_id3_genre_count ()) { |
|
411 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE, |
|
412 gst_tag_id3_genre_get (data[127]), NULL); |
|
413 } |
|
414 |
|
415 return list; |
|
416 } |
|
417 |
|
418 /** |
|
419 * gst_tag_id3_genre_count: |
|
420 * |
|
421 * Gets the number of ID3v1 genres that can be identified. Winamp genres are |
|
422 * included. |
|
423 * |
|
424 * Returns: the number of ID3v1 genres that can be identified |
|
425 */ |
|
426 #ifdef __SYMBIAN32__ |
|
427 EXPORT_C |
|
428 #endif |
|
429 |
|
430 guint |
|
431 gst_tag_id3_genre_count (void) |
|
432 { |
|
433 return G_N_ELEMENTS (genres); |
|
434 } |
|
435 |
|
436 /** |
|
437 * gst_tag_id3_genre_get: |
|
438 * @id: ID of genre to query |
|
439 * |
|
440 * Gets the ID3v1 genre name for a given ID. |
|
441 * |
|
442 * Returns: the genre or NULL if no genre is associated with that ID. |
|
443 */ |
|
444 #ifdef __SYMBIAN32__ |
|
445 EXPORT_C |
|
446 #endif |
|
447 |
|
448 G_CONST_RETURN gchar * |
|
449 gst_tag_id3_genre_get (const guint id) |
|
450 { |
|
451 if (id >= G_N_ELEMENTS (genres)) |
|
452 return NULL; |
|
453 return genres[id]; |
|
454 } |