2 * Copyright (C) 2003 Benjamin Otte <in7y118@public.uni-hamburg.de>
4 * gstid3tag.c: plugin for reading / modifying id3 tags
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.
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.
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.
24 * @short_description: tag mappings and support functions for plugins
25 * dealing with ID3v1 and ID3v2 tags
26 * @see_also: #GstTagList
30 * Contains various utility functions for plugins to parse or create
31 * ID3 tags and map ID3v2 identifiers to and from GStreamer identifiers.
40 #include "gsttageditingprivate.h"
44 static const gchar *genres[] = {
181 "Christian Gangsta Rap",
185 "Contemporary Christian",
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_ALBUM_ARTIST, "TPE2"},
201 {GST_TAG_COMPOSER, "TCOM"},
202 {GST_TAG_COPYRIGHT, "TCOP"},
203 {GST_TAG_COPYRIGHT_URI, "WCOP"},
204 {GST_TAG_ENCODED_BY, "TENC"},
205 {GST_TAG_GENRE, "TCON"},
206 {GST_TAG_DATE, "TDRC"},
207 {GST_TAG_COMMENT, "COMM"},
208 {GST_TAG_ALBUM_VOLUME_NUMBER, "TPOS"},
209 {GST_TAG_DURATION, "TLEN"},
210 {GST_TAG_ISRC, "TSRC"},
211 {GST_TAG_IMAGE, "APIC"},
212 {GST_TAG_ENCODER, "TSSE"},
213 {GST_TAG_BEATS_PER_MINUTE, "TBPM"},
214 {GST_TAG_ARTIST_SORTNAME, "TSOP"},
215 {GST_TAG_ALBUM_SORTNAME, "TSOA"},
216 {GST_TAG_TITLE_SORTNAME, "TSOT"},
221 * gst_tag_from_id3_tag:
222 * @id3_tag: ID3v2 tag to convert to GStreamer tag
224 * Looks up the GStreamer tag for a ID3v2 tag.
226 * Returns: The corresponding GStreamer tag or NULL if none exists.
228 G_CONST_RETURN gchar *
229 gst_tag_from_id3_tag (const gchar * id3_tag)
233 g_return_val_if_fail (id3_tag != NULL, NULL);
235 while (tag_matches[i].gstreamer_tag != NULL) {
236 if (strncmp (id3_tag, tag_matches[i].original_tag, 5) == 0) {
237 return tag_matches[i].gstreamer_tag;
242 GST_INFO ("Cannot map ID3v2 tag '%c%c%c%c' to GStreamer tag",
243 id3_tag[0], id3_tag[1], id3_tag[2], id3_tag[3]);
248 static const GstTagEntryMatch user_tag_matches[] = {
249 /* musicbrainz identifiers being used in the real world (foobar2000) */
250 {GST_TAG_MUSICBRAINZ_ARTISTID, "TXXX|musicbrainz_artistid"},
251 {GST_TAG_MUSICBRAINZ_ALBUMID, "TXXX|musicbrainz_albumid"},
252 {GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "TXXX|musicbrainz_albumartistid"},
253 {GST_TAG_MUSICBRAINZ_TRMID, "TXXX|musicbrainz_trmid"},
254 {GST_TAG_CDDA_MUSICBRAINZ_DISCID, "TXXX|musicbrainz_discid"},
255 /* musicbrainz identifiers according to spec no one pays
256 * attention to (http://musicbrainz.org/docs/specs/metadata_tags.html) */
257 {GST_TAG_MUSICBRAINZ_ARTISTID, "TXXX|MusicBrainz Artist Id"},
258 {GST_TAG_MUSICBRAINZ_ALBUMID, "TXXX|MusicBrainz Album Id"},
259 {GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "TXXX|MusicBrainz Album Artist Id"},
260 {GST_TAG_MUSICBRAINZ_TRMID, "TXXX|MusicBrainz TRM Id"},
261 /* according to: http://wiki.musicbrainz.org/MusicBrainzTag (yes, no space
262 * before 'ID' and not 'Id' either this time, yay for consistency) */
263 {GST_TAG_CDDA_MUSICBRAINZ_DISCID, "TXXX|MusicBrainz DiscID"},
264 /* foobar2000 uses these identifiers to store gain/peak information in
265 * ID3v2 tags <= v2.3.0. In v2.4.0 there's the RVA2 frame for that */
266 {GST_TAG_TRACK_GAIN, "TXXX|replaygain_track_gain"},
267 {GST_TAG_TRACK_PEAK, "TXXX|replaygain_track_peak"},
268 {GST_TAG_ALBUM_GAIN, "TXXX|replaygain_album_gain"},
269 {GST_TAG_ALBUM_PEAK, "TXXX|replaygain_album_peak"},
270 /* the following two are more or less made up, there seems to be little
271 * evidence that any popular application is actually putting this info
272 * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed
273 * tags' page, the second one is analogue to the vorbis/ape/flac tag. */
274 {GST_TAG_CDDA_CDDB_DISCID, "TXXX|discid"},
275 {GST_TAG_CDDA_CDDB_DISCID, "TXXX|CDDB DiscID"}
279 * gst_tag_from_id3_user_tag:
280 * @type: the type of ID3v2 user tag (e.g. "TXXX" or "UDIF")
281 * @id3_user_tag: ID3v2 user tag to convert to GStreamer tag
283 * Looks up the GStreamer tag for an ID3v2 user tag (e.g. description in
284 * TXXX frame or owner in UFID frame).
286 * Returns: The corresponding GStreamer tag or NULL if none exists.
288 G_CONST_RETURN gchar *
289 gst_tag_from_id3_user_tag (const gchar * type, const gchar * id3_user_tag)
293 g_return_val_if_fail (type != NULL && strlen (type) == 4, NULL);
294 g_return_val_if_fail (id3_user_tag != NULL, NULL);
296 for (i = 0; i < G_N_ELEMENTS (user_tag_matches); ++i) {
297 if (strncmp (type, user_tag_matches[i].original_tag, 4) == 0 &&
298 g_ascii_strcasecmp (id3_user_tag,
299 user_tag_matches[i].original_tag + 5) == 0) {
300 GST_LOG ("Mapped ID3v2 user tag '%s' to GStreamer tag '%s'",
301 user_tag_matches[i].original_tag, user_tag_matches[i].gstreamer_tag);
302 return user_tag_matches[i].gstreamer_tag;
306 GST_INFO ("Cannot map ID3v2 user tag '%s' of type '%s' to GStreamer tag",
313 * gst_tag_to_id3_tag:
314 * @gst_tag: GStreamer tag to convert to vorbiscomment tag
316 * Looks up the ID3v2 tag for a GStreamer tag.
318 * Returns: The corresponding ID3v2 tag or NULL if none exists.
320 G_CONST_RETURN gchar *
321 gst_tag_to_id3_tag (const gchar * gst_tag)
325 g_return_val_if_fail (gst_tag != NULL, NULL);
327 while (tag_matches[i].gstreamer_tag != NULL) {
328 if (strcmp (gst_tag, tag_matches[i].gstreamer_tag) == 0) {
329 return tag_matches[i].original_tag;
337 gst_tag_extract_id3v1_string (GstTagList * list, const gchar * tag,
338 const gchar * start, const guint size)
340 const gchar *env_vars[] = { "GST_ID3V1_TAG_ENCODING",
341 "GST_ID3_TAG_ENCODING", "GST_TAG_ENCODING", NULL
345 utf8 = gst_tag_freeform_string_to_utf8 (start, size, env_vars);
347 if (utf8 && *utf8 != '\0') {
348 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, utf8, NULL);
355 * gst_tag_list_new_from_id3v1:
356 * @data: 128 bytes of data containing the ID3v1 tag
358 * Parses the data containing an ID3v1 tag and returns a #GstTagList from the
361 * Returns: A new tag list or NULL if the data was not an ID3v1 tag.
364 gst_tag_list_new_from_id3v1 (const guint8 * data)
370 g_return_val_if_fail (data != NULL, NULL);
372 if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G')
374 list = gst_tag_list_new ();
375 gst_tag_extract_id3v1_string (list, GST_TAG_TITLE, (gchar *) & data[3], 30);
376 gst_tag_extract_id3v1_string (list, GST_TAG_ARTIST, (gchar *) & data[33], 30);
377 gst_tag_extract_id3v1_string (list, GST_TAG_ALBUM, (gchar *) & data[63], 30);
378 ystr = g_strndup ((gchar *) & data[93], 4);
379 year = strtoul (ystr, NULL, 10);
382 GDate *date = g_date_new_dmy (1, 1, year);
384 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date, NULL);
387 if (data[125] == 0 && data[126] != 0) {
388 gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
390 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_NUMBER,
391 (guint) data[126], NULL);
393 gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
396 if (data[127] < gst_tag_id3_genre_count () && !gst_tag_list_is_empty (list)) {
397 gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
398 gst_tag_id3_genre_get (data[127]), NULL);
405 * gst_tag_id3_genre_count:
407 * Gets the number of ID3v1 genres that can be identified. Winamp genres are
410 * Returns: the number of ID3v1 genres that can be identified
413 gst_tag_id3_genre_count (void)
415 return G_N_ELEMENTS (genres);
419 * gst_tag_id3_genre_get:
420 * @id: ID of genre to query
422 * Gets the ID3v1 genre name for a given ID.
424 * Returns: the genre or NULL if no genre is associated with that ID.
426 G_CONST_RETURN gchar *
427 gst_tag_id3_genre_get (const guint id)
429 if (id >= G_N_ELEMENTS (genres))
435 * gst_tag_list_add_id3_image:
436 * @tag_list: a tag list
437 * @image_data: the (encoded) image
438 * @image_data_len: the length of the encoded image data at @image_data
439 * @id3_picture_type: picture type as per the ID3 (v2.4.0) specification for
440 * the APIC frame (0 = unknown/other)
442 * Adds an image from an ID3 APIC frame (or similar, such as used in FLAC)
443 * to the given tag list. Also see gst_tag_image_data_to_image_buffer() for
444 * more information on image tags in GStreamer.
446 * Returns: %TRUE if the image was processed, otherwise %FALSE
451 gst_tag_list_add_id3_image (GstTagList * tag_list, const guint8 * image_data,
452 guint image_data_len, guint id3_picture_type)
454 GstTagImageType tag_image_type;
455 const gchar *tag_name;
458 g_return_val_if_fail (GST_IS_TAG_LIST (tag_list), FALSE);
459 g_return_val_if_fail (image_data != NULL, FALSE);
460 g_return_val_if_fail (image_data_len > 0, FALSE);
462 if (id3_picture_type == 0x01 || id3_picture_type == 0x02) {
463 /* file icon for preview. Don't add image-type to caps, since there
464 * is only supposed to be one of these, and the type is already indicated
465 * via the special tag */
466 tag_name = GST_TAG_PREVIEW_IMAGE;
467 tag_image_type = GST_TAG_IMAGE_TYPE_NONE;
469 tag_name = GST_TAG_IMAGE;
471 /* Remap the ID3v2 APIC type our ImageType enum */
472 if (id3_picture_type >= 0x3 && id3_picture_type <= 0x14)
473 tag_image_type = (GstTagImageType) (id3_picture_type - 2);
475 tag_image_type = GST_TAG_IMAGE_TYPE_UNDEFINED;
478 image = gst_tag_image_data_to_image_buffer (image_data, image_data_len,
484 gst_tag_list_add (tag_list, GST_TAG_MERGE_APPEND, tag_name, image, NULL);
485 gst_buffer_unref (image);