gstid3v2mux.cc 24.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
/* GStreamer taglib-based ID3v2 muxer
 * Copyright (C) 2006 Christophe Fergeau <teuf@gnome.org>
 * Copyright (C) 2006 Tim-Philipp Müller <tim centricular net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/**
 * SECTION:element-id3v2mux
 * @see_also: #GstID3Demux, #GstTagSetter
 *
 * This element adds ID3v2 tags to the beginning of a stream using the taglib
 * library. More precisely, the tags written are ID3 version 2.4.0 tags (which
 * means in practice that some hardware players or outdated programs might not
 * be able to read them properly).
29
 *
30 31 32
 * Applications can set the tags to write using the #GstTagSetter interface.
 * Tags sent by upstream elements will be picked up automatically (and merged
 * according to the merge mode set via the tag setter interface).
33 34 35 36
 *
 * <refsect2>
 * <title>Example pipelines</title>
 * |[
37
 * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! lame ! id3v2mux ! filesink location=foo.mp3
38 39 40 41
 * ]| A pipeline that transcodes a file from Ogg/Vorbis to mp3 format with an
 * ID3v2 that contains the same as the the Ogg/Vorbis file. Make sure the
 * Ogg/Vorbis file actually has comments to preserve.
 * |[
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
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 45 46 47 48 49 50 51 52 53
 * </refsect2>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "gstid3v2mux.h"

#include <string.h>
Thomas Vander Stichele's avatar
Thomas Vander Stichele committed
54

55 56
#include <textidentificationframe.h>
#include <uniquefileidentifierframe.h>
57
#include <attachedpictureframe.h>
58
#include <relativevolumeframe.h>
59
#include <commentsframe.h>
60 61
#include <unknownframe.h>
#include <id3v2synchdata.h>
62 63 64 65 66 67 68 69 70 71 72 73 74
#include <id3v2tag.h>
#include <gst/tag/tag.h>

using namespace TagLib;

GST_DEBUG_CATEGORY_STATIC (gst_id3v2_mux_debug);
#define GST_CAT_DEFAULT gst_id3v2_mux_debug

static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-id3"));

75 76 77 78
static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("ANY"));
79

80
G_DEFINE_TYPE (GstId3v2Mux, gst_id3v2_mux, GST_TYPE_TAG_MUX);
81

82 83 84 85
static GstBuffer *gst_id3v2_mux_render_tag (GstTagMux * mux,
    const GstTagList * taglist);
static GstBuffer *gst_id3v2_mux_render_end_tag (GstTagMux * mux,
    const GstTagList * taglist);
86 87

static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
88
gst_id3v2_mux_class_init (GstId3v2MuxClass * klass)
89
{
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
90 91
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

92
  GST_TAG_MUX_CLASS (klass)->render_start_tag =
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
93
      GST_DEBUG_FUNCPTR (gst_id3v2_mux_render_tag);
94 95
  GST_TAG_MUX_CLASS (klass)->render_end_tag =
      GST_DEBUG_FUNCPTR (gst_id3v2_mux_render_end_tag);
96

97 98
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_template));
99 100 101
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_template));

102
  gst_element_class_set_static_metadata (element_class,
103 104 105
      "TagLib-based ID3v2 Muxer", "Formatter/Metadata",
      "Adds an ID3v2 header to the beginning of MP3 files using taglib",
      "Christophe Fergeau <teuf@gnome.org>");
106 107 108 109 110 111

  GST_DEBUG_CATEGORY_INIT (gst_id3v2_mux_debug, "id3v2mux", 0,
      "taglib-based ID3v2 tag muxer");
}

static void
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
112
gst_id3v2_mux_init (GstId3v2Mux * id3v2mux)
113 114 115 116
{
  /* nothing to do */
}

117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
#if 0
static void
add_one_txxx_tag (ID3v2::Tag * id3v2tag, const gchar * key, const gchar * val)
{
  ID3v2::UserTextIdentificationFrame * frame;

  if (val == NULL)
    return;

  GST_DEBUG ("Setting %s to %s", key, val);
  frame = new ID3v2::UserTextIdentificationFrame (String::UTF8);
  id3v2tag->addFrame (frame);
  frame->setDescription (key);
  frame->setText (val);
}
#endif

134 135 136 137
typedef void (*GstId3v2MuxAddTagFunc) (ID3v2::Tag * id3v2tag,
    const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * data);

138
static void
139 140
add_encoder_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * unused)
141
{
142 143
  TagLib::StringList string_list;
  guint n;
144

145 146 147
  /* ENCODER_VERSION is either handled with the ENCODER tag or not at all */
  if (strcmp (tag, GST_TAG_ENCODER_VERSION) == 0)
    return;
148

149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
  for (n = 0; n < num_tags; ++n) {
    gchar *encoder = NULL;

    if (gst_tag_list_get_string_index (list, tag, n, &encoder) && encoder) {
      guint encoder_version;
      gchar *s;

      if (gst_tag_list_get_uint_index (list, GST_TAG_ENCODER_VERSION, n,
              &encoder_version) && encoder_version > 0) {
        s = g_strdup_printf ("%s %u", encoder, encoder_version);
      } else {
        s = g_strdup (encoder);
      }

      GST_LOG ("encoder[%u] = '%s'", n, s);
      string_list.append (String (s, String::UTF8));
      g_free (s);
      g_free (encoder);
167
    }
168
  }
169

170 171 172 173 174 175 176 177 178 179
  if (!string_list.isEmpty ()) {
    ID3v2::TextIdentificationFrame * f;

    f = new ID3v2::TextIdentificationFrame ("TSSE", String::UTF8);
    id3v2tag->addFrame (f);
    f->setText (string_list);
  } else {
    GST_WARNING ("Empty list for tag %s, skipping", tag);
  }
}
180

181 182 183 184 185 186
static void
add_date_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * unused)
{
  TagLib::StringList string_list;
  guint n;
187

188
  GST_LOG ("Adding date frame");
189

190 191 192 193 194 195 196 197 198 199 200 201 202
  for (n = 0; n < num_tags; ++n) {
    GDate *date = NULL;

    if (gst_tag_list_get_date_index (list, tag, n, &date) && date != NULL) {
      GDateYear year;
      gchar *s;

      year = g_date_get_year (date);
      if (year > 500 && year < 2100) {
        s = g_strdup_printf ("%u", year);
        GST_LOG ("%s[%u] = '%s'", tag, n, s);
        string_list.append (String (s, String::UTF8));
        g_free (s);
203
      } else {
204
        GST_WARNING ("invalid year %u, skipping", year);
205
      }
206 207

      g_date_free (date);
208
    }
209
  }
210

211 212
  if (!string_list.isEmpty ()) {
    ID3v2::TextIdentificationFrame * f;
213

214 215 216 217 218 219 220
    f = new ID3v2::TextIdentificationFrame ("TDRC", String::UTF8);
    id3v2tag->addFrame (f);
    f->setText (string_list);
  } else {
    GST_WARNING ("Empty list for tag %s, skipping", tag);
  }
}
221

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
static void
add_count_or_num_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  static const struct
  {
    const gchar *gst_tag;
    const gchar *corr_count;    /* corresponding COUNT tag (if number) */
    const gchar *corr_num;      /* corresponding NUMBER tag (if count) */
  } corr[] = {
    {
    GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, NULL}, {
    GST_TAG_TRACK_COUNT, NULL, GST_TAG_TRACK_NUMBER}, {
    GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, NULL}, {
    GST_TAG_ALBUM_VOLUME_COUNT, NULL, GST_TAG_ALBUM_VOLUME_NUMBER}
  };
  guint idx;

  for (idx = 0; idx < G_N_ELEMENTS (corr); ++idx) {
    if (strcmp (corr[idx].gst_tag, tag) == 0)
      break;
  }
244

245 246
  g_assert (idx < G_N_ELEMENTS (corr));
  g_assert (frame_id && strlen (frame_id) == 4);
247

248 249
  if (corr[idx].corr_num == NULL) {
    guint number;
250

251 252
    /* number tag */
    if (gst_tag_list_get_uint_index (list, tag, 0, &number)) {
253
      ID3v2::TextIdentificationFrame * frame;
254 255
      gchar *tag_str;
      guint count;
256

257 258 259 260
      if (gst_tag_list_get_uint_index (list, corr[idx].corr_count, 0, &count))
        tag_str = g_strdup_printf ("%u/%u", number, count);
      else
        tag_str = g_strdup_printf ("%u", number);
261

262 263
      GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
      frame = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
264 265 266 267
      id3v2tag->addFrame (frame);
      frame->setText (tag_str);
      g_free (tag_str);
    }
268 269
  } else if (corr[idx].corr_count == NULL) {
    guint count;
270

271 272 273 274
    /* count tag */
    if (gst_tag_list_get_uint_index (list, corr[idx].corr_num, 0, &count)) {
      GST_DEBUG ("%s handled with %s, skipping", tag, corr[idx].corr_num);
    } else if (gst_tag_list_get_uint_index (list, tag, 0, &count)) {
275 276 277
      ID3v2::TextIdentificationFrame * frame;
      gchar *tag_str;

278 279 280
      tag_str = g_strdup_printf ("0/%u", count);
      GST_DEBUG ("Setting %s to %s (frame_id = %s)", tag, tag_str, frame_id);
      frame = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
281 282 283 284
      id3v2tag->addFrame (frame);
      frame->setText (tag_str);
      g_free (tag_str);
    }
285
  }
286

287 288 289 290
  if (num_tags > 1) {
    GST_WARNING ("more than one %s, can only handle one", tag);
  }
}
291

292 293 294 295 296 297
static void
add_unique_file_id_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * unused)
{
  const gchar *origin = "http://musicbrainz.org";
  gchar *id_str = NULL;
298

299 300
  if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) {
    ID3v2::UniqueFileIdentifierFrame * frame;
301

302 303 304 305 306 307
    GST_LOG ("Adding %s (%s): %s", tag, origin, id_str);
    frame = new ID3v2::UniqueFileIdentifierFrame (origin, id_str);
    id3v2tag->addFrame (frame);
    g_free (id_str);
  }
}
308

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
static void
add_musicbrainz_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * data)
{
  static const struct
  {
    const gchar gst_tag[28];
    const gchar spec_id[28];
    const gchar realworld_id[28];
  } mb_ids[] = {
    {
    GST_TAG_MUSICBRAINZ_ARTISTID, "MusicBrainz Artist Id",
          "musicbrainz_artistid"}, {
    GST_TAG_MUSICBRAINZ_ALBUMID, "MusicBrainz Album Id", "musicbrainz_albumid"}, {
    GST_TAG_MUSICBRAINZ_ALBUMARTISTID, "MusicBrainz Album Artist Id",
          "musicbrainz_albumartistid"}, {
325 326 327 328 329 330 331 332
    GST_TAG_MUSICBRAINZ_TRMID, "MusicBrainz TRM Id", "musicbrainz_trmid"}, {
    GST_TAG_CDDA_MUSICBRAINZ_DISCID, "MusicBrainz DiscID",
          "musicbrainz_discid"}, {
      /* the following one is more or less made up, there seems to be little
       * evidence that any popular application is actually putting this info
       * into TXXX frames; the first one comes from a musicbrainz wiki 'proposed
       * tags' page, the second one is analogue to the vorbis/ape/flac tag. */
    GST_TAG_CDDA_CDDB_DISCID, "CDDB DiscID", "discid"}
333 334 335 336 337 338 339 340 341
  };
  guint i, idx;

  idx = (guint8) data[0];
  g_assert (idx < G_N_ELEMENTS (mb_ids));

  for (i = 0; i < num_tags; ++i) {
    ID3v2::UserTextIdentificationFrame * frame;
    gchar *id_str;
342

343 344
    if (gst_tag_list_get_string_index (list, tag, 0, &id_str) && id_str) {
      GST_DEBUG ("Setting '%s' to '%s'", mb_ids[idx].spec_id, id_str);
345

346 347 348
      /* add two frames, one with the ID the musicbrainz.org spec mentions
       * and one with the ID that applications use in the real world */
      frame = new ID3v2::UserTextIdentificationFrame (String::Latin1);
349
      id3v2tag->addFrame (frame);
350 351
      frame->setDescription (mb_ids[idx].spec_id);
      frame->setText (id_str);
352

353 354 355 356
      frame = new ID3v2::UserTextIdentificationFrame (String::Latin1);
      id3v2tag->addFrame (frame);
      frame->setDescription (mb_ids[idx].realworld_id);
      frame->setText (id_str);
357 358 359

      g_free (id_str);
    }
360 361
  }
}
362

363 364 365 366 367 368
static void
add_id3v2frame_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  ID3v2::FrameFactory * factory = ID3v2::FrameFactory::instance ();
  guint i;
369

370 371 372 373
  for (i = 0; i < num_tags; ++i) {
    ID3v2::Frame * frame;
    const GValue *val;
    GstBuffer *buf;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
374
    GstSample *sample;
375

376
    val = gst_tag_list_get_value_index (list, tag, i);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
377
    sample = (GstSample *) g_value_get_boxed (val);
378

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
379 380
    if (sample && (buf = gst_sample_get_buffer (sample)) &&
        gst_sample_get_caps (sample)) {
381 382
      GstStructure *s;
      gint version = 0;
383

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
384
      s = gst_caps_get_structure (gst_sample_get_caps (sample), 0);
385
      if (s && gst_structure_get_int (s, "version", &version) && version > 0) {
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
386
        GstMapInfo map;
387

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
388
        gst_buffer_map (buf, &map, GST_MAP_READ);
389
        GST_DEBUG ("Injecting ID3v2.%u frame %u/%u of length %u and type %"
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
390
            GST_PTR_FORMAT, version, i, num_tags, map.size, s);
391

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
392 393
        frame = factory->createFrame (ByteVector ((const char *) map.data,
                map.size), (TagLib::uint) version);
394 395
        if (frame)
          id3v2tag->addFrame (frame);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
396 397

        gst_buffer_unmap (buf, &map);
398
      }
399
    }
400 401 402 403 404 405 406 407 408 409
  }
}

static void
add_image_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * unused)
{
  guint n;

  for (n = 0; n < num_tags; ++n) {
410
    const GValue *val;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
411
    GstSample *sample;
412 413
    GstBuffer *image;

414 415 416
    GST_DEBUG ("image %u/%u", n + 1, num_tags);

    val = gst_tag_list_get_value_index (list, tag, n);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
417
    sample = (GstSample *) g_value_get_boxed (val);
418

Edward Hervey's avatar
Edward Hervey committed
419
    if (GST_IS_SAMPLE (sample) && (image = gst_sample_get_buffer (sample)) &&
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
420 421 422
        GST_IS_BUFFER (image) && gst_buffer_get_size (image) > 0 &&
        gst_sample_get_caps (sample) != NULL &&
        !gst_caps_is_empty (gst_sample_get_caps (sample))) {
423 424 425
      const gchar *mime_type;
      GstStructure *s;

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
426
      s = gst_caps_get_structure (gst_sample_get_caps (sample), 0);
427 428 429 430
      mime_type = gst_structure_get_name (s);
      if (mime_type != NULL) {
        ID3v2::AttachedPictureFrame * frame;
        const gchar *desc;
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
431
        GstMapInfo map;
432 433 434 435 436 437

        if (strcmp (mime_type, "text/uri-list") == 0)
          mime_type = "-->";

        frame = new ID3v2::AttachedPictureFrame ();

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
438 439
        gst_buffer_map (image, &map, GST_MAP_READ);

440
        GST_DEBUG ("Attaching picture of %u bytes and mime type %s",
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
441
            map.size, mime_type);
442 443

        id3v2tag->addFrame (frame);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
444
        frame->setPicture (ByteVector ((const char *) map.data, map.size));
445 446 447
        frame->setTextEncoding (String::UTF8);
        frame->setMimeType (mime_type);

Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
448 449
        gst_buffer_unmap (image, &map);

450 451 452
        desc = gst_structure_get_string (s, "image-description");
        frame->setDescription ((desc) ? desc : "");

453 454 455 456 457 458
        /* FIXME set image type properly from caps */
        if (strcmp (tag, GST_TAG_PREVIEW_IMAGE) == 0) {
          frame->setType (ID3v2::AttachedPictureFrame::FileIcon);
        } else {
          frame->setType (ID3v2::AttachedPictureFrame::Other);
        }
459 460
      }
    } else {
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
461 462 463
      GST_WARNING ("NULL image or no caps on image sample (%p, caps=%"
          GST_PTR_FORMAT ")", sample,
          (sample) ? gst_sample_get_caps (sample) : NULL);
464
    }
465 466 467
  }
}

468 469 470 471 472 473 474 475 476 477 478 479 480
static void
add_comment_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * unused)
{
  TagLib::StringList string_list;
  guint n;

  GST_LOG ("Adding comment frames");
  for (n = 0; n < num_tags; ++n) {
    gchar *s = NULL;

    if (gst_tag_list_get_string_index (list, tag, n, &s) && s != NULL) {
      ID3v2::CommentsFrame * f;
481
      gchar *desc = NULL, *val = NULL, *lang = NULL;
482 483 484

      f = new ID3v2::CommentsFrame (String::UTF8);

485 486 487 488 489 490 491 492 493 494 495
      if (strcmp (tag, GST_TAG_COMMENT) == 0 ||
          !gst_tag_parse_extended_comment (s, &desc, &lang, &val, TRUE)) {
        /* create dummy description to allow for multiple comment frames
         * (taglib will drop comment frames if descriptions are not unique) */
        desc = g_strdup_printf ("c%u", n);
        val = g_strdup (s);
      }

      GST_LOG ("%s[%u] = '%s' (%s|%s|%s)", tag, n, s, GST_STR_NULL (desc),
          GST_STR_NULL (lang), GST_STR_NULL (val));

496
      f->setDescription (desc);
497 498 499 500
      f->setText (val);
      if (lang) {
        f->setLanguage (lang);
      }
501

502 503 504
      g_free (lang);
      g_free (desc);
      g_free (val);
505 506 507

      id3v2tag->addFrame (f);
    }
508
    g_free (s);
509 510 511
  }
}

512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534
static void
add_text_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  ID3v2::TextIdentificationFrame * f;
  TagLib::StringList string_list;
  guint n;

  GST_LOG ("Adding '%s' frame", frame_id);
  for (n = 0; n < num_tags; ++n) {
    gchar *s = NULL;

    if (gst_tag_list_get_string_index (list, tag, n, &s) && s != NULL) {
      GST_LOG ("%s: %s[%u] = '%s'", frame_id, tag, n, s);
      string_list.append (String (s, String::UTF8));
      g_free (s);
    }
  }

  if (!string_list.isEmpty ()) {
    f = new ID3v2::TextIdentificationFrame (frame_id, String::UTF8);
    id3v2tag->addFrame (f);
    f->setText (string_list);
535
  } else {
536 537 538 539
    GST_WARNING ("Empty list for tag %s, skipping", tag);
  }
}

540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580
static void
add_uri_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  gchar *url = NULL;

  g_assert (frame_id != NULL);

  /* URI tags are limited to one of each per taglist */
  if (gst_tag_list_get_string_index (list, tag, 0, &url) && url != NULL) {
    guint url_len;

    url_len = strlen (url);
    if (url_len > 0 && gst_uri_is_valid (url)) {
      ID3v2::FrameFactory * factory = ID3v2::FrameFactory::instance ();
      ID3v2::Frame * frame;
      char *data;

      data = g_new0 (char, 10 + url_len);

      memcpy (data, frame_id, 4);
      memcpy (data + 4, ID3v2::SynchData::fromUInt (url_len).data (), 4);
      memcpy (data + 10, url, url_len);
      ByteVector bytes (data, 10 + url_len);

      g_free (data);

      frame = factory->createFrame (bytes, (TagLib::uint) 4);
      if (frame) {
        id3v2tag->addFrame (frame);

        GST_LOG ("%s: %s = '%s'", frame_id, tag, url);
      }
    } else {
      GST_WARNING ("Tag %s does not contain a valid URI (%s)", tag, url);
    }

    g_free (url);
  }
}

581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
static void
add_relative_volume_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  const char *gain_tag_name;
  const char *peak_tag_name;
  gdouble peak_val;
  gdouble gain_val;
  ID3v2::RelativeVolumeFrame * frame;

  frame = new ID3v2::RelativeVolumeFrame ();

  /* figure out tag names and the identification string to use */
  if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
      strcmp (tag, GST_TAG_TRACK_GAIN) == 0) {
    gain_tag_name = GST_TAG_TRACK_GAIN;
    peak_tag_name = GST_TAG_TRACK_PEAK;
    frame->setIdentification ("track");
    GST_DEBUG ("adding track relative-volume frame");
  } else {
    gain_tag_name = GST_TAG_ALBUM_GAIN;
    peak_tag_name = GST_TAG_ALBUM_PEAK;
    frame->setIdentification ("album");
    GST_DEBUG ("adding album relative-volume frame");
  }
  
  /* find the value for the paired tag (gain, if this is peak, and
   * vice versa).  if both tags exist, only write the frame when
   * we're processing the peak tag.
   */
  if (strcmp (tag, GST_TAG_TRACK_PEAK) == 0 ||
      strcmp (tag, GST_TAG_ALBUM_PEAK) == 0) {
    ID3v2::RelativeVolumeFrame::PeakVolume encoded_peak;
    short peak_int;

    gst_tag_list_get_double (list, tag, &peak_val);

    if (gst_tag_list_get_tag_size (list, gain_tag_name) > 0) {
      gst_tag_list_get_double (list, gain_tag_name, &gain_val);
      GST_DEBUG ("setting volume adjustment %g", gain_val);
      frame->setVolumeAdjustment (gain_val);
    }

    /* copying mutagen: always write as 16 bits for sanity. */
    peak_int = (short)(peak_val * G_MAXSHORT);
    encoded_peak.bitsRepresentingPeak = 16;
    encoded_peak.peakVolume = ByteVector::fromShort(peak_int, true);
    GST_DEBUG ("setting peak value %g", peak_val);
    frame->setPeakVolume(encoded_peak);

  } else {
    gst_tag_list_get_double (list, tag, &gain_val);
    GST_DEBUG ("setting volume adjustment %g", gain_val);
    frame->setVolumeAdjustment (gain_val);

    if (gst_tag_list_get_tag_size (list, peak_tag_name) != 0) {
      GST_DEBUG ("both gain and peak tags exist, not adding frame this time around");
      delete frame;
      return;
    }
  }

  id3v2tag->addFrame (frame);
}

646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665
static void
add_bpm_tag (ID3v2::Tag * id3v2tag, const GstTagList * list,
    const gchar * tag, guint num_tags, const gchar * frame_id)
{
  gdouble bpm;

  if (gst_tag_list_get_double_index (list, tag, 0, &bpm)) {
    ID3v2::TextIdentificationFrame * frame;
    gchar *tag_str;

    tag_str = g_strdup_printf ("%u", (guint)bpm);

    GST_DEBUG ("Setting %s to %s", tag, tag_str);
    frame = new ID3v2::TextIdentificationFrame ("TBPM", String::UTF8);
    id3v2tag->addFrame (frame);
    frame->setText (tag_str);
    g_free (tag_str);
  }
}

666 667 668 669 670 671 672 673 674 675 676
/* id3demux produces these for frames it cannot parse */
#define GST_ID3_DEMUX_TAG_ID3V2_FRAME "private-id3v2-frame"

static const struct
{
  const gchar *gst_tag;
  const GstId3v2MuxAddTagFunc func;
  const gchar data[5];
} add_funcs[] = {
  {
  GST_TAG_ARTIST, add_text_tag, "TPE1"}, {
677
  GST_TAG_ALBUM_ARTIST, add_text_tag, "TPE2"}, {
678 679 680
  GST_TAG_TITLE, add_text_tag, "TIT2"}, {
  GST_TAG_ALBUM, add_text_tag, "TALB"}, {
  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 686 687 688 689 690 691 692
  GST_TAG_DATE, add_date_tag, ""}, {
  GST_TAG_IMAGE, add_image_tag, ""}, {
  GST_TAG_PREVIEW_IMAGE, add_image_tag, ""}, {
  GST_ID3_DEMUX_TAG_ID3V2_FRAME, add_id3v2frame_tag, ""}, {
  GST_TAG_MUSICBRAINZ_ARTISTID, add_musicbrainz_tag, "\000"}, {
  GST_TAG_MUSICBRAINZ_ALBUMID, add_musicbrainz_tag, "\001"}, {
  GST_TAG_MUSICBRAINZ_ALBUMARTISTID, add_musicbrainz_tag, "\002"}, {
  GST_TAG_MUSICBRAINZ_TRMID, add_musicbrainz_tag, "\003"}, {
693 694
  GST_TAG_CDDA_MUSICBRAINZ_DISCID, add_musicbrainz_tag, "\004"}, {
  GST_TAG_CDDA_CDDB_DISCID, add_musicbrainz_tag, "\005"}, {
695
  GST_TAG_MUSICBRAINZ_TRACKID, add_unique_file_id_tag, ""}, {
696 697 698
  GST_TAG_ARTIST_SORTNAME, add_text_tag, "TSOP"}, {
  GST_TAG_ALBUM_SORTNAME, add_text_tag, "TSOA"}, {
  GST_TAG_TITLE_SORTNAME, add_text_tag, "TSOT"}, {
699 700 701 702 703
  GST_TAG_TRACK_NUMBER, add_count_or_num_tag, "TRCK"}, {
  GST_TAG_TRACK_COUNT, add_count_or_num_tag, "TRCK"}, {
  GST_TAG_ALBUM_VOLUME_NUMBER, add_count_or_num_tag, "TPOS"}, {
  GST_TAG_ALBUM_VOLUME_COUNT, add_count_or_num_tag, "TPOS"}, {
  GST_TAG_ENCODER, add_encoder_tag, ""}, {
704 705
  GST_TAG_ENCODER_VERSION, add_encoder_tag, ""}, {
  GST_TAG_COPYRIGHT_URI, add_uri_tag, "WCOP"}, {
706 707 708 709
  GST_TAG_LICENSE_URI, add_uri_tag, "WCOP"}, {
  GST_TAG_TRACK_PEAK, add_relative_volume_tag, ""}, {
  GST_TAG_TRACK_GAIN, add_relative_volume_tag, ""}, {
  GST_TAG_ALBUM_PEAK, add_relative_volume_tag, ""}, {
710 711
  GST_TAG_ALBUM_GAIN, add_relative_volume_tag, ""}, {
  GST_TAG_BEATS_PER_MINUTE, add_bpm_tag, ""}
712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
};


static void
foreach_add_tag (const GstTagList * list, const gchar * tag, gpointer userdata)
{
  ID3v2::Tag * id3v2tag = (ID3v2::Tag *) userdata;
  TagLib::StringList string_list;
  guint num_tags, i;

  num_tags = gst_tag_list_get_tag_size (list, tag);

  GST_LOG ("Processing tag %s (num=%u)", tag, num_tags);

  if (num_tags > 1 && gst_tag_is_fixed (tag)) {
    GST_WARNING ("Multiple occurences of fixed tag '%s', ignoring some", tag);
    num_tags = 1;
  }

  for (i = 0; i < G_N_ELEMENTS (add_funcs); ++i) {
    if (strcmp (add_funcs[i].gst_tag, tag) == 0) {
      add_funcs[i].func (id3v2tag, list, tag, num_tags, add_funcs[i].data);
      break;
    }
  }

  if (i == G_N_ELEMENTS (add_funcs)) {
    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 747 748 749 750
{
  ID3v2::Tag id3v2tag;
  ByteVector rendered_tag;
  GstBuffer *buf;
  guint tag_size;

751 752 753 754
  /* write all strings as UTF-8 by default */
  TagLib::ID3v2::FrameFactory::instance ()->
      setDefaultTextEncoding (TagLib::String::UTF8);

755
  /* Render the tag */
756
  gst_tag_list_foreach (taglist, foreach_add_tag, &id3v2tag);
757

758 759 760 761 762 763 764 765 766 767 768 769
#if 0
  /* Do we want to add our own signature to the tag somewhere? */
  {
    gchar *tag_producer_str;

    tag_producer_str = g_strdup_printf ("(GStreamer id3v2mux %s, using "
        "taglib %u.%u)", VERSION, TAGLIB_MAJOR_VERSION, TAGLIB_MINOR_VERSION);
    add_one_txxx_tag (id3v2tag, "tag_encoder", tag_producer_str);
    g_free (tag_producer_str);
  }
#endif

770 771 772 773 774 775 776
  rendered_tag = id3v2tag.render ();
  tag_size = rendered_tag.size ();

  GST_LOG_OBJECT (mux, "tag size = %d bytes", tag_size);

  /* Create buffer with tag */
  buf = gst_buffer_new_and_alloc (tag_size);
Mark Nauwelaerts's avatar
Mark Nauwelaerts committed
777
  gst_buffer_fill (buf, 0, rendered_tag.data (), tag_size);
778 779 780 781

  return buf;
}

782 783
static GstBuffer *
gst_id3v2_mux_render_end_tag (GstTagMux * mux, const GstTagList * taglist)
784
{
785
  return NULL;
786
}