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