Split libgsttag docs into multiple sections.
[platform/upstream/gstreamer.git] / gst-libs / gst / tag / gstid3tag.c
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 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_COPYRIGHT, "TCOP"},
201   {GST_TAG_GENRE, "TCON"},
202   {GST_TAG_DATE, "TDRC"},
203   {GST_TAG_COMMENT, "COMM"},
204   {GST_TAG_ALBUM_VOLUME_NUMBER, "TPOS"},
205   {GST_TAG_DURATION, "TLEN"},
206   {NULL, NULL}
207 };
208
209 /**
210 * gst_tag_from_id3_tag:
211 * @id3_tag: ID3v2 tag to convert to GStreamer tag
212 *
213 * Looks up the GStreamer tag for a ID3v2 tag.
214 *
215 * Returns: The corresponding GStreamer tag or NULL if none exists.
216 */
217 G_CONST_RETURN gchar *
218 gst_tag_from_id3_tag (const gchar * id3_tag)
219 {
220   int i = 0;
221
222   g_return_val_if_fail (id3_tag != NULL, NULL);
223
224   while (tag_matches[i].gstreamer_tag != NULL) {
225     if (strncmp (id3_tag, tag_matches[i].original_tag, 5) == 0) {
226       return tag_matches[i].gstreamer_tag;
227     }
228     i++;
229   }
230
231   GST_INFO ("Cannot map ID3v2 tag '%c%c%c%c' to GStreamer tag",
232       id3_tag[0], id3_tag[1], id3_tag[2], id3_tag[3]);
233
234   return NULL;
235 }
236
237 /**
238 * gst_tag_to_id3_tag:
239 * @gst_tag: GStreamer tag to convert to vorbiscomment tag
240 *
241 * Looks up the ID3v2 tag for a GStreamer tag.
242 *
243 * Returns: The corresponding ID3v2 tag or NULL if none exists.
244 */
245 G_CONST_RETURN gchar *
246 gst_tag_to_id3_tag (const gchar * gst_tag)
247 {
248   int i = 0;
249
250   g_return_val_if_fail (gst_tag != NULL, NULL);
251
252   while (tag_matches[i].gstreamer_tag != NULL) {
253     if (strcmp (gst_tag, tag_matches[i].gstreamer_tag) == 0) {
254       return tag_matches[i].original_tag;
255     }
256     i++;
257   }
258   return NULL;
259 }
260
261 static void
262 gst_tag_extract_id3v1_string (GstTagList * list, const gchar * tag,
263     const gchar * start, const guint size)
264 {
265   const gchar *env;
266   gsize bytes_read;
267   gchar *utf8;
268
269   /* Should we try the charsets specified
270    * via environment variables FIRST ? */
271   if (g_utf8_validate (start, size, NULL)) {
272     utf8 = g_strndup (start, size);
273     goto beach;
274   }
275
276   env = g_getenv ("GST_ID3V1_TAG_ENCODING");
277   if (!env || *env == '\0')
278     env = g_getenv ("GST_ID3_TAG_ENCODING");
279   if (!env || *env == '\0')
280     env = g_getenv ("GST_TAG_ENCODING");
281
282   /* Try charsets specified via the environment */
283   if (env && *env != '\0') {
284     gchar **c, **csets;
285
286     csets = g_strsplit (env, G_SEARCHPATH_SEPARATOR_S, -1);
287
288     for (c = csets; c && *c; ++c) {
289       if ((utf8 =
290               g_convert (start, size, "UTF-8", *c, &bytes_read, NULL, NULL))) {
291         if (bytes_read == size) {
292           g_strfreev (csets);
293           goto beach;
294         }
295         g_free (utf8);
296         utf8 = NULL;
297       }
298     }
299   }
300   /* Try current locale (if not UTF-8) */
301   if (!g_get_charset (&env)) {
302     if ((utf8 = g_locale_to_utf8 (start, size, &bytes_read, NULL, NULL))) {
303       if (bytes_read == size) {
304         goto beach;
305       }
306       g_free (utf8);
307       utf8 = NULL;
308     }
309   }
310
311   /* Try ISO-8859-1 */
312   utf8 =
313       g_convert (start, size, "UTF-8", "ISO-8859-1", &bytes_read, NULL, NULL);
314   if (utf8 != NULL && bytes_read == size) {
315     goto beach;
316   }
317
318   g_free (utf8);
319   return;
320
321 beach:
322
323   g_strchomp (utf8);
324   if (utf8 && utf8[0] != '\0') {
325     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, utf8, NULL);
326   }
327
328   g_free (utf8);
329 }
330
331 /**
332 * gst_tag_list_new_from_id3v1:
333 * @data: 128 bytes of data containing the ID3v1 tag
334 *
335 * Parses the data containing an ID3v1 tag and returns a #GstTagList from the
336 * parsed data.
337 *
338 * Returns: A new tag list or NULL if the data was not an ID3v1 tag.
339 */
340 GstTagList *
341 gst_tag_list_new_from_id3v1 (const guint8 * data)
342 {
343   guint year;
344   gchar *ystr;
345   GstTagList *list;
346
347   g_return_val_if_fail (data != NULL, NULL);
348
349   if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G')
350     return NULL;
351   list = gst_tag_list_new ();
352   gst_tag_extract_id3v1_string (list, GST_TAG_TITLE, (gchar *) & data[3], 30);
353   gst_tag_extract_id3v1_string (list, GST_TAG_ARTIST, (gchar *) & data[33], 30);
354   gst_tag_extract_id3v1_string (list, GST_TAG_ALBUM, (gchar *) & data[63], 30);
355   ystr = g_strndup ((gchar *) & data[93], 4);
356   year = strtoul (ystr, NULL, 10);
357   g_free (ystr);
358   if (year > 0) {
359     GDate *date = g_date_new_dmy (1, 1, year);
360
361     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date, NULL);
362     g_date_free (date);
363   }
364   if (data[125] == 0) {
365     gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
366         28);
367     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_NUMBER,
368         (guint) data[126], NULL);
369   } else {
370     gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
371         30);
372   }
373   if (data[127] < gst_tag_id3_genre_count ()) {
374     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
375         gst_tag_id3_genre_get (data[127]), NULL);
376   }
377
378   return list;
379 }
380
381 /**
382  * gst_tag_id3_genre_count:
383  *
384  * Gets the number of ID3v1 genres that can be identified. Winamp genres are 
385  * included.
386  *
387  * Returns: the number of ID3v1 genres that can be identified
388  */
389 guint
390 gst_tag_id3_genre_count (void)
391 {
392   return G_N_ELEMENTS (genres);
393 }
394
395 /**
396  * gst_tag_id3_genre_get:
397  * @id: ID of genre to query
398  *
399  * Gets the ID3v1 genre name for a given ID.
400  *
401  * Returns: the genre or NULL if no genre is associated with that ID.
402  */
403 G_CONST_RETURN gchar *
404 gst_tag_id3_genre_get (const guint id)
405 {
406   if (id >= G_N_ELEMENTS (genres))
407     return NULL;
408   return genres[id];
409 }