major plugin revamp based on x-fixer's code
[flac.git] / src / plugin_common / canonical_tag.c
index fe07214..7195119 100644 (file)
-/* plugin_common - Routines common to several plugins
- * Copyright (C) 2002,2003,2004  Josh Coalson
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
- *
- * This program 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 General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
- */
-
-#include <stdlib.h>
-#include <stdio.h>
-
-#include "canonical_tag.h"
-#include "id3v2.h"
-#include "vorbiscomment.h"
-#include "FLAC/assert.h"
-#include "FLAC/metadata.h"
-
-static void local__safe_free(void *object)
-{
-       if(0 != object)
-               free(object);
-}
-
-static void local__copy_field(char **dest, const char *src, unsigned n)
-{
-       if(n > 0) {
-               const char *p = src + n;
-               while(p > src && *(--p) == ' ')
-                       ;
-               n = p - src + 1;
-               if(0 != (*dest = malloc(n+1))) {
-                       memcpy(*dest, src, n);
-                       (*dest)[n] = '\0';
-               }
-       }
-       else
-               *dest = 0;
-}
-
-static FLAC__bool local__get_id3v1_tag_as_canonical(const char *filename, FLAC_Plugin__CanonicalTag *tag)
-{
-       FLAC_Plugin__Id3v1_Tag id3v1_tag;
-
-       if(FLAC_plugin__id3v1_tag_get(filename, &id3v1_tag)) {
-               FLAC_plugin__canonical_tag_convert_from_id3v1(tag, &id3v1_tag);
-               return true;
-       }
-       return false;
-}
-
-FLAC_Plugin__CanonicalTag *FLAC_plugin__canonical_tag_new()
-{
-       FLAC_Plugin__CanonicalTag *object = (FLAC_Plugin__CanonicalTag*)malloc(sizeof(FLAC_Plugin__CanonicalTag));
-       if(0 != object)
-               FLAC_plugin__canonical_tag_init(object);
-       return object;
-}
-
-void FLAC_plugin__canonical_tag_delete(FLAC_Plugin__CanonicalTag *object)
-{
-       FLAC__ASSERT(0 != object);
-       FLAC_plugin__canonical_tag_clear(object);
-       free(object);
-}
-
-void FLAC_plugin__canonical_tag_init(FLAC_Plugin__CanonicalTag *object)
-{
-       FLAC__ASSERT(0 != object);
-       object->title = 0;
-       object->composer = 0;
-       object->performer = 0;
-       object->album = 0;
-       object->year_recorded = 0;
-       object->year_performed = 0;
-       object->track_number = 0;
-       object->tracks_in_album = 0;
-       object->genre = 0;
-       object->comment = 0;
-}
-
-void FLAC_plugin__canonical_tag_clear(FLAC_Plugin__CanonicalTag *object)
-{
-       FLAC__ASSERT(0 != object);
-       local__safe_free(object->title);
-       local__safe_free(object->composer);
-       local__safe_free(object->performer);
-       local__safe_free(object->album);
-       local__safe_free(object->year_recorded);
-       local__safe_free(object->year_performed);
-       local__safe_free(object->track_number);
-       local__safe_free(object->tracks_in_album);
-       local__safe_free(object->genre);
-       local__safe_free(object->comment);
-       FLAC_plugin__canonical_tag_init(object);
-}
-
-static void local__grab(char **dest, char **src)
-{
-       if(0 == *dest) {
-               *dest = *src;
-               *src = 0;
-       }
-}
-
-void FLAC_plugin__canonical_tag_merge(FLAC_Plugin__CanonicalTag *dest, FLAC_Plugin__CanonicalTag *src)
-{
-       local__grab(&dest->title, &src->title);
-       local__grab(&dest->composer, &src->composer);
-       local__grab(&dest->performer, &src->performer);
-       local__grab(&dest->album, &src->album);
-       local__grab(&dest->year_recorded, &src->year_recorded);
-       local__grab(&dest->year_performed, &src->year_performed);
-       local__grab(&dest->track_number, &src->track_number);
-       local__grab(&dest->tracks_in_album, &src->tracks_in_album);
-       local__grab(&dest->genre, &src->genre);
-       local__grab(&dest->comment, &src->comment);
-}
-
-void FLAC_plugin__canonical_tag_convert_from_id3v1(FLAC_Plugin__CanonicalTag *object, const FLAC_Plugin__Id3v1_Tag *id3v1_tag)
-{
-       local__copy_field(&object->title, id3v1_tag->title, 30);
-       local__copy_field(&object->composer, id3v1_tag->artist, 30);
-       local__copy_field(&object->performer, id3v1_tag->artist, 30);
-       local__copy_field(&object->album, id3v1_tag->album, 30);
-       local__copy_field(&object->year_performed, id3v1_tag->year, 4);
-
-       /* Check for v1.1 tags. */
-       if (id3v1_tag->comment.v1_1.zero == 0) {
-               if(0 != (object->track_number = malloc(4)))
-                       sprintf(object->track_number, "%u", (unsigned)id3v1_tag->comment.v1_1.track);
-               local__copy_field(&object->comment, id3v1_tag->comment.v1_1.comment, 28);
-       }
-       else {
-               object->track_number = strdup("0");
-               local__copy_field(&object->comment, id3v1_tag->comment.v1_0.comment, 30);
-       }
-
-       object->genre = strdup(FLAC_plugin__id3v1_tag_get_genre_as_string(id3v1_tag->genre));
-}
-
-void FLAC_plugin__canonical_tag_get_combined(const char *filename, FLAC_Plugin__CanonicalTag *tag)
-{
-       FLAC_Plugin__CanonicalTag id3v1_tag, id3v2_tag;
-
-       FLAC_plugin__vorbiscomment_get(filename, tag);
-
-       FLAC_plugin__canonical_tag_init(&id3v2_tag);
-       (void)FLAC_plugin__id3v2_tag_get(filename, &id3v2_tag);
-
-       FLAC_plugin__canonical_tag_init(&id3v1_tag);
-       (void)local__get_id3v1_tag_as_canonical(filename, &id3v1_tag);
-
-       /* merge tags, preferring, in order: vorbis comments, id3v2, id3v1 */
-       FLAC_plugin__canonical_tag_merge(tag, &id3v2_tag);
-       FLAC_plugin__canonical_tag_merge(tag, &id3v1_tag);
-
-       FLAC_plugin__canonical_tag_clear(&id3v1_tag);
-       FLAC_plugin__canonical_tag_clear(&id3v2_tag);
-}
+/* plugin_common - Routines common to several plugins\r
+ * Copyright (C) 2002,2003,2004  Josh Coalson\r
+ *\r
+ * This program is free software; you can redistribute it and/or\r
+ * modify it under the terms of the GNU General Public License\r
+ * as published by the Free Software Foundation; either version 2\r
+ * of the License, or (at your option) any later version.\r
+ *\r
+ * This program is distributed in the hope that it will be useful,\r
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\r
+ * GNU General Public License for more details.\r
+ *\r
+ * You should have received a copy of the GNU General Public License\r
+ * along with this program; if not, write to the Free Software\r
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.\r
+ */\r
+\r
+#ifdef HAVE_CONFIG_H\r
+#include <config.h>\r
+#endif\r
+\r
+#include <stdlib.h>\r
+#include <stdio.h>\r
+\r
+#include "canonical_tag.h"\r
+#include "id3v2.h"\r
+#include "vorbiscomment.h"\r
+#include "FLAC/assert.h"\r
+#include "FLAC/metadata.h"\r
+\r
+#if 0\r
+#define __USE_GNU /*@@@@@@ needed on glibc systems to get wcsdup() and wcscasecmp() */\r
+#endif\r
+#include <wchar.h>\r
+\r
+/*\r
+ * Here lies hackage to get any missing wide character string functions we\r
+ * need.  The fallback implementations here are from glibc.\r
+ */\r
+\r
+#if !defined(_MSC_VER) && !defined(HAVE_WCSDUP)\r
+/* Copyright (C) 1995, 1996, 1997, 1998 Free Software Foundation, Inc.\r
+   This file is part of the GNU C Library.\r
+   Contributed by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.\r
+\r
+   The GNU C Library is free software; you can redistribute it and/or\r
+   modify it under the terms of the GNU Lesser General Public\r
+   License as published by the Free Software Foundation; either\r
+   version 2.1 of the License, or (at your option) any later version.\r
+\r
+   The GNU C Library is distributed in the hope that it will be useful,\r
+   but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+   Lesser General Public License for more details.\r
+\r
+   You should have received a copy of the GNU Lesser General Public\r
+   License along with the GNU C Library; if not, write to the Free\r
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA\r
+   02111-1307 USA.  */\r
+\r
+#include <wchar.h>\r
+#include <string.h>\r
+#include <stdlib.h>\r
+\r
+\r
+/* Duplicate S, returning an identical malloc'd string.         */\r
+wchar_t *\r
+wcsdup (s)\r
+     const wchar_t *s;\r
+{\r
+  size_t len = (__wcslen (s) + 1) * sizeof (wchar_t);\r
+  void *new = malloc (len);\r
+\r
+  if (new == NULL)\r
+    return NULL;\r
+\r
+  return (wchar_t *) memcpy (new, (void *) s, len);\r
+}\r
+#endif\r
+\r
+#if !defined(_MSC_VER) && !defined(HAVE_WCSCASECMP)\r
+/* Copyright (C) 1991, 1992, 1995, 1996, 1997 Free Software Foundation, Inc.\r
+   This file is part of the GNU C Library.\r
+\r
+   The GNU C Library is free software; you can redistribute it and/or\r
+   modify it under the terms of the GNU Lesser General Public\r
+   License as published by the Free Software Foundation; either\r
+   version 2.1 of the License, or (at your option) any later version.\r
+\r
+   The GNU C Library is distributed in the hope that it will be useful,\r
+   but WITHOUT ANY WARRANTY; without even the implied warranty of\r
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\r
+   Lesser General Public License for more details.\r
+\r
+   You should have received a copy of the GNU Lesser General Public\r
+   License along with the GNU C Library; if not, write to the Free\r
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA\r
+   02111-1307 USA.  */\r
+\r
+#include <wctype.h>\r
+#include <wchar.h>\r
+\r
+#ifndef weak_alias\r
+# define __wcscasecmp wcscasecmp\r
+# define TOLOWER(Ch) towlower (Ch)\r
+#else\r
+# ifdef USE_IN_EXTENDED_LOCALE_MODEL\r
+#  define __wcscasecmp __wcscasecmp_l\r
+#  define TOLOWER(Ch) __towlower_l ((Ch), loc)\r
+# else\r
+#  define TOLOWER(Ch) towlower (Ch)\r
+# endif\r
+#endif\r
+\r
+#ifdef USE_IN_EXTENDED_LOCALE_MODEL\r
+# define LOCALE_PARAM , loc\r
+# define LOCALE_PARAM_DECL __locale_t loc;\r
+#else\r
+# define LOCALE_PARAM\r
+# define LOCALE_PARAM_DECL\r
+#endif\r
+\r
+/* Compare S1 and S2, ignoring case, returning less than, equal to or\r
+   greater than zero if S1 is lexicographically less than,\r
+   equal to or greater than S2.  */\r
+int\r
+__wcscasecmp (s1, s2 LOCALE_PARAM)\r
+     const wchar_t *s1;\r
+     const wchar_t *s2;\r
+     LOCALE_PARAM_DECL\r
+{\r
+  wint_t c1, c2;\r
+\r
+  if (s1 == s2)\r
+    return 0;\r
+\r
+  do\r
+    {\r
+      c1 = TOLOWER (*s1++);\r
+      c2 = TOLOWER (*s2++);\r
+      if (c1 == L'\0')\r
+       break;\r
+    }\r
+  while (c1 == c2);\r
+\r
+  return c1 - c2;\r
+}\r
+#ifndef __wcscasecmp\r
+weak_alias (__wcscasecmp, wcscasecmp)\r
+#endif\r
+#endif\r
+\r
+#ifndef _MSC_VER\r
+/* @@@ cheesy and does base 10 only */\r
+wchar_t *local__itow(int value, wchar_t *string)\r
+{\r
+       if (value == 0) {\r
+               string[0] = (wchar_t)'0';\r
+               string[1] = (wchar_t)0;\r
+       }\r
+       else {\r
+               /* convert backwards, then reverse string */\r
+               wchar_t *start = string, *s;\r
+               if (value < 0) {\r
+                       *start++ = (wchar_t)'-';\r
+                       value = -value; /* @@@ overflow at INT_MIN */\r
+               }\r
+               s = start;\r
+               while (value > 0) {\r
+                       *s++ = (wchar_t)((value % 10) + '0');\r
+                       value /= 10;\r
+               }\r
+               *s-- = (wchar_t)0;\r
+               while (s > start) {\r
+                       wchar_t tmp = *s;\r
+                       *s-- = *start;\r
+                       *start++ = tmp;\r
+               }\r
+       }\r
+\r
+       return string;\r
+}\r
+#endif\r
+\r
+/*\r
+ *  helpers\r
+ */\r
+\r
+/* TODO: should be moved out somewhere? @@@ */\r
+\r
+wchar_t *FLAC_plugin__convert_ansi_to_wide(const char *src)\r
+{\r
+       int len;\r
+       wchar_t *dest;\r
+\r
+       FLAC__ASSERT(0 != src);\r
+\r
+       len = strlen(src) + 1;\r
+       /* copy */\r
+       dest = malloc(len*sizeof(wchar_t));\r
+       if (dest) mbstowcs(dest, src, len);\r
+       return dest;\r
+}\r
+\r
+/* TODO: more validation? @@@ */\r
+static __inline int utf8len(const FLAC__byte *utf8)\r
+{\r
+       FLAC__ASSERT(0 != utf8);\r
+       if ((*utf8 & 0x80) == 0)\r
+               return 1;\r
+       else if ((*utf8 & 0xE0) == 0xC0)\r
+               return 2;\r
+       else if ((*utf8 & 0xF0) == 0xE0)\r
+               return 3;\r
+       else return 0;\r
+}\r
+\r
+/* TODO: validation? @@@ */\r
+static __inline int utf8_to_ucs2(const FLAC__byte *utf8, wchar_t *ucs2)\r
+{\r
+       int len;\r
+       FLAC__ASSERT(utf8!=0 && *utf8!=0 && ucs2!=0);\r
+\r
+       if (!(len = utf8len(utf8))) return 0;\r
+\r
+       if (len == 1)\r
+               *ucs2 = *utf8;\r
+       else if (len == 2)\r
+               *ucs2 = (*utf8 & 0x3F)<<6 | (*(utf8+1) & 0x3F);\r
+       else if (len == 3)\r
+               *ucs2 = (*utf8 & 0x1F)<<12 | (*(utf8+1) & 0x3F)<<6 | (*(utf8+2) & 0x3F);\r
+       else {\r
+               FLAC__ASSERT(len == 0);\r
+       }\r
+\r
+       return len;\r
+}\r
+\r
+wchar_t *FLAC_plugin__convert_utf8_to_ucs2(const char *src, unsigned length)\r
+{\r
+       wchar_t *out, *p;\r
+       const char *s;\r
+       int len = 0;\r
+       /* calculate length */\r
+       for (s=src; length && *s; len++)\r
+       {\r
+               int l = utf8len(s);\r
+               if (!l) break;\r
+               s += l;\r
+               length -= l;\r
+       }\r
+       /* allocate */\r
+       len++;\r
+       p = out = (wchar_t*)malloc(len * sizeof(wchar_t));\r
+       if (!out) return NULL;\r
+       /* convert */\r
+       for (s=src; --len; p++)\r
+       {\r
+               int l = utf8_to_ucs2(s, p);\r
+               /* l==0 is possible, because real conversion */\r
+               /* might do more careful validation */\r
+               if (!l) break;\r
+               s += l;\r
+       }\r
+       *p = 0;\r
+\r
+       return out;\r
+}\r
+\r
+static __inline int ucs2len(wchar_t ucs2)\r
+{\r
+       if (ucs2 < 0x0080)\r
+               return 1;\r
+       else if (ucs2 < 0x0800)\r
+               return 2;\r
+       else return 3;\r
+}\r
+\r
+static __inline int ucs2_to_utf8(wchar_t ucs2, FLAC__byte *utf8)\r
+{\r
+       if (ucs2 < 0x080)\r
+       {\r
+               utf8[0] = (FLAC__byte)ucs2;\r
+               return 1;\r
+       }\r
+       else if (ucs2 < 0x800)\r
+       {\r
+               utf8[0] = 0xc0 | (ucs2 >> 6);\r
+               utf8[1] = 0x80 | (ucs2 & 0x3f);\r
+               return 2;\r
+       }\r
+       else\r
+       {\r
+               utf8[0] = 0xe0 | (ucs2 >> 12);\r
+               utf8[1] = 0x80 | ((ucs2 >> 6) & 0x3f);\r
+               utf8[2] = 0x80 | (ucs2 & 0x3f);\r
+               return 3;\r
+       }\r
+}\r
+\r
+char *FLAC_plugin__convert_ucs2_to_utf8(const wchar_t *src)\r
+{\r
+       const wchar_t *s;\r
+       char *out, *p;\r
+       int len = 0;\r
+       FLAC__ASSERT(0 != src);\r
+       /* calculate length */\r
+       for (s=src; *s; s++)\r
+               len += ucs2len(*s);\r
+       /* allocate */\r
+       len++;\r
+       p = out = malloc(len);\r
+       if (!out) return NULL;\r
+       /* convert */\r
+       for (s=src; *s; s++)\r
+       {\r
+               int l = ucs2_to_utf8(*s, p);\r
+               p += l;\r
+       }\r
+       *p = 0;\r
+\r
+       return out;\r
+}\r
+\r
+/*\r
+ *  init/clear/delete\r
+ */\r
+\r
+FLAC_Plugin__CanonicalTag *FLAC_plugin__canonical_tag_new()\r
+{\r
+       FLAC_Plugin__CanonicalTag *object = (FLAC_Plugin__CanonicalTag*)malloc(sizeof(FLAC_Plugin__CanonicalTag));\r
+       if (object != 0)\r
+               FLAC_plugin__canonical_tag_init(object);\r
+       return object;\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_delete(FLAC_Plugin__CanonicalTag *object)\r
+{\r
+       FLAC_plugin__canonical_tag_clear(object);\r
+       free(object);\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_init(FLAC_Plugin__CanonicalTag *object)\r
+{\r
+       object->head = object->tail = 0;\r
+       object->count = 0;\r
+}\r
+\r
+static void FLAC_plugin__canonical_tag_clear_entry(FLAC__tag_entry *entry)\r
+{\r
+       free(entry->name);\r
+       free(entry->value);\r
+       free(entry);\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_clear(FLAC_Plugin__CanonicalTag *object)\r
+{\r
+       FLAC__tag_entry *entry = object->head;\r
+\r
+       while (entry)\r
+       {\r
+               FLAC__tag_entry *next = entry->next;\r
+               FLAC_plugin__canonical_tag_clear_entry(entry);\r
+               entry = next;\r
+       }\r
+\r
+       FLAC_plugin__canonical_tag_init(object);\r
+}\r
+\r
+/*\r
+ *  internal\r
+ */\r
+\r
+static FLAC__tag_entry *FLAC_plugin__canonical_find(const FLAC_Plugin__CanonicalTag *tag, const wchar_t *name)\r
+{\r
+       FLAC__tag_entry *entry = tag->head;\r
+\r
+       while (entry)\r
+       {\r
+#if defined _MSC_VER || defined __MINGW32__\r
+#define FLAC__WCSCASECMP wcsicmp\r
+#else\r
+#define FLAC__WCSCASECMP wcscasecmp\r
+#endif\r
+               if (!FLAC__WCSCASECMP(name, entry->name))\r
+#undef FLAC__WCSCASECMP\r
+                       break;\r
+               entry = entry->next;\r
+       }\r
+\r
+       return entry;\r
+}\r
+\r
+/* NOTE: does NOT copy strings. takes ownership over passed strings. */\r
+static void FLAC_plugin__canonical_add_tail(FLAC_Plugin__CanonicalTag *tag, wchar_t *name, wchar_t *value)\r
+{\r
+       FLAC__tag_entry *entry = (FLAC__tag_entry*)malloc(sizeof(FLAC__tag_entry));\r
+       if (!entry)\r
+       {\r
+               free(name);\r
+               free(value);\r
+               return;\r
+       }\r
+       /* init */\r
+       entry->name = name;\r
+       entry->value = value;\r
+       /* add */\r
+       entry->prev = tag->tail;\r
+       if (tag->tail)\r
+               tag->tail->next = entry;\r
+       tag->tail = entry;\r
+       if (!tag->head)\r
+               tag->head = entry;\r
+       entry->next = 0;\r
+       tag->count++;\r
+}\r
+\r
+static void FLAC_plugin__canonical_add_new(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, const wchar_t *value)\r
+{\r
+       FLAC_plugin__canonical_add_tail(tag, wcsdup(name), wcsdup(value));\r
+}\r
+\r
+/* NOTE: does NOT copy value, but copies name */\r
+static void FLAC_plugin__canonical_set_nc(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, wchar_t *value)\r
+{\r
+       FLAC__tag_entry *entry = FLAC_plugin__canonical_find(tag, name);\r
+\r
+       if (entry)\r
+       {\r
+               free(entry->value);\r
+               entry->value = value;\r
+       }\r
+       else FLAC_plugin__canonical_add_tail(tag, wcsdup(name), value);\r
+}\r
+\r
+/* NOTE: does NOT copy strings. takes ownership over passed strings. (except sep!) */\r
+static void FLAC_plugin__canonical_add_nc(FLAC_Plugin__CanonicalTag *tag, wchar_t *name, wchar_t *value, const wchar_t *sep)\r
+{\r
+       FLAC__tag_entry *entry;\r
+\r
+       if (sep && (entry = FLAC_plugin__canonical_find(tag, name)))\r
+       {\r
+               unsigned newlen = wcslen(entry->value) + wcslen(value) + wcslen(sep) + 1;\r
+               wchar_t *newvalue = realloc(entry->value, newlen*sizeof(wchar_t));\r
+\r
+               if (newvalue)\r
+               {\r
+                       entry->value = newvalue;\r
+                       wcscat(entry->value, sep);\r
+                       wcscat(entry->value, value);\r
+               }\r
+\r
+               free(name);\r
+               free(value);\r
+       }\r
+       else FLAC_plugin__canonical_add_tail(tag, name, value);\r
+}\r
+\r
+/*\r
+ *  manipulation\r
+ */\r
+\r
+void FLAC_plugin__canonical_set(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, const wchar_t *value)\r
+{\r
+       FLAC_plugin__canonical_set_nc(tag, name, wcsdup(value));\r
+}\r
+\r
+void FLAC_plugin__canonical_set_new(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, const wchar_t *value)\r
+{\r
+       FLAC__tag_entry *entry = FLAC_plugin__canonical_find(tag, name);\r
+       if (!entry) FLAC_plugin__canonical_add_new(tag, name, value);\r
+}\r
+\r
+void FLAC_plugin__canonical_set_ansi(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, const char *value)\r
+{\r
+       wchar_t *val = FLAC_plugin__convert_ansi_to_wide(value);\r
+       if (val) FLAC_plugin__canonical_set_nc(tag, name, val);\r
+}\r
+\r
+void FLAC_plugin__canonical_add(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name, const wchar_t *value, const wchar_t *sep)\r
+{\r
+       FLAC__tag_entry *entry;\r
+\r
+       if (sep && (entry = FLAC_plugin__canonical_find(tag, name)))\r
+       {\r
+               unsigned newlen = wcslen(entry->value) + wcslen(value) + wcslen(sep) + 1;\r
+               wchar_t *newvalue = realloc(entry->value, newlen*sizeof(wchar_t));\r
+\r
+               if (newvalue)\r
+               {\r
+                       entry->value = newvalue;\r
+                       wcscat(entry->value, sep);\r
+                       wcscat(entry->value, value);\r
+               }\r
+       }\r
+       else FLAC_plugin__canonical_add_new(tag, name, value);\r
+}\r
+\r
+void FLAC_plugin__canonical_add_utf8(FLAC_Plugin__CanonicalTag *tag, const char *name, const char *value, unsigned namelen, unsigned vallen, const char *sep)\r
+{\r
+       wchar_t *n = FLAC_plugin__convert_utf8_to_ucs2(name, namelen);\r
+       wchar_t *v = FLAC_plugin__convert_utf8_to_ucs2(value, vallen);\r
+       wchar_t *s = sep ? FLAC_plugin__convert_utf8_to_ucs2(sep, -1) : 0;\r
+\r
+       if (n && v)\r
+       {\r
+               FLAC_plugin__canonical_add_nc(tag, n, v, s);\r
+       }\r
+       else\r
+       {\r
+               if (n) free(n);\r
+               if (v) free(v);\r
+       }\r
+       if (s) free(s);\r
+}\r
+\r
+const wchar_t *FLAC_plugin__canonical_get(const FLAC_Plugin__CanonicalTag *tag, const wchar_t *name)\r
+{\r
+       FLAC__tag_entry *entry = FLAC_plugin__canonical_find(tag, name);\r
+       return entry ? entry->value : 0;\r
+}\r
+\r
+FLAC__bool FLAC_plugin__canonical_remove(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name)\r
+{\r
+       FLAC__tag_entry *entry = FLAC_plugin__canonical_find(tag, name);\r
+\r
+       if (entry)\r
+       {\r
+               if (entry->prev)\r
+                       entry->prev->next = entry->next;\r
+               else tag->head = entry->next;\r
+\r
+               if (entry->next)\r
+                       entry->next->prev = entry->prev;\r
+               else tag->tail = entry->prev;\r
+\r
+               FLAC_plugin__canonical_tag_clear_entry(entry);\r
+               tag->count--;\r
+               return true;\r
+       }\r
+\r
+       return false;\r
+}\r
+\r
+void FLAC_plugin__canonical_remove_all(FLAC_Plugin__CanonicalTag *tag, const wchar_t *name)\r
+{\r
+       while (FLAC_plugin__canonical_remove(tag, name));\r
+}\r
+\r
+char *FLAC_plugin__canonical_get_formatted(FLAC__tag_iterator it)\r
+{\r
+       int len1 = wcslen(it->name);\r
+       int len2 = wcslen(it->value);\r
+       int len  = len1 + len2 + 1;\r
+       wchar_t *val = malloc((len+1) * sizeof(wchar_t));\r
+\r
+       if (val)\r
+       {\r
+               char *res;\r
+\r
+               memcpy(val, it->name, len1 * sizeof(wchar_t));\r
+               val[len1] = '=';\r
+               memcpy(val+len1+1, it->value, len2 * sizeof(wchar_t));\r
+               val[len] = 0;\r
+\r
+               res = FLAC_plugin__convert_ucs2_to_utf8(val);\r
+               free(val);\r
+               return res;\r
+       }\r
+\r
+       return NULL;\r
+}\r
+\r
+/*\r
+ *  merging\r
+ */\r
+\r
+void FLAC_plugin__canonical_tag_merge(FLAC_Plugin__CanonicalTag *dest, const FLAC_Plugin__CanonicalTag *src)\r
+{\r
+       FLAC__tag_entry *entry = src->head;\r
+\r
+       while (entry)\r
+       {\r
+               FLAC_plugin__canonical_set_new(dest, entry->name, entry->value);\r
+               entry = entry->next;\r
+       }\r
+}\r
+\r
+static wchar_t *local__copy_field(const char *src, unsigned n)\r
+{\r
+       const char *p = src + n;\r
+       wchar_t *dest;\r
+       FLAC__ASSERT(n > 0);\r
+\r
+       while (p>src && *(--p)==' ');\r
+\r
+       n = p - src + 1;\r
+       if (!n) return NULL;\r
+\r
+       if ((dest = malloc((n+1)*sizeof(wchar_t))) != 0)\r
+       {\r
+               mbstowcs(dest, src, n);\r
+               dest[n] = 0;\r
+       }\r
+       return dest;\r
+}\r
+\r
+static void local__add_id3_field(FLAC_Plugin__CanonicalTag *object, const char *value, size_t length, const wchar_t *new_name)\r
+{\r
+       wchar_t *tmp;\r
+       if (0 != value && length > 0) {\r
+               tmp = local__copy_field(value, length);\r
+               if (tmp)\r
+                       FLAC_plugin__canonical_add_tail(object, wcsdup(new_name), tmp);\r
+       }\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_convert_from_id3v1(FLAC_Plugin__CanonicalTag *object, const FLAC_Plugin__Id3v1_Tag *id3v1_tag)\r
+{\r
+       wchar_t *tmp;\r
+       FLAC_plugin__canonical_tag_clear(object);\r
+\r
+       local__add_id3_field(object, id3v1_tag->title, 30, L"TITLE");\r
+       local__add_id3_field(object, id3v1_tag->artist, 30, L"ARTIST");\r
+       local__add_id3_field(object, id3v1_tag->album, 30, L"ALBUM");\r
+       local__add_id3_field(object, id3v1_tag->year, 4, L"YEAR");\r
+\r
+       /* check for v1.1 tags */\r
+       if (id3v1_tag->zero == 0)\r
+       {\r
+               if (id3v1_tag->track && (tmp=(wchar_t*)malloc(sizeof(id3v1_tag->track)*4*sizeof(wchar_t)))!=0)\r
+               {\r
+#ifdef _MSC_VER\r
+                       _itow(id3v1_tag->track, tmp, 10);\r
+#else\r
+                       local__itow(id3v1_tag->track, tmp);\r
+#endif\r
+                       FLAC_plugin__canonical_add_tail(object, wcsdup(L"TRACKNUMBER"), tmp);\r
+               }\r
+               local__add_id3_field(object, id3v1_tag->comment, 28, L"DESCRIPTION");\r
+       }\r
+       else\r
+       {\r
+               local__add_id3_field(object, id3v1_tag->comment, 30, L"DESCRIPTION");\r
+       }\r
+\r
+       tmp = FLAC_plugin__convert_ansi_to_wide(FLAC_plugin__id3v1_tag_get_genre_as_string(id3v1_tag->genre));\r
+       if (tmp) FLAC_plugin__canonical_add_tail(object, wcsdup(L"GENRE"), tmp);\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_convert_from_id3v2(FLAC_Plugin__CanonicalTag *object, const FLAC_Plugin__Id3v2_Tag *id3v2_tag)\r
+{\r
+       FLAC_plugin__canonical_tag_clear(object);\r
+\r
+       local__add_id3_field(object, id3v2_tag->title          , strlen(id3v2_tag->title)          , L"TITLE");\r
+       local__add_id3_field(object, id3v2_tag->composer       , strlen(id3v2_tag->composer)       , L"ARTIST");\r
+       local__add_id3_field(object, id3v2_tag->performer      , strlen(id3v2_tag->performer)      , L"PERFORMER");\r
+       local__add_id3_field(object, id3v2_tag->album          , strlen(id3v2_tag->album)          , L"ALBUM");\r
+       local__add_id3_field(object, id3v2_tag->year_recorded  , strlen(id3v2_tag->year_recorded)  , L"YEAR_RECORDED");\r
+       local__add_id3_field(object, id3v2_tag->year_performed , strlen(id3v2_tag->year_performed) , L"YEAR_PERFORMED");\r
+       local__add_id3_field(object, id3v2_tag->track_number   , strlen(id3v2_tag->track_number)   , L"TRACKNUMBER");\r
+       local__add_id3_field(object, id3v2_tag->tracks_in_album, strlen(id3v2_tag->tracks_in_album), L"TRACKS_IN_ALBUM");\r
+       local__add_id3_field(object, id3v2_tag->genre          , strlen(id3v2_tag->genre)          , L"GENRE");\r
+       local__add_id3_field(object, id3v2_tag->comment        , strlen(id3v2_tag->comment)        , L"DESCRIPTION");\r
+}\r
+\r
+static FLAC__bool local__get_id3v1_tag_as_canonical(const char *filename, FLAC_Plugin__CanonicalTag *tag)\r
+{\r
+       FLAC_Plugin__Id3v1_Tag id3v1_tag;\r
+\r
+       if (FLAC_plugin__id3v1_tag_get(filename, &id3v1_tag))\r
+       {\r
+               FLAC_plugin__canonical_tag_convert_from_id3v1(tag, &id3v1_tag);\r
+               return true;\r
+       }\r
+       return false;\r
+}\r
+\r
+static FLAC__bool local__get_id3v2_tag_as_canonical(const char *filename, FLAC_Plugin__CanonicalTag *tag)\r
+{\r
+       FLAC_Plugin__Id3v2_Tag id3v2_tag;\r
+\r
+       if (FLAC_plugin__id3v2_tag_get(filename, &id3v2_tag))\r
+       {\r
+               FLAC_plugin__canonical_tag_convert_from_id3v2(tag, &id3v2_tag);\r
+               return true;\r
+       }\r
+       return false;\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_add_id3v1(const char *filename, FLAC_Plugin__CanonicalTag *tag)\r
+{\r
+       FLAC_Plugin__CanonicalTag id3v1_tag;\r
+\r
+       FLAC_plugin__canonical_tag_init(&id3v1_tag);\r
+       (void)local__get_id3v1_tag_as_canonical(filename, &id3v1_tag);\r
+       FLAC_plugin__canonical_tag_merge(tag, &id3v1_tag);\r
+\r
+       FLAC_plugin__canonical_tag_clear(&id3v1_tag);\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_add_id3v2(const char *filename, FLAC_Plugin__CanonicalTag *tag)\r
+{\r
+       FLAC_Plugin__CanonicalTag id3v2_tag;\r
+\r
+       FLAC_plugin__canonical_tag_init(&id3v2_tag);\r
+       (void)local__get_id3v2_tag_as_canonical(filename, &id3v2_tag);\r
+       FLAC_plugin__canonical_tag_merge(tag, &id3v2_tag);\r
+\r
+       FLAC_plugin__canonical_tag_clear(&id3v2_tag);\r
+}\r
+\r
+void FLAC_plugin__canonical_tag_get_combined(const char *filename, FLAC_Plugin__CanonicalTag *tag, const char *sep)\r
+{\r
+       FLAC_plugin__vorbiscomment_get(filename, tag, sep);\r
+       FLAC_plugin__canonical_tag_add_id3v2(filename, tag);\r
+       FLAC_plugin__canonical_tag_add_id3v1(filename, tag);\r
+}\r