6ee79b0caa008953a8b138ab20533c6ff5b479e6
[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 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "gsttageditingprivate.h"
27 #include <stdlib.h>
28 #include <string.h>
29
30 static const gchar *genres[] = {
31   "Blues",
32   "Classic Rock",
33   "Country",
34   "Dance",
35   "Disco",
36   "Funk",
37   "Grunge",
38   "Hip-Hop",
39   "Jazz",
40   "Metal",
41   "New Age",
42   "Oldies",
43   "Other",
44   "Pop",
45   "R&B",
46   "Rap",
47   "Reggae",
48   "Rock",
49   "Techno",
50   "Industrial",
51   "Alternative",
52   "Ska",
53   "Death Metal",
54   "Pranks",
55   "Soundtrack",
56   "Euro-Techno",
57   "Ambient",
58   "Trip-Hop",
59   "Vocal",
60   "Jazz+Funk",
61   "Fusion",
62   "Trance",
63   "Classical",
64   "Instrumental",
65   "Acid",
66   "House",
67   "Game",
68   "Sound Clip",
69   "Gospel",
70   "Noise",
71   "Alternative Rock",
72   "Bass",
73   "Soul",
74   "Punk",
75   "Space",
76   "Meditative",
77   "Instrumental Pop",
78   "Instrumental Rock",
79   "Ethnic",
80   "Gothic",
81   "Darkwave",
82   "Techno-Industrial",
83   "Electronic",
84   "Pop-Folk",
85   "Eurodance",
86   "Dream",
87   "Southern Rock",
88   "Comedy",
89   "Cult",
90   "Gangsta",
91   "Top 40",
92   "Christian Rap",
93   "Pop/Funk",
94   "Jungle",
95   "Native American",
96   "Cabaret",
97   "New Wave",
98   "Psychadelic",
99   "Rave",
100   "Showtunes",
101   "Trailer",
102   "Lo-Fi",
103   "Tribal",
104   "Acid Punk",
105   "Acid Jazz",
106   "Polka",
107   "Retro",
108   "Musical",
109   "Rock & Roll",
110   "Hard Rock",
111   "Folk",
112   "Folk/Rock",
113   "National Folk",
114   "Swing",
115   "Fusion",
116   "Bebob",
117   "Latin",
118   "Revival",
119   "Celtic",
120   "Bluegrass",
121   "Avantgarde",
122   "Gothic Rock",
123   "Progressive Rock",
124   "Psychadelic Rock",
125   "Symphonic Rock",
126   "Slow Rock",
127   "Big Band",
128   "Chorus",
129   "Easy Listening",
130   "Acoustic",
131   "Humour",
132   "Speech",
133   "Chanson",
134   "Opera",
135   "Chamber Music",
136   "Sonata",
137   "Symphony",
138   "Booty Bass",
139   "Primus",
140   "Porn Groove",
141   "Satire",
142   "Slow Jam",
143   "Club",
144   "Tango",
145   "Samba",
146   "Folklore",
147   "Ballad",
148   "Power Ballad",
149   "Rhythmic Soul",
150   "Freestyle",
151   "Duet",
152   "Punk Rock",
153   "Drum Solo",
154   "A Capella",
155   "Euro-House",
156   "Dance Hall",
157   "Goa",
158   "Drum & Bass",
159   "Club-House",
160   "Hardcore",
161   "Terror",
162   "Indie",
163   "BritPop",
164   "Negerpunk",
165   "Polsk Punk",
166   "Beat",
167   "Christian Gangsta Rap",
168   "Heavy Metal",
169   "Black Metal",
170   "Crossover",
171   "Contemporary Christian",
172   "Christian Rock",
173   "Merengue",
174   "Salsa",
175   "Thrash Metal",
176   "Anime",
177   "Jpop",
178   "Synthpop"
179 };
180
181 static GstTagEntryMatch tag_matches[] = {
182   {GST_TAG_TITLE, "TIT2"},
183   {GST_TAG_ALBUM, "TALB"},
184   {GST_TAG_TRACK_NUMBER, "TRCK"},
185   {GST_TAG_ARTIST, "TPE1"},
186   {GST_TAG_COPYRIGHT, "TCOP"},
187   {GST_TAG_GENRE, "TCON"},
188   {GST_TAG_DATE, "TDRC"},
189   {GST_TAG_COMMENT, "COMM"},
190   {GST_TAG_ALBUM_VOLUME_NUMBER, "TPOS"},
191   {GST_TAG_DURATION, "TLEN"},
192   {NULL, NULL}
193 };
194
195 /**
196 * gst_tag_from_id3_tag:
197 * @id3_tag: ID3v2 tag to convert to GStreamer tag
198 *
199 * Looks up the GStreamer tag for a ID3v2 tag.
200 *
201 * Returns: The corresponding GStreamer tag or NULL if none exists.
202 */
203 G_CONST_RETURN gchar *
204 gst_tag_from_id3_tag (const gchar * id3_tag)
205 {
206   int i = 0;
207
208   g_return_val_if_fail (id3_tag != NULL, NULL);
209
210   while (tag_matches[i].gstreamer_tag != NULL) {
211     if (strncmp (id3_tag, tag_matches[i].original_tag, 5) == 0) {
212       return tag_matches[i].gstreamer_tag;
213     }
214     i++;
215   }
216
217   GST_INFO ("Cannot map ID3v2 tag '%c%c%c%c' to GStreamer tag",
218       id3_tag[0], id3_tag[1], id3_tag[2], id3_tag[3]);
219
220   return NULL;
221 }
222
223 /**
224 * gst_tag_to_id3_tag:
225 * @gst_tag: GStreamer tag to convert to vorbiscomment tag
226 *
227 * Looks up the ID3v2 tag for a GStreamer tag.
228 *
229 * Returns: The corresponding ID3v2 tag or NULL if none exists.
230 */
231 G_CONST_RETURN gchar *
232 gst_tag_to_id3_tag (const gchar * gst_tag)
233 {
234   int i = 0;
235
236   g_return_val_if_fail (gst_tag != NULL, NULL);
237
238   while (tag_matches[i].gstreamer_tag != NULL) {
239     if (strcmp (gst_tag, tag_matches[i].gstreamer_tag) == 0) {
240       return tag_matches[i].original_tag;
241     }
242     i++;
243   }
244   return NULL;
245 }
246
247 static void
248 gst_tag_extract_id3v1_string (GstTagList * list, const gchar * tag,
249     const gchar * start, const guint size)
250 {
251   const gchar *env;
252   gsize bytes_read;
253   gchar *utf8;
254
255   /* Should we try the charsets specified
256    * via environment variables FIRST ? */
257   if (g_utf8_validate (start, size, NULL)) {
258     utf8 = g_strndup (start, size);
259     goto beach;
260   }
261
262   env = g_getenv ("GST_ID3V1_TAG_ENCODING");
263   if (!env || *env == '\0')
264     env = g_getenv ("GST_ID3_TAG_ENCODING");
265   if (!env || *env == '\0')
266     env = g_getenv ("GST_TAG_ENCODING");
267
268   /* Try charsets specified via the environment */
269   if (env && *env != '\0') {
270     gchar **c, **csets;
271
272     csets = g_strsplit (env, G_SEARCHPATH_SEPARATOR_S, -1);
273
274     for (c = csets; c && *c; ++c) {
275       if ((utf8 =
276               g_convert (start, size, "UTF-8", *c, &bytes_read, NULL, NULL))) {
277         if (bytes_read == size) {
278           g_strfreev (csets);
279           goto beach;
280         }
281         g_free (utf8);
282         utf8 = NULL;
283       }
284     }
285   }
286   /* Try current locale (if not UTF-8) */
287   if (!g_get_charset (&env)) {
288     if ((utf8 = g_locale_to_utf8 (start, size, &bytes_read, NULL, NULL))) {
289       if (bytes_read == size) {
290         goto beach;
291       }
292       g_free (utf8);
293       utf8 = NULL;
294     }
295   }
296
297   /* Try ISO-8859-1 */
298   utf8 =
299       g_convert (start, size, "UTF-8", "ISO-8859-1", &bytes_read, NULL, NULL);
300   if (utf8 != NULL && bytes_read == size) {
301     goto beach;
302   }
303
304   g_free (utf8);
305   return;
306
307 beach:
308
309   g_strchomp (utf8);
310   if (utf8 && utf8[0] != '\0') {
311     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, tag, utf8, NULL);
312   }
313
314   g_free (utf8);
315 }
316
317 /**
318 * gst_tag_list_new_from_id3v1:
319 * @data: 128 bytes of data containing the ID3v1 tag
320 *
321 * Parses the data containing an ID3v1 tag and returns a #GstTagList from the
322 * parsed data.
323 *
324 * Returns: A new tag list or NULL if the data was not an ID3v1 tag.
325 */
326 GstTagList *
327 gst_tag_list_new_from_id3v1 (const guint8 * data)
328 {
329   guint year;
330   gchar *ystr;
331   GstTagList *list;
332
333   g_return_val_if_fail (data != NULL, NULL);
334
335   if (data[0] != 'T' || data[1] != 'A' || data[2] != 'G')
336     return NULL;
337   list = gst_tag_list_new ();
338   gst_tag_extract_id3v1_string (list, GST_TAG_TITLE, (gchar *) & data[3], 30);
339   gst_tag_extract_id3v1_string (list, GST_TAG_ARTIST, (gchar *) & data[33], 30);
340   gst_tag_extract_id3v1_string (list, GST_TAG_ALBUM, (gchar *) & data[63], 30);
341   ystr = g_strndup ((gchar *) & data[93], 4);
342   year = strtoul (ystr, NULL, 10);
343   g_free (ystr);
344   if (year > 0) {
345     GDate *date = g_date_new_dmy (1, 1, year);
346
347     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_DATE, date, NULL);
348     g_date_free (date);
349   }
350   if (data[125] == 0) {
351     gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
352         28);
353     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_TRACK_NUMBER,
354         (guint) data[126], NULL);
355   } else {
356     gst_tag_extract_id3v1_string (list, GST_TAG_COMMENT, (gchar *) & data[97],
357         30);
358   }
359   if (data[127] < gst_tag_id3_genre_count ()) {
360     gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
361         gst_tag_id3_genre_get (data[127]), NULL);
362   }
363
364   return list;
365 }
366
367 /**
368  * gst_tag_id3_genre_count:
369  *
370  * Gets the number of ID3v1 genres that can be identified. Winamp genres are 
371  * included.
372  *
373  * Returns: the number of ID3v1 genres that can be identified
374  */
375 guint
376 gst_tag_id3_genre_count (void)
377 {
378   return G_N_ELEMENTS (genres);
379 }
380
381 /**
382  * gst_tag_id3_genre_get:
383  * @id: ID of genre to query
384  *
385  * Gets the ID3v1 genre name for a given ID.
386  *
387  * Returns: the genre or NULL if no genre is associated with that ID.
388  */
389 G_CONST_RETURN gchar *
390 gst_tag_id3_genre_get (const guint id)
391 {
392   if (id >= G_N_ELEMENTS (genres))
393     return NULL;
394   return genres[id];
395 }