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>
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.
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.
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.
22 * SECTION:element-id3v2mux
23 * @see_also: #GstID3Demux, #GstTagSetter
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).
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).
35 * <title>Example pipelines</title>
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.
42 * gst-launch -m filesrc location=foo.mp3 ! id3demux ! fakesink silent=TRUE 2> /dev/null | grep taglist
43 * ]| Verify that tags have been written.
51 #include "gstid3v2mux.h"
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>
63 #include <gst/tag/tag.h>
65 using namespace TagLib;
67 GST_DEBUG_CATEGORY_STATIC (gst_id3v2_mux_debug);
68 #define GST_CAT_DEFAULT gst_id3v2_mux_debug
70 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
73 GST_STATIC_CAPS ("application/x-id3"));
75 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
78 GST_STATIC_CAPS ("ANY"));
80 GST_BOILERPLATE (GstId3v2Mux, gst_id3v2_mux, GstTagMux, GST_TYPE_TAG_MUX);
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);
88 gst_id3v2_mux_base_init (gpointer g_class)
90 GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
92 gst_element_class_add_static_pad_template (element_class, &sink_template);
93 gst_element_class_add_static_pad_template (element_class, &src_template);
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>");
100 GST_DEBUG_CATEGORY_INIT (gst_id3v2_mux_debug, "id3v2mux", 0,
101 "taglib-based ID3v2 tag muxer");
105 gst_id3v2_mux_class_init (GstId3v2MuxClass * klass)
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);
114 gst_id3v2_mux_init (GstId3v2Mux * id3v2mux, GstId3v2MuxClass * id3v2mux_class)
121 add_one_txxx_tag (ID3v2::Tag * id3v2tag, const gchar * key, const gchar * val)
123 ID3v2::UserTextIdentificationFrame * frame;
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);
136 typedef void (*GstId3v2MuxAddTagFunc) (ID3v2::Tag * id3v2tag,
137 const GstTagList * list,
138 const gchar * tag, guint num_tags, const gchar * data);
141 add_encoder_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
142 const gchar * tag, guint num_tags, const gchar * unused)
144 TagLib::StringList string_list;
147 /* ENCODER_VERSION is either handled with the ENCODER tag or not at all */
148 if (strcmp (tag, GST_TAG_ENCODER_VERSION) == 0)
151 for (n = 0; n < num_tags; ++n) {
152 gchar *encoder = NULL;
154 if (gst_tag_list_get_string_index (list, tag, n, &encoder) && encoder) {
155 guint encoder_version;
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);
162 s = g_strdup (encoder);
165 GST_LOG ("encoder[%u] = '%s'", n, s);
166 string_list.append (String (s, String::UTF8));
172 if (!string_list.isEmpty ()) {
173 ID3v2::TextIdentificationFrame * f;
175 f = new ID3v2::TextIdentificationFrame ("TSSE", String::UTF8);
176 id3v2tag->addFrame (f);
177 f->setText (string_list);
179 GST_WARNING ("Empty list for tag %s, skipping", tag);
184 add_date_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
185 const gchar * tag, guint num_tags, const gchar * unused)
187 TagLib::StringList string_list;
190 GST_LOG ("Adding date frame");
192 for (n = 0; n < num_tags; ++n) {
195 if (gst_tag_list_get_date_index (list, tag, n, &date) && date != NULL) {
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));
206 GST_WARNING ("invalid year %u, skipping", year);
213 if (!string_list.isEmpty ()) {
214 ID3v2::TextIdentificationFrame * f;
216 f = new ID3v2::TextIdentificationFrame ("TDRC", String::UTF8);
217 id3v2tag->addFrame (f);
218 f->setText (string_list);
220 GST_WARNING ("Empty list for tag %s, skipping", tag);
225 add_count_or_num_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
226 const gchar * tag, guint num_tags, const gchar * frame_id)
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) */
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}
242 for (idx = 0; idx < G_N_ELEMENTS (corr); ++idx) {
243 if (strcmp (corr[idx].gst_tag, tag) == 0)
247 g_assert (idx < G_N_ELEMENTS (corr));
248 g_assert (frame_id && strlen (frame_id) == 4);
250 if (corr[idx].corr_num == NULL) {
254 if (gst_tag_list_get_uint_index (list, tag, 0, &number)) {
255 ID3v2::TextIdentificationFrame * frame;
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);
262 tag_str = g_strdup_printf ("%u", number);
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);
270 } else if (corr[idx].corr_count == NULL) {
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;
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);
290 GST_WARNING ("more than one %s, can only handle one", tag);
295 add_unique_file_id_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
296 const gchar * tag, guint num_tags, const gchar * unused)
298 const gchar *origin = "http://musicbrainz.org";
299 gchar *id_str = NULL;
301 if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) {
302 ID3v2::UniqueFileIdentifierFrame * frame;
304 GST_LOG ("Adding %s (%s): %s", tag, origin, id_str);
305 frame = new ID3v2::UniqueFileIdentifierFrame (origin, id_str);
306 id3v2tag->addFrame (frame);
312 add_musicbrainz_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
313 const gchar * tag, guint num_tags, const gchar * data)
317 const gchar gst_tag[28];
318 const gchar spec_id[28];
319 const gchar realworld_id[28];
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"}
338 idx = (guint8) data[0];
339 g_assert (idx < G_N_ELEMENTS (mb_ids));
341 for (i = 0; i < num_tags; ++i) {
342 ID3v2::UserTextIdentificationFrame * frame;
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);
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);
355 frame = new ID3v2::UserTextIdentificationFrame (String::Latin1);
356 id3v2tag->addFrame (frame);
357 frame->setDescription (mb_ids[idx].realworld_id);
358 frame->setText (id_str);
366 add_id3v2frame_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
367 const gchar * tag, guint num_tags, const gchar * frame_id)
369 ID3v2::FrameFactory * factory = ID3v2::FrameFactory::instance ();
372 for (i = 0; i < num_tags; ++i) {
373 ID3v2::Frame * frame;
377 val = gst_tag_list_get_value_index (list, tag, i);
378 buf = (GstBuffer *) gst_value_get_mini_object (val);
380 if (buf && GST_BUFFER_CAPS (buf)) {
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));
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);
392 frame = factory->createFrame (bytes, (TagLib::uint) version);
394 id3v2tag->addFrame (frame);
401 add_image_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
402 const gchar * tag, guint num_tags, const gchar * unused)
406 for (n = 0; n < num_tags; ++n) {
410 GST_DEBUG ("image %u/%u", n + 1, num_tags);
412 val = gst_tag_list_get_value_index (list, tag, n);
413 image = (GstBuffer *) gst_value_get_mini_object (val);
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;
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;
427 if (strcmp (mime_type, "text/uri-list") == 0)
430 frame = new ID3v2::AttachedPictureFrame ();
432 GST_DEBUG ("Attaching picture of %u bytes and mime type %s",
433 GST_BUFFER_SIZE (image), mime_type);
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);
441 desc = gst_structure_get_string (s, "image-description");
442 frame->setDescription ((desc) ? desc : "");
444 /* FIXME set image type properly from caps */
445 if (strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0) {
446 frame->setType (ID3v2::AttachedPictureFrame::FileIcon);
448 frame->setType (ID3v2::AttachedPictureFrame::Other);
452 GST_WARNING ("NULL image or no caps on image buffer (%p, caps=%"
453 GST_PTR_FORMAT ")", image, (image) ? GST_BUFFER_CAPS (image) : NULL);
459 add_comment_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
460 const gchar * tag, guint num_tags, const gchar * unused)
462 TagLib::StringList string_list;
465 GST_LOG ("Adding comment frames");
466 for (n = 0; n < num_tags; ++n) {
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;
473 f = new ID3v2::CommentsFrame (String::UTF8);
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);
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));
486 f->setDescription (desc);
489 f->setLanguage (lang);
496 id3v2tag->addFrame (f);
503 add_text_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
504 const gchar * tag, guint num_tags, const gchar * frame_id)
506 ID3v2::TextIdentificationFrame * f;
507 TagLib::StringList string_list;
510 GST_LOG ("Adding '%s' frame", frame_id);
511 for (n = 0; n < num_tags; ++n) {
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));
521 if (!string_list.isEmpty ()) {
522 f = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
523 id3v2tag->addFrame (f);
524 f->setText (string_list);
526 GST_WARNING ("Empty list for tag %s, skipping", tag);
531 add_uri_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
532 const gchar * tag, guint num_tags, const gchar * frame_id)
536 g_assert (frame_id != NULL);
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) {
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;
548 data = g_new0 (char, 10 + url_len);
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);
557 frame = factory->createFrame (bytes, (TagLib::uint) 4);
559 id3v2tag->addFrame (frame);
561 GST_LOG ("%s: %s = '%s'", frame_id, tag, url);
564 GST_WARNING ("Tag %s does not contain a valid URI (%s)", tag, url);
572 add_relative_volume_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
573 const gchar * tag, guint num_tags, const gchar * frame_id)
575 const char *gain_tag_name;
576 const char *peak_tag_name;
579 ID3v2::RelativeVolumeFrame * frame;
581 frame = new ID3v2::RelativeVolumeFrame ();
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");
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");
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.
601 if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
602 strcmp (tag, GST_TAG_ALBUM_PEAK) == 0) {
603 ID3v2::RelativeVolumeFrame::PeakVolume encoded_peak;
606 gst_tag_list_get_double (list, tag, &peak_val);
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);
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);
622 gst_tag_list_get_double (list, tag, &gain_val);
623 GST_DEBUG ("setting volume adjustment %g", gain_val);
624 frame->setVolumeAdjustment (gain_val);
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");
633 id3v2tag->addFrame (frame);
637 add_bpm_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
638 const gchar * tag, guint num_tags, const gchar * frame_id)
642 if (gst_tag_list_get_double_index (list, tag, 0, &bpm)) {
643 ID3v2::TextIdentificationFrame * frame;
646 tag_str = g_strdup_printf ("%u", (guint)bpm);
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);
656 /* id3demux produces these for frames it cannot parse */
657 #define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame"
661 const gchar *gst_tag;
662 const GstId3v2MuxAddTagFunc func;
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, ""}
706 foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata)
708 ID3v2::Tag * id3v2tag = (ID3v2::Tag *) userdata;
709 TagLib::StringList string_list;
712 num_tags = gst_tag_list_get_tag_size (list, tag);
714 GST_LOG ("Processing tag %s (num=%u)", tag, num_tags);
716 if (num_tags > 1 && gst_tag_is_fixed (tag)) {
717 GST_WARNING ("Multiple occurences of fixed tag '%s', ignoring some", tag);
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);
728 if (i == G_N_ELEMENTS (add_funcs)) {
729 GST_WARNING ("Unsupported tag '%s' - not written", tag);
734 gst_id3v2_mux_render_tag (GstTagMux * mux, const GstTagList * taglist)
737 ByteVector rendered_tag;
742 /* write all strings as UTF-8 by default */
743 TagLib::ID3v2::FrameFactory::instance ()->
744 setDefaultTextEncoding (TagLib::String::UTF8);
747 gst_tag_list_foreach (taglist, foreach_add_tag, &id3v2tag);
750 /* Do we want to add our own signature to the tag somewhere? */
752 gchar *tag_producer_str;
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);
761 rendered_tag = id3v2tag.render ();
762 tag_size = rendered_tag.size ();
764 GST_LOG_OBJECT (mux, "tag size = %d bytes", tag_size);
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);
770 caps = gst_static_pad_template_get_caps (&src_template);
771 gst_buffer_set_caps (buf, caps);
772 gst_caps_unref (caps);
778 gst_id3v2_mux_render_end_tag (GstTagMux * mux, const GstTagList * taglist)