}
+/**
+ * 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) {
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);
}
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);
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;
}
data->data_count += strlen (result);
data->entries = g_list_prepend (data->entries, result);
}
+
+ g_list_free (comments);
}
/**
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)
{
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;
}