tag: id3v2: Read replay gain tags
authorTim-Philipp Müller <tim@centricular.net>
Thu, 16 Mar 2006 13:22:28 +0000 (13:22 +0000)
committerTim-Philipp Müller <tim.muller@collabora.co.uk>
Sun, 14 Aug 2011 23:10:27 +0000 (00:10 +0100)
Original commit message from CVS:
* gst-libs/gst/tag/id3v2frames.c: (id3demux_id3v2_parse_frame),
(parse_id_string), (parse_unique_file_identifier),
(parse_relative_volume_adjustment_two), (id3v2_tag_to_taglist):
Read replay gain tags (#323721).

gst-libs/gst/tag/id3v2frames.c

index 81ff347..0c866f7 100644 (file)
@@ -41,6 +41,7 @@ static gchar *parse_user_text_identification_frame (ID3TagsWorking * work,
     const gchar ** tag_name);
 static gchar *parse_unique_file_identifier (ID3TagsWorking * work,
     const gchar ** tag_name);
+static gboolean parse_relative_volume_adjustment_two (ID3TagsWorking * work);
 static gboolean id3v2_tag_to_taglist (ID3TagsWorking * work,
     const gchar * tag_name, const gchar * tag_str);
 /* Parse a single string into an array of gchar* */
@@ -90,6 +91,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work)
 
   tag_name = gst_tag_from_id3_tag (work->frame_id);
   if (tag_name == NULL &&
+      strncmp (work->frame_id, "RVA2", 4) != 0 &&
       strncmp (work->frame_id, "TXXX", 4) != 0 &&
       strncmp (work->frame_id, "UFID", 4) != 0) {
     return FALSE;
@@ -156,6 +158,7 @@ id3demux_id3v2_parse_frame (ID3TagsWorking * work)
     /* Attached picture */
   } else if (!strcmp (work->frame_id, "RVA2")) {
     /* Relative volume */
+    result = parse_relative_volume_adjustment_two (work);
   } else if (!strcmp (work->frame_id, "UFID")) {
     /* Unique file identifier */
     tag_str = parse_unique_file_identifier (work, &tag_name);
@@ -305,16 +308,14 @@ parse_user_text_identification_frame (ID3TagsWorking * work,
   return ret;
 }
 
-static gchar *
-parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name)
+static gboolean
+parse_id_string (ID3TagsWorking * work, gchar ** p_str, gint * p_len,
+    gint * p_datalen)
 {
   gint len, datalen;
-  gchar *owner_id, *data, *ret = NULL;
 
   if (work->parse_size < 2)
-    return NULL;
-
-  GST_LOG ("parsing UFID frame of size %d", work->parse_size);
+    return FALSE;
 
   for (len = 0; len < work->parse_size - 1; ++len) {
     if (work->parse_data[len] == '\0')
@@ -322,10 +323,27 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name)
   }
 
   datalen = work->parse_size - (len + 1);
-  if (datalen <= 0)
+  if (len == 0 || datalen <= 0)
+    return FALSE;
+
+  *p_str = g_strndup ((gchar *) work->parse_data, len);
+  *p_len = len;
+  *p_datalen = datalen;
+
+  return TRUE;
+}
+
+static gchar *
+parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name)
+{
+  gint len, datalen;
+  gchar *owner_id, *data, *ret = NULL;
+
+  GST_LOG ("parsing UFID frame of size %d", work->parse_size);
+
+  if (!parse_id_string (work, &owner_id, &len, &datalen))
     return NULL;
 
-  owner_id = g_strndup ((gchar *) work->parse_data, len);
   data = (gchar *) work->parse_data + len + 1;
   GST_LOG ("UFID owner ID: %s (+ %d bytes of data)", owner_id, datalen);
 
@@ -342,6 +360,81 @@ parse_unique_file_identifier (ID3TagsWorking * work, const gchar ** tag_name)
 }
 
 static gboolean
+parse_relative_volume_adjustment_two (ID3TagsWorking * work)
+{
+  const gchar *gain_tag_name = NULL;
+  const gchar *peak_tag_name = NULL;
+  gdouble gain_dB, peak_val;
+  guint64 peak;
+  guint8 *data, chan, peak_bits;
+  gchar *id;
+  gint len, datalen, i;
+
+  if (!parse_id_string (work, &id, &len, &datalen))
+    return FALSE;
+
+  if (datalen < (1 + 2 + 1)) {
+    GST_WARNING ("broken RVA2 frame, data size only %d bytes", datalen);
+    g_free (id);
+    return FALSE;
+  }
+
+  data = work->parse_data + len + 1;
+  chan = GST_READ_UINT8 (data);
+  gain_dB = (gdouble) ((gint16) GST_READ_UINT16_BE (data + 1)) / 512.0;
+  /* The meaning of the peak value is not defined in the ID3v2 spec. However,
+   * the first/only implementation of this seems to have been in XMMS, and
+   * other libs (like mutagen) seem to follow that implementation as well:
+   * see http://bugs.xmms.org/attachment.cgi?id=113&action=view */
+  peak_bits = GST_READ_UINT8 (data + 1 + 2);
+  if (peak_bits > 64) {
+    GST_WARNING ("silly peak precision of %d bits, ignoring", (gint) peak_bits);
+    peak_bits = 0;
+  }
+  data += 1 + 2 + 1;
+  datalen -= 1 + 2 + 1;
+  if (peak_bits == 16) {
+    peak = GST_READ_UINT16_BE (data);
+  } else {
+    peak = 0;
+    for (i = 0; i < (GST_ROUND_UP_8 (peak_bits) / 8) && datalen > 0; ++i) {
+      peak = peak << 8;
+      peak |= GST_READ_UINT8 (data);
+      ++data;
+      --datalen;
+    }
+  }
+
+  peak = peak << (64 - GST_ROUND_UP_8 (peak_bits));
+  peak_val = (gdouble) peak / gst_util_guint64_to_gdouble (G_MAXINT64);
+  GST_LOG ("RVA2 frame: id=%s, chan=%u, adj=%.2fdB, peak_bits=%u, peak=%.2f",
+      id, chan, gain_dB, (guint) peak_bits, peak_val);
+
+  if (strcmp (id, "track") == 0) {
+    gain_tag_name = GST_TAG_TRACK_GAIN;
+    peak_tag_name = GST_TAG_TRACK_PEAK;
+  } else if (strcmp (id, "album") == 0) {
+    gain_tag_name = GST_TAG_ALBUM_GAIN;
+    peak_tag_name = GST_TAG_ALBUM_PEAK;
+  } else {
+    GST_INFO ("Unhandled RVA2 frame id '%s'", id);
+  }
+
+  if (gain_tag_name) {
+    gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND,
+        gain_tag_name, gain_dB, NULL);
+  }
+  if (peak_tag_name && peak_bits > 0) {
+    gst_tag_list_add (work->tags, GST_TAG_MERGE_APPEND,
+        peak_tag_name, peak_val, NULL);
+  }
+
+  g_free (id);
+
+  return (gain_tag_name != NULL || peak_tag_name != NULL);
+}
+
+static gboolean
 id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name,
     const gchar * tag_str)
 {
@@ -442,6 +535,13 @@ id3v2_tag_to_taglist (ID3TagsWorking * work, const gchar * tag_name,
       if (g_value_transform (&src, &dest)) {
         gst_tag_list_add_values (tag_list, GST_TAG_MERGE_APPEND,
             tag_name, &dest, NULL);
+      } else if (tag_type == G_TYPE_DOUBLE) {
+        /* replaygain tags in TXXX frames ... */
+        g_value_set_double (&dest, g_strtod (tag_str, NULL));
+        gst_tag_list_add_values (tag_list, GST_TAG_MERGE_KEEP,
+            tag_name, &dest, NULL);
+        GST_LOG ("Converted string '%s' to double %f", tag_str,
+            g_value_get_double (&dest));
       } else {
         GST_WARNING ("Failed to transform tag from string to type '%s'",
             g_type_name (tag_type));