upload tizen1.0 source
[framework/multimedia/gst-plugins-good0.10.git] / ext / taglib / gstid3v2mux.cc
1 /* GStreamer taglib-based ID3v2 muxer
2  * Copyright (C) 2006 Christophe Fergeau <teuf@gnome.org>
3  * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library 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 GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  */
20
21 /**
22  * SECTION:element-id3v2mux
23  * @see_also: #GstID3Demux, #GstTagSetter
24  *
25  * This element adds ID3v2 tags to the beginning of a stream using the taglib
26  * library. More precisely, the tags written are ID3 version 2.4.0 tags (which
27  * means in practice that some hardware players or outdated programs might not
28  * be able to read them properly).
29  *
30  * Applications can set the tags to write using the #GstTagSetter interface.
31  * Tags sent by upstream elements will be picked up automatically (and merged
32  * according to the merge mode set via the tag setter interface).
33  *
34  * <refsect2>
35  * <title>Example pipelines</title>
36  * |[
37  * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3v2mux ! filesink location=foo.mp3
38  * ]| A pipeline that transcodes a file from Ogg/Vorbis to mp3 format with an
39  * ID3v2 that contains the same as the the Ogg/Vorbis file. Make sure the
40  * Ogg/Vorbis file actually has comments to preserve.
41  * |[
42  * gst-launch -m filesrc location=foo.mp3 ! id3demux ! fakesink silent=TRUE 2&gt; /dev/null | grep taglist
43  * ]| Verify that tags have been written.
44  * </refsect2>
45  */
46
47 #ifdef HAVE_CONFIG_H
48 #include <config.h>
49 #endif
50
51 #include "gstid3v2mux.h"
52
53 #include <string.h>
54
55 #include <textidentificationframe.h>
56 #include <uniquefileidentifierframe.h>
57 #include <attachedpictureframe.h>
58 #include <relativevolumeframe.h>
59 #include <commentsframe.h>
60 #include <unknownframe.h>
61 #include <id3v2synchdata.h>
62 #include <id3v2tag.h>
63 #include <gst/tag/tag.h>
64
65 using namespace TagLib;
66
67 GST_DEBUG_CATEGORY_STATIC (gst_id3v2_mux_debug);
68 #define GST_CAT_DEFAULT gst_id3v2_mux_debug
69
70 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
71     GST_PAD_SRC,
72     GST_PAD_ALWAYS,
73     GST_STATIC_CAPS ("application/x-id3"));
74
75
76 GST_BOILERPLATE (GstId3v2Mux, gst_id3v2_mux, GstTagLibMux,
77     GST_TYPE_TAG_LIB_MUX);
78
79 static GstBuffer *gst_id3v2_mux_render_tag (GstTagLibMux * mux,
80     GstTagList * taglist);
81
82 static void
83 gst_id3v2_mux_base_init (gpointer g_class)
84 {
85   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
86
87   gst_element_class_add_pad_template (element_class,
88       gst_static_pad_template_get (&src_template));
89
90   gst_element_class_set_details_simple (element_class,
91       "TagLib-based ID3v2 Muxer", "Formatter/Metadata",
92       "Adds an ID3v2 header to the beginning of MP3 files using taglib",
93       "Christophe Fergeau <teuf@gnome.org>");
94
95   GST_DEBUG_CATEGORY_INIT (gst_id3v2_mux_debug, "id3v2mux", 0,
96       "taglib-based ID3v2 tag muxer");
97 }
98
99 static void
100 gst_id3v2_mux_class_init (GstId3v2MuxClass * klass)
101 {
102   GST_TAG_LIB_MUX_CLASS (klass)->render_tag =
103       GST_DEBUG_FUNCPTR (gst_id3v2_mux_render_tag);
104 }
105
106 static void
107 gst_id3v2_mux_init (GstId3v2Mux * id3v2mux, GstId3v2MuxClass * id3v2mux_class)
108 {
109   /* nothing to do */
110 }
111
112 #if 0
113 static void
114 add_one_txxx_tag (ID3v2::Tag * id3v2tag, const gchar * key, const gchar * val)
115 {
116   ID3v2::UserTextIdentificationFrame * frame;
117
118   if (val == NULL)
119     return;
120
121   GST_DEBUG ("Setting %s to %s", key, val);
122   frame = new ID3v2::UserTextIdentificationFrame (String::UTF8);
123   id3v2tag->addFrame (frame);
124   frame->setDescription (key);
125   frame->setText (val);
126 }
127 #endif
128
129 typedef void (*GstId3v2MuxAddTagFunc) (ID3v2::Tag * id3v2tag,
130     const GstTagList * list,
131     const gchar * tag, guint num_tags, const gchar * data);
132
133 static void
134 add_encoder_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
135     const gchar * tag, guint num_tags, const gchar * unused)
136 {
137   TagLib::StringList string_list;
138   guint n;
139
140   /* ENCODER_VERSION is either handled with the ENCODER tag or not at all */
141   if (strcmp (tag, GST_TAG_ENCODER_VERSION) == 0)
142     return;
143
144   for (n = 0; n < num_tags; ++n) {
145     gchar *encoder = NULL;
146
147     if (gst_tag_list_get_string_index (list, tag, n, &encoder) && encoder) {
148       guint encoder_version;
149       gchar *s;
150
151       if (gst_tag_list_get_uint_index (list, GST_TAG_ENCODER_VERSION, n,
152               &encoder_version) && encoder_version > 0) {
153         s = g_strdup_printf ("%s %u", encoder, encoder_version);
154       } else {
155         s = g_strdup (encoder);
156       }
157
158       GST_LOG ("encoder[%u] = '%s'", n, s);
159       string_list.append (String (s, String::UTF8));
160       g_free (s);
161       g_free (encoder);
162     }
163   }
164
165   if (!string_list.isEmpty ()) {
166     ID3v2::TextIdentificationFrame * f;
167
168     f = new ID3v2::TextIdentificationFrame ("TSSE", String::UTF8);
169     id3v2tag->addFrame (f);
170     f->setText (string_list);
171   } else {
172     GST_WARNING ("Empty list for tag %s, skipping", tag);
173   }
174 }
175
176 static void
177 add_date_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
178     const gchar * tag, guint num_tags, const gchar * unused)
179 {
180   TagLib::StringList string_list;
181   guint n;
182
183   GST_LOG ("Adding date frame");
184
185   for (n = 0; n < num_tags; ++n) {
186     GDate *date = NULL;
187
188     if (gst_tag_list_get_date_index (list, tag, n, &date) && date != NULL) {
189       GDateYear year;
190       gchar *s;
191
192       year = g_date_get_year (date);
193       if (year > 500 && year < 2100) {
194         s = g_strdup_printf ("%u", year);
195         GST_LOG ("%s[%u] = '%s'", tag, n, s);
196         string_list.append (String (s, String::UTF8));
197         g_free (s);
198       } else {
199         GST_WARNING ("invalid year %u, skipping", year);
200       }
201
202       g_date_free (date);
203     }
204   }
205
206   if (!string_list.isEmpty ()) {
207     ID3v2::TextIdentificationFrame * f;
208
209     f = new ID3v2::TextIdentificationFrame ("TDRC", String::UTF8);
210     id3v2tag->addFrame (f);
211     f->setText (string_list);
212   } else {
213     GST_WARNING ("Empty list for tag %s, skipping", tag);
214   }
215 }
216
217 static void
218 add_count_or_num_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
219     const gchar * tag, guint num_tags, const gchar * frame_id)
220 {
221   static const struct
222   {
223     const gchar *gst_tag;
224     const gchar *corr_count;    /* corresponding COUNT tag (if number) */
225     const gchar *corr_num;      /* corresponding NUMBER tag (if count) */
226   } corr[] = {
227     {
228     GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, NULL}, {
229     GST_TAG_TRACK_COUNT, NULL, GST_TAG_TRACK_NUMBER}, {
230     GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, NULL}, {
231     GST_TAG_ALBUM_VOLUME_COUNT, NULL, GST_TAG_ALBUM_VOLUME_NUMBER}
232   };
233   guint idx;
234
235   for (idx = 0; idx < G_N_ELEMENTS (corr); ++idx) {
236     if (strcmp (corr[idx].gst_tag, tag) == 0)
237       break;
238   }
239
240   g_assert (idx < G_N_ELEMENTS (corr));
241   g_assert (frame_id && strlen (frame_id) == 4);
242
243   if (corr[idx].corr_num == NULL) {
244     guint number;
245
246     /* number tag */
247     if (gst_tag_list_get_uint_index (list, tag, 0, &number)) {
248       ID3v2::TextIdentificationFrame * frame;
249       gchar *tag_str;
250       guint count;
251
252       if (gst_tag_list_get_uint_index (list, corr[idx].corr_count, 0, &count))
253         tag_str = g_strdup_printf ("%u/%u", number, count);
254       else
255         tag_str = g_strdup_printf ("%u", number);
256
257       GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
258       frame = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
259       id3v2tag->addFrame (frame);
260       frame->setText (tag_str);
261       g_free (tag_str);
262     }
263   } else if (corr[idx].corr_count == NULL) {
264     guint count;
265
266     /* count tag */
267     if (gst_tag_list_get_uint_index (list, corr[idx].corr_num, 0, &count)) {
268       GST_DEBUG ("%s handled with %s, skipping", tag, corr[idx].corr_num);
269     } else if (gst_tag_list_get_uint_index (list, tag, 0, &count)) {
270       ID3v2::TextIdentificationFrame * frame;
271       gchar *tag_str;
272
273       tag_str = g_strdup_printf ("0/%u", count);
274       GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
275       frame = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
276       id3v2tag->addFrame (frame);
277       frame->setText (tag_str);
278       g_free (tag_str);
279     }
280   }
281
282   if (num_tags > 1) {
283     GST_WARNING ("more than one %s, can only handle one", tag);
284   }
285 }
286
287 static void
288 add_unique_file_id_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
289     const gchar * tag, guint num_tags, const gchar * unused)
290 {
291   const gchar *origin = "http://musicbrainz.org";
292   gchar *id_str = NULL;
293
294   if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) {
295     ID3v2::UniqueFileIdentifierFrame * frame;
296
297     GST_LOG ("Adding %s (%s): %s", tag, origin, id_str);
298     frame = new ID3v2::UniqueFileIdentifierFrame (origin, id_str);
299     id3v2tag->addFrame (frame);
300     g_free (id_str);
301   }
302 }
303
304 static void
305 add_musicbrainz_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
306     const gchar * tag, guint num_tags, const gchar * data)
307 {
308   static const struct
309   {
310     const gchar gst_tag[28];
311     const gchar spec_id[28];
312     const gchar realworld_id[28];
313   } mb_ids[] = {
314     {
315     GST_TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id",
316           "musicbrainz_artistid"}, {
317     GST_TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id", "musicbrainz_albumid"}, {
318     GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "MusicBrainz Album Artist Id",
319           "musicbrainz_albumartistid"}, {
320     GST_TAG_MUSICBRAINZ_TRMID, "MusicBrainz TRM Id", "musicbrainz_trmid"}, {
321     GST_TAG_CDDA_MUSICBRAINZ_DISCID, "MusicBrainz DiscID",
322           "musicbrainz_discid"}, {
323       /* the following one is more or less made up, there seems to be little
324        * evidence that any popular application is actually putting this info
325        * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed
326        * tags' page, the second one is analogue to the vorbis/ape/flac tag. */
327     GST_TAG_CDDA_CDDB_DISCID, "CDDB DiscID", "discid"}
328   };
329   guint i, idx;
330
331   idx = (guint8) data[0];
332   g_assert (idx < G_N_ELEMENTS (mb_ids));
333
334   for (i = 0; i < num_tags; ++i) {
335     ID3v2::UserTextIdentificationFrame * frame;
336     gchar *id_str;
337
338     if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) {
339       GST_DEBUG ("Setting '%s' to '%s'", mb_ids[idx].spec_id, id_str);
340
341       /* add two frames, one with the ID the musicbrainz.org spec mentions
342        * and one with the ID that applications use in the real world */
343       frame = new ID3v2::UserTextIdentificationFrame (String::Latin1);
344       id3v2tag->addFrame (frame);
345       frame->setDescription (mb_ids[idx].spec_id);
346       frame->setText (id_str);
347
348       frame = new ID3v2::UserTextIdentificationFrame (String::Latin1);
349       id3v2tag->addFrame (frame);
350       frame->setDescription (mb_ids[idx].realworld_id);
351       frame->setText (id_str);
352
353       g_free (id_str);
354     }
355   }
356 }
357
358 static void
359 add_id3v2frame_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
360     const gchar * tag, guint num_tags, const gchar * frame_id)
361 {
362   ID3v2::FrameFactory * factory = ID3v2::FrameFactory::instance ();
363   guint i;
364
365   for (i = 0; i < num_tags; ++i) {
366     ID3v2::Frame * frame;
367     const GValue *val;
368     GstBuffer *buf;
369
370     val = gst_tag_list_get_value_index (list, tag, i);
371     buf = (GstBuffer *) gst_value_get_mini_object (val);
372
373     if (buf && GST_BUFFER_CAPS (buf)) {
374       GstStructure *s;
375       gint version = 0;
376
377       s = gst_caps_get_structure (GST_BUFFER_CAPS (buf), 0);
378       if (s && gst_structure_get_int (s, "version", &version) && version > 0) {
379         ByteVector bytes ((char *) GST_BUFFER_DATA (buf),
380             GST_BUFFER_SIZE (buf));
381
382         GST_DEBUG ("Injecting ID3v2.%u frame %u/%u of length %u and type %"
383             GST_PTR_FORMAT, version, i, num_tags, GST_BUFFER_SIZE (buf), s);
384
385         frame = factory->createFrame (bytes, (TagLib::uint) version);
386         if (frame)
387           id3v2tag->addFrame (frame);
388       }
389     }
390   }
391 }
392
393 static void
394 add_image_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
395     const gchar * tag, guint num_tags, const gchar * unused)
396 {
397   guint n;
398
399   for (n = 0; n < num_tags; ++n) {
400     const GValue *val;
401     GstBuffer *image;
402
403     GST_DEBUG ("image %u/%u", n + 1, num_tags);
404
405     val = gst_tag_list_get_value_index (list, tag, n);
406     image = (GstBuffer *) gst_value_get_mini_object (val);
407
408     if (GST_IS_BUFFER (image) && GST_BUFFER_SIZE (image) > 0 &&
409         GST_BUFFER_CAPS (image) != NULL &&
410         !gst_caps_is_empty (GST_BUFFER_CAPS (image))) {
411       const gchar *mime_type;
412       GstStructure *s;
413
414       s = gst_caps_get_structure (GST_BUFFER_CAPS (image), 0);
415       mime_type = gst_structure_get_name (s);
416       if (mime_type != NULL) {
417         ID3v2::AttachedPictureFrame * frame;
418         const gchar *desc;
419
420         if (strcmp (mime_type, "text/uri-list") == 0)
421           mime_type = "-->";
422
423         frame = new ID3v2::AttachedPictureFrame ();
424
425         GST_DEBUG ("Attaching picture of %u bytes and mime type %s",
426             GST_BUFFER_SIZE (image), mime_type);
427
428         id3v2tag->addFrame (frame);
429         frame->setPicture (ByteVector ((const char *) GST_BUFFER_DATA (image),
430                 GST_BUFFER_SIZE (image)));
431         frame->setTextEncoding (String::UTF8);
432         frame->setMimeType (mime_type);
433
434         desc = gst_structure_get_string (s, "image-description");
435         frame->setDescription ((desc) ? desc : "");
436
437         /* FIXME set image type properly from caps */
438         if (strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0) {
439           frame->setType (ID3v2::AttachedPictureFrame::FileIcon);
440         } else {
441           frame->setType (ID3v2::AttachedPictureFrame::Other);
442         }
443       }
444     } else {
445       GST_WARNING ("NULL image or no caps on image buffer (%p, caps=%"
446           GST_PTR_FORMAT ")", image, (image) ? GST_BUFFER_CAPS (image) : NULL);
447     }
448   }
449 }
450
451 static void
452 add_comment_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
453     const gchar * tag, guint num_tags, const gchar * unused)
454 {
455   TagLib::StringList string_list;
456   guint n;
457
458   GST_LOG ("Adding comment frames");
459   for (n = 0; n < num_tags; ++n) {
460     gchar *s = NULL;
461
462     if (gst_tag_list_get_string_index (list, tag, n, &s) && s != NULL) {
463       ID3v2::CommentsFrame * f;
464       gchar *desc = NULL, *val = NULL, *lang = NULL;
465
466       f = new ID3v2::CommentsFrame (String::UTF8);
467
468       if (strcmp (tag, GST_TAG_COMMENT) == 0 ||
469           !gst_tag_parse_extended_comment (s, &desc, &lang, &val, TRUE)) {
470         /* create dummy description to allow for multiple comment frames
471          * (taglib will drop comment frames if descriptions are not unique) */
472         desc = g_strdup_printf ("c%u", n);
473         val = g_strdup (s);
474       }
475
476       GST_LOG ("%s[%u] = '%s' (%s|%s|%s)", tag, n, s, GST_STR_NULL (desc),
477           GST_STR_NULL (lang), GST_STR_NULL (val));
478
479       f->setDescription (desc);
480       f->setText (val);
481       if (lang) {
482         f->setLanguage (lang);
483       }
484
485       g_free (lang);
486       g_free (desc);
487       g_free (val);
488
489       id3v2tag->addFrame (f);
490     }
491     g_free (s);
492   }
493 }
494
495 static void
496 add_text_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
497     const gchar * tag, guint num_tags, const gchar * frame_id)
498 {
499   ID3v2::TextIdentificationFrame * f;
500   TagLib::StringList string_list;
501   guint n;
502
503   GST_LOG ("Adding '%s' frame", frame_id);
504   for (n = 0; n < num_tags; ++n) {
505     gchar *s = NULL;
506
507     if (gst_tag_list_get_string_index (list, tag, n, &s) && s != NULL) {
508       GST_LOG ("%s: %s[%u] = '%s'", frame_id, tag, n, s);
509       string_list.append (String (s, String::UTF8));
510       g_free (s);
511     }
512   }
513
514   if (!string_list.isEmpty ()) {
515     f = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
516     id3v2tag->addFrame (f);
517     f->setText (string_list);
518   } else {
519     GST_WARNING ("Empty list for tag %s, skipping", tag);
520   }
521 }
522
523 static void
524 add_uri_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
525     const gchar * tag, guint num_tags, const gchar * frame_id)
526 {
527   gchar *url = NULL;
528
529   g_assert (frame_id != NULL);
530
531   /* URI tags are limited to one of each per taglist */
532   if (gst_tag_list_get_string_index (list, tag, 0, &url) && url != NULL) {
533     guint url_len;
534
535     url_len = strlen (url);
536     if (url_len > 0 && gst_uri_is_valid (url)) {
537       ID3v2::FrameFactory * factory = ID3v2::FrameFactory::instance ();
538       ID3v2::Frame * frame;
539       char *data;
540
541       data = g_new0 (char, 10 + url_len);
542
543       memcpy (data, frame_id, 4);
544       memcpy (data + 4, ID3v2::SynchData::fromUInt (url_len).data (), 4);
545       memcpy (data + 10, url, url_len);
546       ByteVector bytes (data, 10 + url_len);
547
548       g_free (data);
549
550       frame = factory->createFrame (bytes, (TagLib::uint) 4);
551       if (frame) {
552         id3v2tag->addFrame (frame);
553
554         GST_LOG ("%s: %s = '%s'", frame_id, tag, url);
555       }
556     } else {
557       GST_WARNING ("Tag %s does not contain a valid URI (%s)", tag, url);
558     }
559
560     g_free (url);
561   }
562 }
563
564 static void
565 add_relative_volume_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
566     const gchar * tag, guint num_tags, const gchar * frame_id)
567 {
568   const char *gain_tag_name;
569   const char *peak_tag_name;
570   gdouble peak_val;
571   gdouble gain_val;
572   ID3v2::RelativeVolumeFrame * frame;
573
574   frame = new ID3v2::RelativeVolumeFrame ();
575
576   /* figure out tag names and the identification string to use */
577   if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
578       strcmp (tag, GST_TAG_TRACK_GAIN) == 0) {
579     gain_tag_name = GST_TAG_TRACK_GAIN;
580     peak_tag_name = GST_TAG_TRACK_PEAK;
581     frame->setIdentification ("track");
582     GST_DEBUG ("adding track relative-volume frame");
583   } else {
584     gain_tag_name = GST_TAG_ALBUM_GAIN;
585     peak_tag_name = GST_TAG_ALBUM_PEAK;
586     frame->setIdentification ("album");
587     GST_DEBUG ("adding album relative-volume frame");
588   }
589   
590   /* find the value for the paired tag (gain, if this is peak, and
591    * vice versa).  if both tags exist, only write the frame when
592    * we're processing the peak tag.
593    */
594   if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
595       strcmp (tag, GST_TAG_ALBUM_PEAK) == 0) {
596     ID3v2::RelativeVolumeFrame::PeakVolume encoded_peak;
597     short peak_int;
598
599     gst_tag_list_get_double (list, tag, &peak_val);
600
601     if (gst_tag_list_get_tag_size (list, gain_tag_name) > 0) {
602       gst_tag_list_get_double (list, gain_tag_name, &gain_val);
603       GST_DEBUG ("setting volume adjustment %g", gain_val);
604       frame->setVolumeAdjustment (gain_val);
605     }
606
607     /* copying mutagen: always write as 16 bits for sanity. */
608     peak_int = (short)(peak_val * G_MAXSHORT);
609     encoded_peak.bitsRepresentingPeak = 16;
610     encoded_peak.peakVolume = ByteVector::fromShort(peak_int, true);
611     GST_DEBUG ("setting peak value %g", peak_val);
612     frame->setPeakVolume(encoded_peak);
613
614   } else {
615     gst_tag_list_get_double (list, tag, &gain_val);
616     GST_DEBUG ("setting volume adjustment %g", gain_val);
617     frame->setVolumeAdjustment (gain_val);
618
619     if (gst_tag_list_get_tag_size (list, peak_tag_name) != 0) {
620       GST_DEBUG ("both gain and peak tags exist, not adding frame this time around");
621       delete frame;
622       return;
623     }
624   }
625
626   id3v2tag->addFrame (frame);
627 }
628
629 static void
630 add_bpm_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
631     const gchar * tag, guint num_tags, const gchar * frame_id)
632 {
633   gdouble bpm;
634
635   if (gst_tag_list_get_double_index (list, tag, 0, &bpm)) {
636     ID3v2::TextIdentificationFrame * frame;
637     gchar *tag_str;
638
639     tag_str = g_strdup_printf ("%u", (guint)bpm);
640
641     GST_DEBUG ("Setting %s to %s", tag, tag_str);
642     frame = new ID3v2::TextIdentificationFrame ("TBPM", String::UTF8);
643     id3v2tag->addFrame (frame);
644     frame->setText (tag_str);
645     g_free (tag_str);
646   }
647 }
648
649 /* id3demux produces these for frames it cannot parse */
650 #define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame"
651
652 static const struct
653 {
654   const gchar *gst_tag;
655   const GstId3v2MuxAddTagFunc func;
656   const gchar data[5];
657 } add_funcs[] = {
658   {
659   GST_TAG_ARTIST, add_text_tag, "TPE1"}, {
660   GST_TAG_ALBUM_ARTIST, add_text_tag, "TPE2"}, {
661   GST_TAG_TITLE, add_text_tag, "TIT2"}, {
662   GST_TAG_ALBUM, add_text_tag, "TALB"}, {
663   GST_TAG_COPYRIGHT, add_text_tag, "TCOP"}, {
664   GST_TAG_COMPOSER, add_text_tag, "TCOM"}, {
665   GST_TAG_GENRE, add_text_tag, "TCON"}, {
666   GST_TAG_COMMENT, add_comment_tag, ""}, {
667   GST_TAG_EXTENDED_COMMENT, add_comment_tag, ""}, {
668   GST_TAG_DATE, add_date_tag, ""}, {
669   GST_TAG_IMAGE, add_image_tag, ""}, {
670   GST_TAG_PREVIEW_IMAGE, add_image_tag, ""}, {
671   GST_ID3_DEMUX_TAG_ID3V2_FRAME, add_id3v2frame_tag, ""}, {
672   GST_TAG_MUSICBRAINZ_ARTISTID, add_musicbrainz_tag, "\000"}, {
673   GST_TAG_MUSICBRAINZ_ALBUMID, add_musicbrainz_tag, "\001"}, {
674   GST_TAG_MUSICBRAINZ_ALBUMARTISTID, add_musicbrainz_tag, "\002"}, {
675   GST_TAG_MUSICBRAINZ_TRMID, add_musicbrainz_tag, "\003"}, {
676   GST_TAG_CDDA_MUSICBRAINZ_DISCID, add_musicbrainz_tag, "\004"}, {
677   GST_TAG_CDDA_CDDB_DISCID, add_musicbrainz_tag, "\005"}, {
678   GST_TAG_MUSICBRAINZ_TRACKID, add_unique_file_id_tag, ""}, {
679   GST_TAG_ARTIST_SORTNAME, add_text_tag, "TSOP"}, {
680   GST_TAG_ALBUM_SORTNAME, add_text_tag, "TSOA"}, {
681   GST_TAG_TITLE_SORTNAME, add_text_tag, "TSOT"}, {
682   GST_TAG_TRACK_NUMBER, add_count_or_num_tag, "TRCK"}, {
683   GST_TAG_TRACK_COUNT, add_count_or_num_tag, "TRCK"}, {
684   GST_TAG_ALBUM_VOLUME_NUMBER, add_count_or_num_tag, "TPOS"}, {
685   GST_TAG_ALBUM_VOLUME_COUNT, add_count_or_num_tag, "TPOS"}, {
686   GST_TAG_ENCODER, add_encoder_tag, ""}, {
687   GST_TAG_ENCODER_VERSION, add_encoder_tag, ""}, {
688   GST_TAG_COPYRIGHT_URI, add_uri_tag, "WCOP"}, {
689   GST_TAG_LICENSE_URI, add_uri_tag, "WCOP"}, {
690   GST_TAG_TRACK_PEAK, add_relative_volume_tag, ""}, {
691   GST_TAG_TRACK_GAIN, add_relative_volume_tag, ""}, {
692   GST_TAG_ALBUM_PEAK, add_relative_volume_tag, ""}, {
693   GST_TAG_ALBUM_GAIN, add_relative_volume_tag, ""}, {
694   GST_TAG_BEATS_PER_MINUTE, add_bpm_tag, ""}
695 };
696
697
698 static void
699 foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata)
700 {
701   ID3v2::Tag * id3v2tag = (ID3v2::Tag *) userdata;
702   TagLib::StringList string_list;
703   guint num_tags, i;
704
705   num_tags = gst_tag_list_get_tag_size (list, tag);
706
707   GST_LOG ("Processing tag %s (num=%u)", tag, num_tags);
708
709   if (num_tags > 1 && gst_tag_is_fixed (tag)) {
710     GST_WARNING ("Multiple occurences of fixed tag '%s', ignoring some", tag);
711     num_tags = 1;
712   }
713
714   for (i = 0; i < G_N_ELEMENTS (add_funcs); ++i) {
715     if (strcmp (add_funcs[i].gst_tag, tag) == 0) {
716       add_funcs[i].func (id3v2tag, list, tag, num_tags, add_funcs[i].data);
717       break;
718     }
719   }
720
721   if (i == G_N_ELEMENTS (add_funcs)) {
722     GST_WARNING ("Unsupported tag '%s' - not written", tag);
723   }
724 }
725
726 static GstBuffer *
727 gst_id3v2_mux_render_tag (GstTagLibMux * mux, GstTagList * taglist)
728 {
729   ID3v2::Tag id3v2tag;
730   ByteVector rendered_tag;
731   GstBuffer *buf;
732   guint tag_size;
733
734   /* write all strings as UTF-8 by default */
735   TagLib::ID3v2::FrameFactory::instance ()->
736       setDefaultTextEncoding (TagLib::String::UTF8);
737
738   /* Render the tag */
739   gst_tag_list_foreach (taglist, foreach_add_tag, &id3v2tag);
740
741 #if 0
742   /* Do we want to add our own signature to the tag somewhere? */
743   {
744     gchar *tag_producer_str;
745
746     tag_producer_str = g_strdup_printf ("(GStreamer id3v2mux %s, using "
747         "taglib %u.%u)", VERSION, TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION);
748     add_one_txxx_tag (id3v2tag, "tag_encoder", tag_producer_str);
749     g_free (tag_producer_str);
750   }
751 #endif
752
753   rendered_tag = id3v2tag.render ();
754   tag_size = rendered_tag.size ();
755
756   GST_LOG_OBJECT (mux, "tag size = %d bytes", tag_size);
757
758   /* Create buffer with tag */
759   buf = gst_buffer_new_and_alloc (tag_size);
760   memcpy (GST_BUFFER_DATA (buf), rendered_tag.data (), tag_size);
761   gst_buffer_set_caps (buf, GST_PAD_CAPS (mux->srcpad));
762
763   return buf;
764 }
765
766 gboolean
767 gst_id3v2_mux_plugin_init (GstPlugin * plugin)
768 {
769   if (!gst_element_register (plugin, "id3v2mux", GST_RANK_NONE,
770           GST_TYPE_ID3V2_MUX))
771     return FALSE;
772
773   gst_tag_register_musicbrainz_tags ();
774
775   return TRUE;
776 }