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