gst-libs/gst/tag/gstvorbistag.c: Serialise unknown vorbis comments into GST_TAG_EXTEN...
authorTim-Philipp Müller <tim@centricular.net>
Thu, 17 Aug 2006 16:52:06 +0000 (16:52 +0000)
committerTim-Philipp Müller <tim@centricular.net>
Thu, 17 Aug 2006 16:52:06 +0000 (16:52 +0000)
Original commit message from CVS:
* gst-libs/gst/tag/gstvorbistag.c: (gst_vorbis_tag_add),
(gst_tag_to_vorbis_comments):
Serialise unknown vorbis comments into GST_TAG_EXTENDED_COMMENT
tags and deserialise them properly as well (#351768).
Add some more gtk-doc blurbs and also some g_return_if_fail().
* tests/check/libs/tag.c: (GST_START_TEST),
(back_to_vorbis_comments), (taglists_are_equal), (tag_suite):
More tests.

ChangeLog
gst-libs/gst/tag/gstvorbistag.c
tests/check/libs/tag.c

index 2aca2fe..b68e37d 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,15 @@
+2006-08-17  Tim-Philipp Müller  <tim at centricular dot net>
+
+       * gst-libs/gst/tag/gstvorbistag.c: (gst_vorbis_tag_add),
+       (gst_tag_to_vorbis_comments):
+         Serialise unknown vorbis comments into GST_TAG_EXTENDED_COMMENT
+         tags and deserialise them properly as well (#351768).
+         Add some more gtk-doc blurbs and also some g_return_if_fail().
+
+       * tests/check/libs/tag.c: (GST_START_TEST),
+       (back_to_vorbis_comments), (taglists_are_equal), (tag_suite):
+         More tests.
+
 2006-08-17  Wim Taymans  <wim@fluendo.com>
 
        * ext/ogg/Makefile.am:
index 841e94d..916d9c7 100644 (file)
@@ -130,14 +130,43 @@ gst_tag_to_vorbis_tag (const gchar * gst_tag)
 }
 
 
+/**
+ * gst_vorbis_tag_add:
+ * @list: a #GstTagList
+ * @tag: a vorbiscomment tag string (key in key=value), must be valid UTF-8
+ * @val: a vorbiscomment value string (value in key=value), must be valid UTF-8
+ *
+ * Convenience function using gst_tag_from_vorbis_tag(), parsing
+ * a vorbis comment string into the right type and adding it to the
+ * given taglist @list.
+ *
+ * Unknown vorbiscomment tags will be added to the tag list in form
+ * of a #GST_TAG_EXTENDED_COMMENT (since 0.10.10 at least).
+ */
 void
 gst_vorbis_tag_add (GstTagList * list, const gchar * tag, const gchar * value)
 {
-  const gchar *gst_tag = gst_tag_from_vorbis_tag (tag);
+  const gchar *gst_tag;
   GType tag_type;
 
-  if (gst_tag == NULL)
+  g_return_if_fail (list != NULL);
+  g_return_if_fail (tag != NULL);
+  g_return_if_fail (value != NULL);
+
+  g_return_if_fail (g_utf8_validate (tag, -1, NULL));
+  g_return_if_fail (g_utf8_validate (value, -1, NULL));
+  g_return_if_fail (strchr (tag, '=') == NULL);
+
+  gst_tag = gst_tag_from_vorbis_tag (tag);
+  if (gst_tag == NULL) {
+    gchar *ext_comment;
+
+    ext_comment = g_strdup_printf ("%s=%s", tag, value);
+    gst_tag_list_add (list, GST_TAG_MERGE_APPEND, GST_TAG_EXTENDED_COMMENT,
+        ext_comment, NULL);
+    g_free (ext_comment);
     return;
+  }
 
   tag_type = gst_tag_get_type (gst_tag);
   switch (tag_type) {
@@ -177,22 +206,18 @@ gst_vorbis_tag_add (GstTagList * list, const gchar * tag, const gchar * value)
       if (strcmp (tag, "LANGUAGE") == 0) {
         const gchar *s = strchr (value, '[');
 
-        /* FIXME: gsttaglist.h says our language tag contains ISO-639-1
-         * codes, which are 2 letter codes. The code below extracts 3-letter
-         * identifiers, which would be ISO-639-2. Mixup? Oversight? Wrong core
-         * docs? What do files in the wild contain? (tpm) */
+        /* Accept both ISO-639-1 and ISO-639-2 codes */
         if (s && strchr (s, ']') == s + 4) {
           valid = g_strndup (s + 1, 3);
+        } else if (s && strchr (s, ']') == s + 3) {
+          valid = g_strndup (s + 1, 2);
+        } else if (strlen (value) != 2 && strlen (value) != 3) {
+          GST_WARNING ("doesn't contain an ISO-639 language code: %s", value);
         }
       }
 
       if (!valid) {
-        if (!g_utf8_validate (value, -1, (const gchar **) &valid)) {
-          valid = g_strndup (value, valid - value);
-          GST_DEBUG ("Invalid vorbis comment tag, truncated it to %s", valid);
-        } else {
-          valid = g_strdup (value);
-        }
+        valid = g_strdup (value);
       }
       gst_tag_list_add (list, GST_TAG_MERGE_APPEND, gst_tag, valid, NULL);
       g_free (valid);
@@ -322,15 +347,32 @@ typedef struct
 }
 MyForEach;
 
+/**
+ * gst_tag_to_vorbis_comments:
+ * @list: a #GstTagList
+ * @tag: a GStreamer tag identifier, such as #GST_TAG_ARTIST
+ *
+ * Creates a new tag list that contains the information parsed out of a 
+ * vorbiscomment packet.
+ *
+ * Returns: A #GList of newly-allowcated key=value strings. Free with
+ *          g_list_foreach (list, (GFunc) g_free, NULL) plus g_list_free (list)
+ */
 GList *
 gst_tag_to_vorbis_comments (const GstTagList * list, const gchar * tag)
 {
+  const gchar *vorbis_tag = NULL;
   GList *l = NULL;
   guint i;
-  const gchar *vorbis_tag = gst_tag_to_vorbis_tag (tag);
 
-  if (!vorbis_tag)
-    return NULL;
+  g_return_val_if_fail (list != NULL, NULL);
+  g_return_val_if_fail (tag != NULL, NULL);
+
+  if (strcmp (tag, GST_TAG_EXTENDED_COMMENT) != 0) {
+    vorbis_tag = gst_tag_to_vorbis_tag (tag);
+    if (!vorbis_tag)
+      return NULL;
+  }
 
   for (i = 0; i < gst_tag_list_get_tag_size (list, tag); i++) {
     GType tag_type = gst_tag_get_type (tag);
@@ -346,11 +388,25 @@ gst_tag_to_vorbis_comments (const GstTagList * list, const gchar * tag)
         break;
       }
       case G_TYPE_STRING:{
-        gchar *str;
+        gchar *str = NULL;
 
         if (!gst_tag_list_get_string_index (list, tag, i, &str))
           g_return_val_if_reached (NULL);
-        result = g_strdup_printf ("%s=%s", vorbis_tag, str);
+
+        /* special case: GST_TAG_EXTENDED_COMMENT */
+        if (vorbis_tag == NULL) {
+          gchar *key = NULL, *val = NULL;
+
+          if (gst_tag_parse_extended_comment (str, &key, NULL, &val, TRUE)) {
+            result = g_strdup_printf ("%s=%s", key, val);
+            g_free (key);
+            g_free (val);
+          } else {
+            GST_WARNING ("Not a valid extended comment string: %s", str);
+          }
+        } else {
+          result = g_strdup_printf ("%s=%s", vorbis_tag, str);
+        }
         g_free (str);
         break;
       }
@@ -404,6 +460,8 @@ write_one_tag (const GstTagList * list, const gchar * tag, gpointer user_data)
     data->data_count += strlen (result);
     data->entries = g_list_prepend (data->entries, result);
   }
+
+  g_list_free (comments);
 }
 
 /**
index 67624e2..4feaac0 100644 (file)
@@ -133,6 +133,297 @@ GST_START_TEST (test_parse_extended_comment)
 
 GST_END_TEST;
 
+#define ASSERT_TAG_LIST_HAS_STRING(list,field,string)                      \
+  {                                                                        \
+    gboolean got_match = FALSE;                                            \
+    guint i, size;                                                         \
+                                                                           \
+    fail_unless (gst_tag_list_get_tag_size (list,field) > 0);              \
+    size = gst_tag_list_get_tag_size (list,field);                         \
+    for (i = 0; i < size; ++i) {                                           \
+      gchar *___s = NULL;                                                  \
+                                                                           \
+      fail_unless (gst_tag_list_get_string_index (list, field, i, &___s)); \
+      fail_unless (___s != NULL);                                          \
+      if (g_str_equal (___s, string)) {                                    \
+        got_match = TRUE;                                                  \
+        g_free (___s);                                                     \
+        break;                                                             \
+      }                                                                    \
+      g_free (___s);                                                       \
+    }                                                                      \
+    fail_unless (got_match);                                               \
+  }
+
+#define ASSERT_TAG_LIST_HAS_UINT(list,field,num)                           \
+  {                                                                        \
+    guint ___n;                                                            \
+                                                                           \
+    fail_unless (gst_tag_list_get_tag_size (list,field) > 0);              \
+    fail_unless (gst_tag_list_get_tag_size (list,field) == 1);             \
+    fail_unless (gst_tag_list_get_uint_index (list, field, 0, &___n));     \
+    fail_unless_equals_int (___n, num);                                    \
+  }
+
+GST_START_TEST (test_muscibrainz_tag_registration)
+{
+  GstTagList *list;
+
+  gst_tag_register_musicbrainz_tags ();
+
+  list = gst_tag_list_new ();
+
+  /* musicbrainz tags aren't registered yet */
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_TRACKID", "123456");
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_ARTISTID", "234567");
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_ALBUMID", "345678");
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_ALBUMARTISTID", "4567890");
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_TRMID", "5678901");
+  gst_vorbis_tag_add (list, "MUSICBRAINZ_SORTNAME", "Five, 678901");
+
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_TRACKID, "123456");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_ARTISTID, "234567");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_ALBUMID, "345678");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_ALBUMARTISTID,
+      "4567890");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_TRMID, "5678901");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_MUSICBRAINZ_SORTNAME,
+      "Five, 678901");
+
+  gst_tag_list_free (list);
+}
+
+GST_END_TEST;
+
+/* is there an easier way to compare two structures / tagslists? */
+static gboolean
+taglists_are_equal (const GstTagList * list_1, const GstTagList * list_2)
+{
+  GstCaps *c_list_1 = gst_caps_new_empty ();
+  GstCaps *c_list_2 = gst_caps_new_empty ();
+  gboolean ret;
+
+  gst_caps_append_structure (c_list_1,
+      gst_structure_copy ((GstStructure *) list_1));
+  gst_caps_append_structure (c_list_2,
+      gst_structure_copy ((GstStructure *) list_2));
+
+  ret = gst_caps_is_equal (c_list_2, c_list_1);
+
+  gst_caps_unref (c_list_1);
+  gst_caps_unref (c_list_2);
+}
+
+GST_START_TEST (test_vorbis_tags)
+{
+  GstTagList *list;
+
+  list = gst_tag_list_new ();
+
+  /* NULL pointers aren't allowed */
+  ASSERT_CRITICAL (gst_vorbis_tag_add (NULL, "key", "value"));
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, NULL, "value"));
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, "key", NULL));
+
+  /* must be UTF-8 */
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, "key", "v\777lue"));
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, "k\777y", "value"));
+
+  /* key can't have a '=' in it */
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, "k=y", "value"));
+  ASSERT_CRITICAL (gst_vorbis_tag_add (list, "key=", "value"));
+
+  /* should be allowed in values though */
+  gst_vorbis_tag_add (list, "keeey", "va=ue");
+
+  /* add some tags */
+  gst_vorbis_tag_add (list, "TITLE", "Too");
+  gst_vorbis_tag_add (list, "ALBUM", "Aoo");
+  gst_vorbis_tag_add (list, "ARTIST", "Alboo");
+  gst_vorbis_tag_add (list, "PERFORMER", "Perfoo");
+  gst_vorbis_tag_add (list, "COPYRIGHT", "Copyfoo");
+  gst_vorbis_tag_add (list, "DESCRIPTION", "Descoo");
+  gst_vorbis_tag_add (list, "LICENSE", "Licoo");
+  gst_vorbis_tag_add (list, "ORGANIZATION", "Orgoo");
+  gst_vorbis_tag_add (list, "GENRE", "Goo");
+  gst_vorbis_tag_add (list, "CONTACT", "Coo");
+  gst_vorbis_tag_add (list, "COMMENT", "Stroodle is good");
+  gst_vorbis_tag_add (list, "COMMENT", "Peroxysulfid stroodles the brain");
+
+  gst_vorbis_tag_add (list, "TRACKNUMBER", "5");
+  gst_vorbis_tag_add (list, "TRACKTOTAL", "77");
+  gst_vorbis_tag_add (list, "DISCNUMBER", "1");
+  gst_vorbis_tag_add (list, "DISCTOTAL", "2");
+  gst_vorbis_tag_add (list, "DATE", "1954-12-31");
+
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_TITLE, "Too");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_ALBUM, "Aoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_ARTIST, "Alboo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_PERFORMER, "Perfoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_COPYRIGHT, "Copyfoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_DESCRIPTION, "Descoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LICENSE, "Licoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_ORGANIZATION, "Orgoo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_GENRE, "Goo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_CONTACT, "Coo");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_COMMENT,
+      "Peroxysulfid stroodles the brain");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_COMMENT, "Stroodle is good");
+
+  ASSERT_TAG_LIST_HAS_UINT (list, GST_TAG_TRACK_NUMBER, 5);
+  ASSERT_TAG_LIST_HAS_UINT (list, GST_TAG_TRACK_COUNT, 77);
+  ASSERT_TAG_LIST_HAS_UINT (list, GST_TAG_ALBUM_VOLUME_NUMBER, 1);
+  ASSERT_TAG_LIST_HAS_UINT (list, GST_TAG_ALBUM_VOLUME_COUNT, 2);
+
+  {
+    GDate *date = NULL;
+
+    fail_unless (gst_tag_list_get_date (list, GST_TAG_DATE, &date));
+    fail_unless (date != NULL);
+    fail_unless (g_date_get_day (date) == 31);
+    fail_unless (g_date_get_month (date) == G_DATE_DECEMBER);
+    fail_unless (g_date_get_year (date) == 1954);
+
+    g_date_free (date);
+  }
+
+  /* unknown vorbis comments should go into a GST_TAG_EXTENDED_COMMENT */
+  gst_vorbis_tag_add (list, "CoEdSub_ID", "98172AF-973-10-B");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_EXTENDED_COMMENT,
+      "CoEdSub_ID=98172AF-973-10-B");
+  gst_vorbis_tag_add (list, "RuBuWuHash", "1337BA42F91");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_EXTENDED_COMMENT,
+      "RuBuWuHash=1337BA42F91");
+
+#if 0
+  /* TODO: test these as well */
+  {
+  GST_TAG_TRACK_GAIN, "REPLAYGAIN_TRACK_GAIN"}, {
+  GST_TAG_TRACK_PEAK, "REPLAYGAIN_TRACK_PEAK"}, {
+  GST_TAG_ALBUM_GAIN, "REPLAYGAIN_ALBUM_GAIN"}, {
+  GST_TAG_ALBUM_PEAK, "REPLAYGAIN_ALBUM_PEAK"}, {
+  GST_TAG_LANGUAGE_CODE, "LANGUAGE"},
+#endif
+      /* make sure we can convert back and forth without loss */
+  {
+    GstTagList *new_list, *even_newer_list;
+    GstBuffer *buf, *buf2;
+    gchar *vendor_id = NULL;
+
+    buf = gst_tag_list_to_vorbiscomment_buffer (list,
+        (const guint8 *) "\003vorbis", 7, "libgstunittest");
+    fail_unless (buf != NULL);
+    new_list = gst_tag_list_from_vorbiscomment_buffer (buf,
+        (const guint8 *) "\003vorbis", 7, &vendor_id);
+    fail_unless (new_list != NULL);
+    fail_unless (vendor_id != NULL);
+    g_free (vendor_id);
+    vendor_id = NULL;
+
+    GST_LOG ("new_list = %" GST_PTR_FORMAT, new_list);
+    fail_unless (taglists_are_equal (list, new_list));
+
+    buf2 = gst_tag_list_to_vorbiscomment_buffer (new_list,
+        (const guint8 *) "\003vorbis", 7, "libgstunittest");
+    fail_unless (buf2 != NULL);
+    even_newer_list = gst_tag_list_from_vorbiscomment_buffer (buf2,
+        (const guint8 *) "\003vorbis", 7, &vendor_id);
+    fail_unless (even_newer_list != NULL);
+    fail_unless (vendor_id != NULL);
+    g_free (vendor_id);
+    vendor_id = NULL;
+
+    GST_LOG ("even_newer_list = %" GST_PTR_FORMAT, even_newer_list);
+    fail_unless (taglists_are_equal (new_list, even_newer_list));
+
+    gst_tag_list_free (new_list);
+    gst_tag_list_free (even_newer_list);
+    gst_buffer_unref (buf);
+    gst_buffer_unref (buf2);
+  }
+
+  /* there can only be one language per taglist ... */
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "fr");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "fr");
+
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "[fr]");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "fr");
+
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "French [fr]");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "fr");
+
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "[eng] English");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "eng");
+
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "eng");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "eng");
+
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "[eng]");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "eng");
+
+  /* free-form *sigh* */
+  gst_tag_list_free (list);
+  list = gst_tag_list_new ();
+  gst_vorbis_tag_add (list, "LANGUAGE", "English");
+  ASSERT_TAG_LIST_HAS_STRING (list, GST_TAG_LANGUAGE_CODE, "English");
+
+  gst_tag_list_free (list);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_id3_tags)
+{
+  guint i;
+
+  fail_unless (gst_tag_id3_genre_count () > 0);
+
+  for (i = 0; i < gst_tag_id3_genre_count (); ++i) {
+    const gchar *genre;
+
+    genre = gst_tag_id3_genre_get (i);
+    fail_unless (genre != NULL);
+  }
+
+  {
+    /* TODO: GstTagList *gst_tag_list_new_from_id3v1 (const guint8 *data) */
+  }
+
+  /* gst_tag_from_id3_tag */
+  fail_unless (gst_tag_from_id3_tag ("TALB") != NULL);
+  ASSERT_CRITICAL (gst_tag_from_id3_tag (NULL));
+  fail_unless (gst_tag_from_id3_tag ("R2D2") == NULL);
+
+  /* gst_tag_from_id3_user_tag */
+  ASSERT_CRITICAL (gst_tag_from_id3_user_tag (NULL, "foo"));
+  ASSERT_CRITICAL (gst_tag_from_id3_user_tag ("foo", NULL));
+  fail_unless (gst_tag_from_id3_user_tag ("R2D2", "R2D2") == NULL);
+
+  /* gst_tag_to_id3_tag */
+  ASSERT_CRITICAL (gst_tag_to_id3_tag (NULL));
+  fail_unless (gst_tag_to_id3_tag ("R2D2") == NULL);
+  fail_unless (gst_tag_to_id3_tag (GST_TAG_ARTIST) != NULL);
+
+
+  fail_unless (GST_TYPE_TAG_IMAGE_TYPE != 0);
+  fail_unless (g_type_name (GST_TYPE_TAG_IMAGE_TYPE) != NULL);
+}
+
+GST_END_TEST;
+
+
 static Suite *
 tag_suite (void)
 {
@@ -140,8 +431,10 @@ tag_suite (void)
   TCase *tc_chain = tcase_create ("general");
 
   suite_add_tcase (s, tc_chain);
+  tcase_add_test (tc_chain, test_muscibrainz_tag_registration);
   tcase_add_test (tc_chain, test_parse_extended_comment);
-
+  tcase_add_test (tc_chain, test_vorbis_tags);
+  tcase_add_test (tc_chain, test_id3_tags);
   return s;
 }