changeset 688:cc1969408403 trunk

[svn] - add scrobbler support
author nenolod
date Sat, 25 Feb 2006 12:11:20 -0800
parents 0a220c3183b8
children 43a4216d3dc1
files Plugins/General/scrobbler/Makefile.in Plugins/General/scrobbler/config.h Plugins/General/scrobbler/fmt.c Plugins/General/scrobbler/fmt.h Plugins/General/scrobbler/gtkstuff.c Plugins/General/scrobbler/gtkstuff.h Plugins/General/scrobbler/md5.c Plugins/General/scrobbler/md5.h Plugins/General/scrobbler/queue.c Plugins/General/scrobbler/queue.h Plugins/General/scrobbler/scrobbler.c Plugins/General/scrobbler/scrobbler.h Plugins/General/scrobbler/tags/Makefile.in Plugins/General/scrobbler/tags/ape.c Plugins/General/scrobbler/tags/cdaudio.c Plugins/General/scrobbler/tags/id3genres.c Plugins/General/scrobbler/tags/id3v1.c Plugins/General/scrobbler/tags/id3v2.c Plugins/General/scrobbler/tags/include/ape.h Plugins/General/scrobbler/tags/include/bmp_vfs.h Plugins/General/scrobbler/tags/include/cdaudio.h Plugins/General/scrobbler/tags/include/endian.h Plugins/General/scrobbler/tags/include/id3v1.h Plugins/General/scrobbler/tags/include/id3v2.h Plugins/General/scrobbler/tags/include/itunes.h Plugins/General/scrobbler/tags/include/tags.h Plugins/General/scrobbler/tags/include/unicode.h Plugins/General/scrobbler/tags/include/vorbis.h Plugins/General/scrobbler/tags/include/wma.h Plugins/General/scrobbler/tags/itunes.c Plugins/General/scrobbler/tags/tags.c Plugins/General/scrobbler/tags/unicode.c Plugins/General/scrobbler/tags/vorbis.c Plugins/General/scrobbler/tags/wma.c Plugins/General/scrobbler/xmms_scrobbler.c configure.ac mk/rules.mk.in
diffstat 37 files changed, 6229 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/Makefile.in	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,23 @@
+include ../../../mk/rules.mk
+include ../../../mk/objective.mk
+
+SUBDIRS = tags
+
+OBJECTIVE_LIBS = libscrobbler.so
+
+LIBDIR = $(plugindir)/$(GENERAL_PLUGIN_DIR)
+
+LIBADD += $(GTK_LIBS) $(CURL_LIBS) $(MUSICBRAINZ_LIBS) tags/libmetatag.a
+
+SOURCES = 			\
+	fmt.c			\
+	gtkstuff.c		\
+	md5.c			\
+	queue.c			\
+	scrobbler.c		\
+	xmms_scrobbler.c
+
+
+CFLAGS += -fPIC -DPIC $(GTK_CFLAGS) $(CURL_CFLAGS) -I../../../intl -I../../..
+
+OBJECTS = ${SOURCES:.c=.o}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/config.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,14 @@
+#include "../../../config.h"
+
+#ifndef __SCROBBLER_CONFIG_H__
+#define __SCROBBLER_CONFIG_H__
+
+#define DEBUG 0
+#define META_DEBUG 0
+#define SUB_DEBUG 0
+#define CLIENT "Audacious"
+#define USER_AGENT "AudioScrobbler/1.1" PACKAGE_NAME "/" PACKAGE_VERSION
+#define MAKE_BMP
+#define ALLOW_MULTIPLE
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/fmt.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,84 @@
+#include <wchar.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "fmt.h"
+#include <curl/curl.h>
+
+char *fmt_escape(char *str)
+{
+	return curl_escape(str, 0);
+}
+
+char *fmt_unescape(char *str)
+{
+	return curl_unescape(str, 0);
+}
+
+char *fmt_timestr(time_t t, int gmt)
+{
+	struct tm *tm;
+	static char buf[30];
+
+	tm = gmt ? gmtime(&t) : localtime(&t);
+	snprintf(buf, sizeof(buf), "%d-%.2d-%.2d %.2d:%.2d:%.2d",
+			tm->tm_year + 1900,
+			tm->tm_mon + 1,
+			tm->tm_mday,
+			tm->tm_hour,
+			tm->tm_min,
+			tm->tm_sec);
+	return buf;
+}
+
+char *fmt_vastr(char *fmt, ...)
+{
+	va_list ap;
+	static char buf[4096];
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+	return buf;
+}
+
+void fmt_debug(char *file, const char *fun, char *str)
+{
+	fprintf(stderr, "%s [%s] %s: %s\n", fmt_timestr(time(NULL), 0), 
+			file, fun, str);
+}
+
+char *fmt_string_pack(char *string, char *fmt, ...)
+{
+	int buflen = 0, stringlen = 0;
+	char buf[4096];
+	va_list ap;
+
+	va_start(ap, fmt);
+	vsnprintf(buf, sizeof(buf), fmt, ap);
+	va_end(ap);
+	if(string != NULL) stringlen = strlen(string);
+	buflen = strlen(buf);
+
+	string = realloc(string, stringlen + buflen + 1);
+	memcpy(string + stringlen, buf, buflen);
+	*(string + stringlen + buflen) = 0;
+	return string;
+}
+
+int fmt_strcasecmp(const char *s1, const char *s2)
+{
+	while (toupper(*s1) == toupper(*s2++))
+		if (!*s1++)
+			return 0;
+	return toupper(s1[0]) - toupper(s2[-1]);
+}
+
+int fmt_strncasecmp(const char *s1, const char *s2, size_t n)
+{
+	while (toupper(*s1) == toupper(*s2++) && --n)
+		if(!*s1++)
+			return 0;
+	return n ? toupper(s1[0]) - toupper(s2[-1]) : 0;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/fmt.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,15 @@
+#ifndef FMT_H
+#define FMT_H 1
+#define pdebug(s, b) if(b) { fmt_debug(__FILE__, __FUNCTION__, (s)); }
+
+#include <time.h>
+
+char *fmt_escape(char *);
+char *fmt_unescape(char *);
+char *fmt_timestr(time_t, int);
+char *fmt_vastr(char *, ...);
+void fmt_debug(char *, const char *, char *);
+char *fmt_string_pack(char *, char *, ...);
+int fmt_strcasecmp(const char *s1, const char *s2);
+int fmt_strncasecmp(const char *s1, const char *s2, size_t n);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/gtkstuff.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,189 @@
+#include "libaudacious/util.h"
+#include "libaudacious/configdb.h"
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <stdio.h>
+#include <string.h>
+#include "config.h"
+#include "md5.h"
+
+static GtkWidget 	*eduname,
+			*edpwd;
+static int errorbox_done;
+void about_show(void)
+{
+	static GtkWidget *aboutbox;
+	gchar *tmp;
+	if (aboutbox)
+		return;
+
+	tmp = g_strdup_printf("Audacious AudioScrobbler Plugin\n\n"
+				"Originally created by Audun Hove <audun@nlc.no> and Pipian <pipian@pipian.com>\n");
+	aboutbox = xmms_show_message(_("About Scrobbler Plugin"),
+			_(tmp),
+			_("Ok"), FALSE, NULL, NULL);
+
+	g_free(tmp);
+	gtk_signal_connect(GTK_OBJECT(aboutbox), "destroy",
+			GTK_SIGNAL_FUNC(gtk_widget_destroyed), &aboutbox);
+}
+
+static void set_errorbox_done(GtkWidget *errorbox, GtkWidget **errorboxptr)
+{
+	errorbox_done = -1;
+	gtk_widget_destroyed(errorbox, errorboxptr);
+}
+
+void init_errorbox_done(void)
+{
+	errorbox_done = 1;
+}
+
+int get_errorbox_done(void)
+{
+	return errorbox_done;
+}
+
+void errorbox_show(char *errortxt)
+{
+	static GtkWidget *errorbox;
+	gchar *tmp;
+
+	if(errorbox_done != 1)
+		return;
+	errorbox_done = 0;
+	tmp = g_strdup_printf("There has been an error"
+			" that may require your attention.\n\n"
+			"Contents of server error:\n\n"
+			"%s\n",
+			errortxt);
+
+	errorbox = xmms_show_message("Scrobbler Error",
+			tmp,
+			"OK", FALSE, NULL, NULL);
+	g_free(tmp);
+	gtk_signal_connect(GTK_OBJECT(errorbox), "destroy",
+			GTK_SIGNAL_FUNC(set_errorbox_done), &errorbox);
+}
+
+static char *hexify(char *pass, int len)
+{
+	static char buf[33];
+	char *bp = buf;
+	char hexchars[] = "0123456789abcdef";
+	int i;
+	
+	memset(buf, 0, sizeof(buf));
+	
+	for(i = 0; i < len; i++) {
+		*(bp++) = hexchars[(pass[i] >> 4) & 0x0f];
+		*(bp++) = hexchars[pass[i] & 0x0f];
+	}
+	*bp = 0;
+	return buf;
+}
+
+static void saveconfig(GtkWidget *wid, gpointer data)
+{
+	ConfigDb *cfgfile;
+
+	const char *pwd = gtk_entry_get_text(GTK_ENTRY(edpwd));
+	const char *uid = gtk_entry_get_text(GTK_ENTRY(eduname));
+
+	if ((cfgfile = bmp_cfg_db_open())) {
+	
+		md5_state_t md5state;
+		unsigned char md5pword[16];
+	
+		bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "username", (char *)uid);
+
+		if (pwd != NULL && pwd[0] != '\0') {
+			md5_init(&md5state);
+			md5_append(&md5state, (unsigned const char *)pwd, strlen(pwd));
+			md5_finish(&md5state, md5pword);
+			bmp_cfg_db_set_string(cfgfile, "audioscrobbler", "password",
+					(char *)hexify(md5pword, sizeof(md5pword)));
+		}
+		bmp_cfg_db_close(cfgfile);
+	}
+	gtk_widget_destroy(GTK_WIDGET(data));
+}
+
+void configure_dialog(void)
+{
+	static GtkWidget *cnfdlg;
+	GtkWidget	*btnok,
+			*btncancel,
+			*vbox,
+			*hbox,
+			*unhbox,
+			*pwhbox,
+			*lblun,
+			*lblpw,
+			*frame;
+			
+	ConfigDb 	*cfgfile;
+	
+	if (cnfdlg)
+		return;
+
+	cnfdlg = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+	gtk_window_set_type_hint(GTK_WINDOW(cnfdlg),
+			GDK_WINDOW_TYPE_HINT_DIALOG);
+	gtk_window_set_title(GTK_WINDOW(cnfdlg),
+			"Scrobbler configuration");
+
+	gtk_signal_connect(GTK_OBJECT(cnfdlg), "destroy",
+			GTK_SIGNAL_FUNC(gtk_widget_destroyed), &cnfdlg);
+	
+	vbox = gtk_vbox_new(FALSE, 0);
+
+	unhbox = gtk_hbox_new(FALSE, 0);
+	eduname = gtk_entry_new();
+	lblun = gtk_label_new("Username");
+	gtk_box_pack_start(GTK_BOX(unhbox), lblun, FALSE, FALSE, 3);
+	gtk_box_pack_start(GTK_BOX(unhbox), eduname, FALSE, FALSE, 3);
+	
+	pwhbox = gtk_hbox_new(FALSE, 0);
+	edpwd = gtk_entry_new();
+	lblpw = gtk_label_new("Password");
+	gtk_entry_set_visibility(GTK_ENTRY(edpwd), FALSE);
+	gtk_box_pack_start(GTK_BOX(pwhbox), lblpw, FALSE, FALSE, 3);
+	gtk_box_pack_start(GTK_BOX(pwhbox), edpwd, FALSE, FALSE, 3);
+	
+	gtk_box_pack_start(GTK_BOX(vbox), unhbox, FALSE, FALSE, 3);
+	gtk_box_pack_start(GTK_BOX(vbox), pwhbox, FALSE, FALSE, 3);
+	
+	hbox = gtk_hbox_new(FALSE, 0);
+
+	btnok = gtk_button_new_with_label("Ok");
+	gtk_signal_connect(GTK_OBJECT(btnok), "clicked",
+			GTK_SIGNAL_FUNC(saveconfig), GTK_OBJECT(cnfdlg));
+	
+	btncancel = gtk_button_new_with_label("Cancel");
+	gtk_signal_connect_object(GTK_OBJECT(btncancel), "clicked",
+			GTK_SIGNAL_FUNC(gtk_widget_destroy), GTK_OBJECT(cnfdlg));
+	gtk_box_pack_start(GTK_BOX(hbox), btnok, FALSE, FALSE, 3);
+	gtk_box_pack_start(GTK_BOX(hbox), btncancel, FALSE, FALSE, 3);
+	
+	gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 3);
+
+	frame = gtk_frame_new(" The plugin will have to be restarted for changes to take effect! ");
+	gtk_container_add(GTK_CONTAINER(frame), vbox);
+	gtk_container_add(GTK_CONTAINER(cnfdlg), frame);
+
+	if ((cfgfile = bmp_cfg_db_open())) {
+		gchar *username = NULL;
+		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "username",
+			&username);
+		if (username) {
+			gtk_entry_set_text(GTK_ENTRY(eduname), username);
+			g_free(username);
+		}
+		bmp_cfg_db_close(cfgfile);
+	}
+	
+	gtk_widget_show_all(cnfdlg);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/gtkstuff.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,8 @@
+#ifndef GTKSTUFF_H
+#define GTKSTUFF_H 1
+void about_show(void);
+void errorbox_show(char *);
+void init_errorbox_done(void);
+int get_errorbox_done(void);
+void configure_dialog(void);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/md5.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,381 @@
+/*
+  Copyright (C) 1999, 2000, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.c,v 1.1 2003/08/24 01:20:37 audhov Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.c is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
+	either statically or dynamically; added missing #include <string.h>
+	in library.
+  2002-03-11 lpd Corrected argument list for main(), and added int return
+	type, in test program and T value program.
+  2002-02-21 lpd Added missing #include <stdio.h> in test program.
+  2000-07-03 lpd Patched to eliminate warnings about "constant is
+	unsigned in ANSI C, signed in traditional"; made test program
+	self-checking.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
+  1999-05-03 lpd Original version.
+ */
+
+#include "md5.h"
+#include <string.h>
+
+#undef BYTE_ORDER	/* 1 = big-endian, -1 = little-endian, 0 = unknown */
+#ifdef ARCH_IS_BIG_ENDIAN
+#  define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
+#else
+#  define BYTE_ORDER 0
+#endif
+
+#define T_MASK ((md5_word_t)~0)
+#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
+#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
+#define T3    0x242070db
+#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
+#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
+#define T6    0x4787c62a
+#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
+#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
+#define T9    0x698098d8
+#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
+#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
+#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
+#define T13    0x6b901122
+#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
+#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
+#define T16    0x49b40821
+#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
+#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
+#define T19    0x265e5a51
+#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
+#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
+#define T22    0x02441453
+#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
+#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
+#define T25    0x21e1cde6
+#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
+#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
+#define T28    0x455a14ed
+#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
+#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
+#define T31    0x676f02d9
+#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
+#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
+#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
+#define T35    0x6d9d6122
+#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
+#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
+#define T38    0x4bdecfa9
+#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
+#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
+#define T41    0x289b7ec6
+#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
+#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
+#define T44    0x04881d05
+#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
+#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
+#define T47    0x1fa27cf8
+#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
+#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
+#define T50    0x432aff97
+#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
+#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
+#define T53    0x655b59c3
+#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
+#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
+#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
+#define T57    0x6fa87e4f
+#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
+#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
+#define T60    0x4e0811a1
+#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
+#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
+#define T63    0x2ad7d2bb
+#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
+
+
+static void
+md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
+{
+    md5_word_t
+	a = pms->abcd[0], b = pms->abcd[1],
+	c = pms->abcd[2], d = pms->abcd[3];
+    md5_word_t t;
+#if BYTE_ORDER > 0
+    /* Define storage only for big-endian CPUs. */
+    md5_word_t X[16];
+#else
+    /* Define storage for little-endian or both types of CPUs. */
+    md5_word_t xbuf[16];
+    const md5_word_t *X;
+#endif
+
+    {
+#if BYTE_ORDER == 0
+	/*
+	 * Determine dynamically whether this is a big-endian or
+	 * little-endian machine, since we can use a more efficient
+	 * algorithm on the latter.
+	 */
+	static const int w = 1;
+
+	if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
+#endif
+#if BYTE_ORDER <= 0		/* little-endian */
+	{
+	    /*
+	     * On little-endian machines, we can process properly aligned
+	     * data without copying it.
+	     */
+	    if (!((data - (const md5_byte_t *)0) & 3)) {
+		/* data are properly aligned */
+		X = (const md5_word_t *)data;
+	    } else {
+		/* not aligned */
+		memcpy(xbuf, data, 64);
+		X = xbuf;
+	    }
+	}
+#endif
+#if BYTE_ORDER == 0
+	else			/* dynamic big-endian */
+#endif
+#if BYTE_ORDER >= 0		/* big-endian */
+	{
+	    /*
+	     * On big-endian machines, we must arrange the bytes in the
+	     * right order.
+	     */
+	    const md5_byte_t *xp = data;
+	    int i;
+
+#  if BYTE_ORDER == 0
+	    X = xbuf;		/* (dynamic only) */
+#  else
+#    define xbuf X		/* (static only) */
+#  endif
+	    for (i = 0; i < 16; ++i, xp += 4)
+		xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
+	}
+#endif
+    }
+
+#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+    /* Round 1. */
+    /* Let [abcd k s i] denote the operation
+       a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
+#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + F(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+    /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  7,  T1);
+    SET(d, a, b, c,  1, 12,  T2);
+    SET(c, d, a, b,  2, 17,  T3);
+    SET(b, c, d, a,  3, 22,  T4);
+    SET(a, b, c, d,  4,  7,  T5);
+    SET(d, a, b, c,  5, 12,  T6);
+    SET(c, d, a, b,  6, 17,  T7);
+    SET(b, c, d, a,  7, 22,  T8);
+    SET(a, b, c, d,  8,  7,  T9);
+    SET(d, a, b, c,  9, 12, T10);
+    SET(c, d, a, b, 10, 17, T11);
+    SET(b, c, d, a, 11, 22, T12);
+    SET(a, b, c, d, 12,  7, T13);
+    SET(d, a, b, c, 13, 12, T14);
+    SET(c, d, a, b, 14, 17, T15);
+    SET(b, c, d, a, 15, 22, T16);
+#undef SET
+
+     /* Round 2. */
+     /* Let [abcd k s i] denote the operation
+          a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
+#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + G(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  1,  5, T17);
+    SET(d, a, b, c,  6,  9, T18);
+    SET(c, d, a, b, 11, 14, T19);
+    SET(b, c, d, a,  0, 20, T20);
+    SET(a, b, c, d,  5,  5, T21);
+    SET(d, a, b, c, 10,  9, T22);
+    SET(c, d, a, b, 15, 14, T23);
+    SET(b, c, d, a,  4, 20, T24);
+    SET(a, b, c, d,  9,  5, T25);
+    SET(d, a, b, c, 14,  9, T26);
+    SET(c, d, a, b,  3, 14, T27);
+    SET(b, c, d, a,  8, 20, T28);
+    SET(a, b, c, d, 13,  5, T29);
+    SET(d, a, b, c,  2,  9, T30);
+    SET(c, d, a, b,  7, 14, T31);
+    SET(b, c, d, a, 12, 20, T32);
+#undef SET
+
+     /* Round 3. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
+#define H(x, y, z) ((x) ^ (y) ^ (z))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + H(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  5,  4, T33);
+    SET(d, a, b, c,  8, 11, T34);
+    SET(c, d, a, b, 11, 16, T35);
+    SET(b, c, d, a, 14, 23, T36);
+    SET(a, b, c, d,  1,  4, T37);
+    SET(d, a, b, c,  4, 11, T38);
+    SET(c, d, a, b,  7, 16, T39);
+    SET(b, c, d, a, 10, 23, T40);
+    SET(a, b, c, d, 13,  4, T41);
+    SET(d, a, b, c,  0, 11, T42);
+    SET(c, d, a, b,  3, 16, T43);
+    SET(b, c, d, a,  6, 23, T44);
+    SET(a, b, c, d,  9,  4, T45);
+    SET(d, a, b, c, 12, 11, T46);
+    SET(c, d, a, b, 15, 16, T47);
+    SET(b, c, d, a,  2, 23, T48);
+#undef SET
+
+     /* Round 4. */
+     /* Let [abcd k s t] denote the operation
+          a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
+#define I(x, y, z) ((y) ^ ((x) | ~(z)))
+#define SET(a, b, c, d, k, s, Ti)\
+  t = a + I(b,c,d) + X[k] + Ti;\
+  a = ROTATE_LEFT(t, s) + b
+     /* Do the following 16 operations. */
+    SET(a, b, c, d,  0,  6, T49);
+    SET(d, a, b, c,  7, 10, T50);
+    SET(c, d, a, b, 14, 15, T51);
+    SET(b, c, d, a,  5, 21, T52);
+    SET(a, b, c, d, 12,  6, T53);
+    SET(d, a, b, c,  3, 10, T54);
+    SET(c, d, a, b, 10, 15, T55);
+    SET(b, c, d, a,  1, 21, T56);
+    SET(a, b, c, d,  8,  6, T57);
+    SET(d, a, b, c, 15, 10, T58);
+    SET(c, d, a, b,  6, 15, T59);
+    SET(b, c, d, a, 13, 21, T60);
+    SET(a, b, c, d,  4,  6, T61);
+    SET(d, a, b, c, 11, 10, T62);
+    SET(c, d, a, b,  2, 15, T63);
+    SET(b, c, d, a,  9, 21, T64);
+#undef SET
+
+     /* Then perform the following additions. (That is increment each
+        of the four registers by the value it had before this block
+        was started.) */
+    pms->abcd[0] += a;
+    pms->abcd[1] += b;
+    pms->abcd[2] += c;
+    pms->abcd[3] += d;
+}
+
+void
+md5_init(md5_state_t *pms)
+{
+    pms->count[0] = pms->count[1] = 0;
+    pms->abcd[0] = 0x67452301;
+    pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
+    pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
+    pms->abcd[3] = 0x10325476;
+}
+
+void
+md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
+{
+    const md5_byte_t *p = data;
+    int left = nbytes;
+    int offset = (pms->count[0] >> 3) & 63;
+    md5_word_t nbits = (md5_word_t)(nbytes << 3);
+
+    if (nbytes <= 0)
+	return;
+
+    /* Update the message length. */
+    pms->count[1] += nbytes >> 29;
+    pms->count[0] += nbits;
+    if (pms->count[0] < nbits)
+	pms->count[1]++;
+
+    /* Process an initial partial block. */
+    if (offset) {
+	int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
+
+	memcpy(pms->buf + offset, p, copy);
+	if (offset + copy < 64)
+	    return;
+	p += copy;
+	left -= copy;
+	md5_process(pms, pms->buf);
+    }
+
+    /* Process full blocks. */
+    for (; left >= 64; p += 64, left -= 64)
+	md5_process(pms, p);
+
+    /* Process a final partial block. */
+    if (left)
+	memcpy(pms->buf, p, left);
+}
+
+void
+md5_finish(md5_state_t *pms, md5_byte_t digest[16])
+{
+    static const md5_byte_t pad[64] = {
+	0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+	0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+    };
+    md5_byte_t data[8];
+    int i;
+
+    /* Save the length before padding. */
+    for (i = 0; i < 8; ++i)
+	data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
+    /* Pad to 56 bytes mod 64. */
+    md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
+    /* Append the length. */
+    md5_append(pms, data, 8);
+    for (i = 0; i < 16; ++i)
+	digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/md5.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,91 @@
+/*
+  Copyright (C) 1999, 2002 Aladdin Enterprises.  All rights reserved.
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+  L. Peter Deutsch
+  ghost@aladdin.com
+
+ */
+/* $Id: md5.h,v 1.1 2003/08/24 01:20:37 audhov Exp $ */
+/*
+  Independent implementation of MD5 (RFC 1321).
+
+  This code implements the MD5 Algorithm defined in RFC 1321, whose
+  text is available at
+	http://www.ietf.org/rfc/rfc1321.txt
+  The code is derived from the text of the RFC, including the test suite
+  (section A.5) but excluding the rest of Appendix A.  It does not include
+  any code or documentation that is identified in the RFC as being
+  copyrighted.
+
+  The original and principal author of md5.h is L. Peter Deutsch
+  <ghost@aladdin.com>.  Other authors are noted in the change history
+  that follows (in reverse chronological order):
+
+  2002-04-13 lpd Removed support for non-ANSI compilers; removed
+	references to Ghostscript; clarified derivation from RFC 1321;
+	now handles byte order either statically or dynamically.
+  1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
+  1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
+	added conditionalization for C++ compilation from Martin
+	Purschke <purschke@bnl.gov>.
+  1999-05-03 lpd Original version.
+ */
+
+#ifndef md5_INCLUDED
+#  define md5_INCLUDED
+
+/*
+ * This package supports both compile-time and run-time determination of CPU
+ * byte order.  If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
+ * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
+ * defined as non-zero, the code will be compiled to run only on big-endian
+ * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
+ * run on either big- or little-endian CPUs, but will run slightly less
+ * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
+ */
+
+typedef unsigned char md5_byte_t; /* 8-bit byte */
+typedef unsigned int md5_word_t; /* 32-bit word */
+
+/* Define the state of the MD5 Algorithm. */
+typedef struct md5_state_s {
+    md5_word_t count[2];	/* message length in bits, lsw first */
+    md5_word_t abcd[4];		/* digest buffer */
+    md5_byte_t buf[64];		/* accumulate block */
+} md5_state_t;
+
+#ifdef __cplusplus
+extern "C" 
+{
+#endif
+
+/* Initialize the algorithm. */
+void md5_init(md5_state_t *pms);
+
+/* Append a string to the message. */
+void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
+
+/* Finish the message and return the digest. */
+void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
+
+#ifdef __cplusplus
+}  /* end extern "C" */
+#endif
+
+#endif /* md5_INCLUDED */
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/queue.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <curl/curl.h>
+#include "tags/include/tags.h"
+#include "queue.h"
+#include "fmt.h"
+
+static item_t *q_queue = NULL;
+static item_t *q_queue_last = NULL;
+static int q_nitems;
+
+static void q_item_free(item_t *item)
+{
+	if (item == NULL)
+		return;
+	curl_free(item->artist);
+	curl_free(item->title);
+	curl_free(item->utctime);
+	curl_free(item->mb);
+	curl_free(item->album);
+	free(item);
+}
+
+void q_put(metatag_t *meta, int len)
+{
+	item_t *item;
+		
+	item = malloc(sizeof(item_t));
+	item->artist = fmt_escape(meta->artist);
+	item->title = fmt_escape(meta->title);
+	item->utctime = fmt_escape(fmt_timestr(time(NULL), 1));
+	snprintf(item->len, sizeof(item->len), "%d", len);
+	if(meta->mb == NULL)
+		item->mb = fmt_escape("");
+	else
+		item->mb = fmt_escape(meta->mb);
+	if(meta->album == NULL)
+		item->album = fmt_escape("");
+	else
+		item->album = fmt_escape(meta->album);
+
+	q_nitems++;
+
+	item->next = NULL;
+
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+        	q_queue_last->next = item;
+		q_queue_last = item;
+	}
+}
+
+item_t *q_put2(char *artist, char *title, char *len, char *time,
+		char *album, char *mb)
+{
+	char *temp = NULL;
+	item_t *item;
+
+	item = calloc(1, sizeof(item_t));
+	temp = fmt_unescape(artist);
+	item->artist = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(title);
+	item->title = fmt_escape(temp);
+	curl_free(temp);
+	memcpy(item->len, len, sizeof(len));
+	temp = fmt_unescape(time);
+	item->utctime = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(album);
+	item->album = fmt_escape(temp);
+	curl_free(temp);
+	temp = fmt_unescape(mb);
+	item->mb = fmt_escape(temp);
+	curl_free(temp);
+
+	q_nitems++;
+
+	item->next = NULL;
+	if(q_queue_last == NULL)
+		q_queue = q_queue_last = item;
+	else
+	{
+		q_queue_last->next = item;
+		q_queue_last = item;
+	}
+
+	return item;
+}
+
+item_t *q_peek(void)
+{
+	if (q_nitems == 0)
+		return NULL;
+	return q_queue;
+}
+
+item_t *q_peekall(int rewind)
+{
+	static item_t *citem = NULL;
+	item_t *temp_item;
+
+	if (rewind) {
+		citem = q_queue;
+		return NULL;
+	}
+
+	temp_item = citem;
+
+	if(citem != NULL)
+		citem = citem->next;
+
+	return temp_item;
+}
+
+int q_get(void)
+{
+	item_t *item;
+
+	if (q_nitems == 0)
+		return 0;
+	
+	item = q_queue;
+
+	if(item == NULL)
+		return 0;
+
+	q_nitems--;
+	q_queue = q_queue->next;
+
+	q_item_free(item);
+
+	if (q_nitems == 0)
+	{
+		q_queue_last = NULL;
+		return 0;
+	}
+
+	return -1;
+}
+
+void q_free(void)
+{
+	while (q_get());
+}
+
+int q_len(void)
+{
+	return q_nitems;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/queue.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,28 @@
+#ifndef QUEUE_H
+#define QUEUE_H 1
+
+#define I_ARTIST(i) i->artist
+#define I_TITLE(i) i->title
+#define I_TIME(i) i->utctime
+#define I_LEN(i) i->len
+#define I_MB(i) i->mb
+#define I_ALBUM(i) i->album
+
+typedef struct {
+	char *artist,
+		*title,
+		*mb,
+		*album,
+		*utctime,
+		len[16];
+	int numtries;
+	void *next;
+} item_t;
+void q_put(metatag_t *, int);
+item_t *q_put2(char *, char *, char *, char *, char *, char *);
+item_t *q_peek(void);
+item_t *q_peekall(int);
+int q_get(void);
+void q_free(void);
+int q_len(void);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/scrobbler.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,761 @@
+#include <pthread.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <curl/curl.h>
+#include <stdio.h>
+#include "fmt.h"
+#include "md5.h"
+#include "tags/include/tags.h"
+#include "queue.h"
+#include "scrobbler.h"
+#include "config.h"
+#include <glib.h>
+
+#define SCROBBLER_HS_URL "http://post.audioscrobbler.com"
+#define SCROBBLER_CLI_ID "xms"
+#define SCROBBLER_HS_WAIT 1800
+#define SCROBBLER_SB_WAIT 10
+#define SCROBBLER_VERSION "1.1"
+#define CACHE_SIZE 1024
+
+/* Scrobblerbackend for xmms plugin, first draft */
+
+static int	sc_hs_status,
+		sc_hs_timeout,
+		sc_hs_errors,
+		sc_sb_errors,
+		sc_bad_users,
+		sc_submit_interval,
+		sc_submit_timeout,
+		sc_srv_res_size,
+		sc_giveup,
+		sc_major_error_present;
+
+static char 	*sc_submit_url,
+		*sc_username,
+		*sc_password,
+		*sc_challenge_hash,
+		sc_response_hash[33],
+		*sc_srv_res,
+		sc_curl_errbuf[CURL_ERROR_SIZE],
+		*sc_major_error;
+
+static void dump_queue();
+
+/* Error functions */
+
+static void sc_throw_error(char *errortxt)
+{
+	sc_major_error_present = 1;
+	if(sc_major_error == NULL)
+		sc_major_error = strdup(errortxt);
+
+	return;
+}
+
+int sc_catch_error(void)
+{
+	return sc_major_error_present;
+}
+
+char *sc_fetch_error(void)
+{
+	return sc_major_error;
+}
+
+void sc_clear_error(void)
+{
+	sc_major_error_present = 0;
+	if(sc_major_error != NULL)
+		free(sc_major_error);
+	sc_major_error = NULL;
+
+	return;
+}
+
+static size_t sc_store_res(void *ptr, size_t size,
+		size_t nmemb,
+		void *stream)
+{
+	int len = size * nmemb;
+
+	sc_srv_res = realloc(sc_srv_res, sc_srv_res_size + len + 1);
+	memcpy(sc_srv_res + sc_srv_res_size,
+			ptr, len);
+	sc_srv_res_size += len;
+	return len;
+}
+
+static void sc_free_res(void)
+{
+	if(sc_srv_res != NULL)
+		free(sc_srv_res);
+	sc_srv_res = NULL;
+	sc_srv_res_size = 0;
+}
+
+static int sc_parse_hs_res(void)
+{
+	char *interval;
+
+	if (!sc_srv_res_size) {
+		pdebug("No reply from server", DEBUG);
+		return -1;
+	}
+	*(sc_srv_res + sc_srv_res_size) = 0;
+
+	if (!strncmp(sc_srv_res, "FAILED ", 7)) {
+		interval = strstr(sc_srv_res, "INTERVAL");
+		if(!interval) {
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		/* Throwing a major error, just in case */
+		/* sc_throw_error(fmt_vastr("%s", sc_srv_res));
+		   sc_hs_errors++; */
+		pdebug(fmt_vastr("error: %s", sc_srv_res), DEBUG);
+
+		return -1;
+	}
+
+	if (!strncmp(sc_srv_res, "UPDATE ", 7)) {
+		interval = strstr(sc_srv_res, "INTERVAL");
+		if(!interval)
+		{
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1;
+		*(sc_submit_url - 1) = 0;
+		sc_submit_url = strdup(sc_submit_url);
+		sc_challenge_hash = strchr(sc_srv_res, '\n') + 1;
+		*(sc_challenge_hash - 1) = 0;
+		sc_challenge_hash = strdup(sc_challenge_hash);
+
+		/* Throwing major error. Need to alert client to update. */
+		sc_throw_error(fmt_vastr("Please update Audacious.\n"
+			"Update available at: http://audacious-media-player.org"));
+		pdebug(fmt_vastr("update client: %s", sc_srv_res + 7), DEBUG);
+
+		/*
+		 * Russ isn't clear on whether we can submit with a not-updated
+		 * client.  Neither is RJ.  I use what we did before.
+		 */
+		sc_giveup = -1;
+		return -1;
+	}
+	if (!strncmp(sc_srv_res, "UPTODATE\n", 9)) {
+		sc_bad_users = 0;
+
+		interval = strstr(sc_srv_res, "INTERVAL");
+		if (!interval) {
+			pdebug("missing INTERVAL", DEBUG);
+			/*
+			 * This is probably a bad thing, but Russ seems to
+			 * think its OK to assume that an UPTODATE response
+			 * may not have an INTERVAL...  We return -1 anyway.
+			 */
+			return -1;
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		sc_submit_url = strchr(strchr(sc_srv_res, '\n') + 1, '\n') + 1;
+		*(sc_submit_url - 1) = 0;
+		sc_submit_url = strdup(sc_submit_url);
+		sc_challenge_hash = strchr(sc_srv_res, '\n') + 1;
+		*(sc_challenge_hash - 1) = 0;
+		sc_challenge_hash = strdup(sc_challenge_hash);
+
+		return 0;
+	}
+	if(!strncmp(sc_srv_res, "BADUSER", 7)) {
+		/* Throwing major error. */
+		sc_throw_error("Incorrect username/password.\n"
+				"Please fix in configuration.");
+		pdebug("incorrect username/password", DEBUG);
+
+		interval = strstr(sc_srv_res, "INTERVAL");
+		if(!interval)
+		{
+			pdebug("missing INTERVAL", DEBUG);
+		}
+		else
+		{
+			*(interval - 1) = 0;
+			sc_submit_interval = strtol(interval + 8, NULL, 10);
+		}
+
+		return -1;
+	}
+
+	pdebug(fmt_vastr("unknown server-reply '%s'", sc_srv_res), DEBUG);
+	return -1;
+}
+
+static void hexify(char *pass, int len)
+{
+	char *bp = sc_response_hash;
+	char hexchars[] = "0123456789abcdef";
+	int i;
+
+	memset(sc_response_hash, 0, sizeof(sc_response_hash));
+	
+	for(i = 0; i < len; i++) {
+		*(bp++) = hexchars[(pass[i] >> 4) & 0x0f];
+		*(bp++) = hexchars[pass[i] & 0x0f];
+	}
+	*bp = 0;
+
+	return;
+}
+
+static int sc_handshake(void)
+{
+	int status;
+	char buf[4096];
+	CURL *curl;
+
+	snprintf(buf, sizeof(buf), "%s/?hs=true&p=%s&c=%s&v=%s&u=%s",
+			SCROBBLER_HS_URL, SCROBBLER_VERSION,
+			SCROBBLER_CLI_ID, "0.3.8.1", sc_username);
+
+	curl = curl_easy_init();
+	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+	curl_easy_setopt(curl, CURLOPT_URL, buf);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, 
+			sc_store_res);
+	memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf));
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf);
+	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+	status = curl_easy_perform(curl);
+	curl_easy_cleanup(curl);
+
+	sc_hs_timeout = time(NULL) + SCROBBLER_HS_WAIT;
+
+	if (status) {
+		pdebug(sc_curl_errbuf, DEBUG);
+		sc_hs_errors++;
+		sc_free_res();
+		return -1;
+	}
+
+	if (sc_parse_hs_res()) {
+		sc_hs_errors++;
+		sc_free_res();
+		return -1;
+	}
+
+	if (sc_challenge_hash != NULL) {
+		md5_state_t md5state;
+		unsigned char md5pword[16];
+		
+		md5_init(&md5state);
+		/*pdebug(fmt_vastr("Pass Hash: %s", sc_password), DEBUG);*/
+		md5_append(&md5state, (unsigned const char *)sc_password,
+				strlen(sc_password));
+		/*pdebug(fmt_vastr("Challenge Hash: %s", sc_challenge_hash), DEBUG);*/
+		md5_append(&md5state, (unsigned const char *)sc_challenge_hash,
+				strlen(sc_challenge_hash));
+		md5_finish(&md5state, md5pword);
+		hexify(md5pword, sizeof(md5pword));
+		/*pdebug(fmt_vastr("Response Hash: %s", sc_response_hash), DEBUG);*/
+	}
+
+	sc_hs_errors = 0;
+	sc_hs_status = 1;
+
+	sc_free_res();
+
+	pdebug(fmt_vastr("submiturl: %s - interval: %d", 
+				sc_submit_url, sc_submit_interval), DEBUG);
+
+	return 0;
+}
+
+static int sc_parse_sb_res(void)
+{
+	char *ch, *ch2;
+
+	if (!sc_srv_res_size) {
+		pdebug("No response from server", DEBUG);
+		return -1;
+	}
+	*(sc_srv_res + sc_srv_res_size) = 0;
+
+	if (!strncmp(sc_srv_res, "OK", 2)) {
+		if ((ch = strstr(sc_srv_res, "INTERVAL"))) {
+			sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						sc_submit_interval), DEBUG);
+		}
+
+		pdebug(fmt_vastr("submission ok: %s", sc_srv_res), DEBUG);
+
+		return 0;
+	}
+
+	if (!strncmp(sc_srv_res, "BADAUTH", 7)) {
+		if ((ch = strstr(sc_srv_res, "INTERVAL"))) {
+			sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						sc_submit_interval), DEBUG);
+		}
+
+		pdebug("incorrect username/password", DEBUG);
+
+		sc_giveup = 0;
+
+		/*
+		 * We obviously aren't authenticated.  The server might have
+		 * lost our handshake status though, so let's try
+		 * re-handshaking...  This might not be proper.
+		 * (we don't give up)
+		 */
+		sc_hs_status = 0;
+
+		if(sc_challenge_hash != NULL)
+			free(sc_challenge_hash);
+		if(sc_submit_url != NULL)
+			free(sc_submit_url);
+
+		sc_challenge_hash = sc_submit_url = NULL;
+		sc_bad_users++;
+
+		if(sc_bad_users > 2)
+		{
+			pdebug("3 BADAUTH returns on submission. Halting "
+				"submissions until login fixed.", DEBUG)
+			sc_throw_error("Incorrect username/password.\n"
+				"Please fix in configuration.");
+		}
+
+		return -1;
+	}
+
+	if (!strncmp(sc_srv_res, "FAILED", 6))  {
+		if ((ch = strstr(sc_srv_res, "INTERVAL"))) {
+			sc_submit_interval = strtol(ch + 8, NULL, 10);
+			pdebug(fmt_vastr("got new interval: %d",
+						sc_submit_interval), DEBUG);
+		}
+
+		/* This could be important. (Such as FAILED - Get new plugin) */
+		/*sc_throw_error(fmt_vastr("%s", sc_srv_res));*/
+
+		pdebug(sc_srv_res, DEBUG);
+
+		return -1;
+	}
+
+	if (!strncmp(sc_srv_res, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">", 50)) {
+		ch = strstr(sc_srv_res, "<TITLE>") + 7;
+		ch2 = strstr(sc_srv_res, "</TITLE>");
+		*ch2 = '\0';
+
+		pdebug(fmt_vastr("HTTP Error (%d): '%s'",
+					atoi(ch), ch + 4), DEBUG);
+		*ch2 = '<';
+
+		return -1;
+	}
+
+	pdebug(fmt_vastr("unknown server-reply %s", sc_srv_res), DEBUG);
+
+	return -1;
+}
+
+static gchar *sc_itemtag(char c, int n, char *str)
+{
+    static char buf[256]; 
+    snprintf(buf, 256, "&%c[%d]=%s", c, n, str);
+    return buf;
+}
+
+#define cfa(f, l, n, v) \
+curl_formadd(f, l, CURLFORM_COPYNAME, n, \
+		CURLFORM_PTRCONTENTS, v, CURLFORM_END)
+
+static int sc_generateentry(GString *submission)
+{
+	int i;
+	item_t *item;
+
+	i = 0;
+#ifdef ALLOW_MULTIPLE
+	q_peekall(1);
+	while ((item = q_peekall(0)) && i < 10) {
+#else
+		item = q_peek();
+#endif
+		if (!item)
+			return i;
+
+                g_string_append(submission,sc_itemtag('a',i,I_ARTIST(item)));
+                g_string_append(submission,sc_itemtag('t',i,I_TITLE(item)));
+                g_string_append(submission,sc_itemtag('l',i,I_LEN(item)));
+                g_string_append(submission,sc_itemtag('i',i,I_TIME(item)));
+                g_string_append(submission,sc_itemtag('m',i,I_MB(item)));
+                g_string_append(submission,sc_itemtag('b',i,I_ALBUM(item)));
+
+		pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%s i[%d]=%s m[%d]=%s b[%d]=%s",
+				i, I_ARTIST(item),
+				i, I_TITLE(item),
+				i, I_LEN(item),
+				i, I_TIME(item),
+				i, I_MB(item),
+				i, I_ALBUM(item)), DEBUG);
+#ifdef ALLOW_MULTIPLE
+		i++;
+	}
+#endif
+
+	return i;
+}
+
+static int sc_submitentry(gchar *entry)
+{
+	CURL *curl;
+	/* struct HttpPost *post = NULL , *last = NULL; */
+	int status;
+        GString *submission;
+
+	curl = curl_easy_init();
+	curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
+	curl_easy_setopt(curl, CURLOPT_URL, sc_submit_url);
+	curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+			sc_store_res);
+	curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
+	curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+	/*cfa(&post, &last, "debug", "failed");*/
+
+	/*pdebug(fmt_vastr("Username: %s", sc_username), DEBUG);*/
+        submission = g_string_new("u=");
+        g_string_append(submission,(gchar *)sc_username);
+
+	/*pdebug(fmt_vastr("Response Hash: %s", sc_response_hash), DEBUG);*/
+        g_string_append(submission,"&s=");
+        g_string_append(submission,(gchar *)sc_response_hash);
+
+	g_string_append(submission, entry);
+
+	curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (char *)submission->str);
+	memset(sc_curl_errbuf, 0, sizeof(sc_curl_errbuf));
+	curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, sc_curl_errbuf);
+
+	/*
+	curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1);
+	curl_easy_setopt(curl, CURLOPT_TIMEOUT, SCROBBLER_SB_WAIT);
+	*/
+
+	status = curl_easy_perform(curl);
+
+	curl_easy_cleanup(curl);
+
+        g_string_free(submission,TRUE);
+
+	if (status) {
+		pdebug(sc_curl_errbuf, DEBUG);
+		sc_sb_errors++;
+		sc_free_res();
+		return -1;
+	}
+
+	if (sc_parse_sb_res()) {
+		sc_sb_errors++;
+		sc_free_res();
+		pdebug(fmt_vastr("Retrying in %d secs, %d elements in queue",
+					sc_submit_interval, q_len()), DEBUG);
+		return -1;
+	}
+	sc_free_res();
+	return 0;
+}
+
+static void sc_handlequeue(GMutex *mutex)
+{
+	GString *submitentry;
+	int nsubmit;
+	int wait;
+
+	if(sc_submit_timeout < time(NULL) && sc_bad_users < 3)
+	{
+		submitentry = g_string_new("");
+
+		g_mutex_lock(mutex);
+
+		nsubmit = sc_generateentry(submitentry);
+
+		g_mutex_unlock(mutex);
+
+		if (nsubmit > 0)
+		{
+			pdebug(fmt_vastr("Number submitting: %d", nsubmit), DEBUG);
+			pdebug(fmt_vastr("Submission: %s", submitentry->str), DEBUG);
+
+			if(!sc_submitentry(submitentry->str))
+			{
+				g_mutex_lock(mutex);
+
+#ifdef ALLOW_MULTIPLE
+				q_free();
+#else
+				q_get();
+#endif
+				/*
+				 * This should make sure that the queue doesn't
+				 * get submitted multiple times on a nasty
+				 * segfault...
+				 */
+				dump_queue();
+
+				g_mutex_unlock(mutex);
+
+				sc_sb_errors = 0;
+			}
+			if(sc_sb_errors)
+			{
+				if(sc_sb_errors < 5)
+					/* Retry after 1 min */
+					wait = 60;
+				else
+					wait = /* sc_submit_interval + */
+						( ((sc_sb_errors - 5) < 7) ?
+						(60 << (sc_sb_errors-5)) :
+						7200 );
+				
+				sc_submit_timeout = time(NULL) + wait;
+
+				pdebug(fmt_vastr("Error while submitting. "
+					"Retrying after %d seconds.", wait),
+					DEBUG);
+			}
+		}
+
+		g_string_free(submitentry, TRUE);
+	}
+}
+
+static void read_cache(void)
+{
+	FILE *fd;
+	char *home, buf[PATH_MAX], *cache = NULL, *ptr1, *ptr2;
+	int cachesize, written, i = 0;
+	item_t *item;
+
+	cachesize = written = 0;
+
+	if(!(home = getenv("HOME")))
+		return;
+
+	snprintf(buf, sizeof(buf), "%s/.audacious/scrobblerqueue.txt", home);
+
+	if (!(fd = fopen(buf, "r")))
+		return;
+	pdebug(fmt_vastr("Opening %s", buf), DEBUG);
+	while(!feof(fd))
+	{
+		cachesize += CACHE_SIZE;
+		cache = realloc(cache, cachesize + 1);
+		written += fread(cache + written, 1, CACHE_SIZE, fd);
+		cache[written] = '\0';
+	}
+	fclose(fd);
+	ptr1 = cache;
+	while(ptr1 < cache + written - 1)
+	{
+		char *artist, *title, *len, *time, *album, *mb;
+
+		pdebug("Pushed:", DEBUG);
+		ptr2 = strchr(ptr1, ' ');
+		artist = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(artist, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		title = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(title, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		len = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(len, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		time = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(time, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, ' ');
+		album = calloc(1, ptr2 - ptr1 + 1);
+		strncpy(album, ptr1, ptr2 - ptr1);
+		ptr1 = ptr2 + 1;
+		ptr2 = strchr(ptr1, '\n');
+		if(ptr2 != NULL)
+			*ptr2 = '\0';
+		mb = calloc(1, strlen(ptr1) + 1);
+		strncpy(mb, ptr1, strlen(ptr1));
+		if(ptr2 != NULL)
+			*ptr2 = '\n';
+		/* Why is our save printing out CR/LF? */
+		ptr1 = ptr2 + 1;
+
+		item = q_put2(artist, title, len, time, album, mb);
+		pdebug(fmt_vastr("a[%d]=%s t[%d]=%s l[%d]=%s i[%d]=%s m[%d]=%s b[%d]=%s",
+				i, I_ARTIST(item),
+				i, I_TITLE(item),
+				i, I_LEN(item),
+				i, I_TIME(item),
+				i, I_MB(item),
+				i, I_ALBUM(item)), DEBUG);
+		free(artist);
+		free(title);
+		free(len);
+		free(time);
+		free(album);
+		free(mb);
+
+		i++;
+	}
+	pdebug("Done loading cache.", DEBUG);
+}
+
+static void dump_queue(void)
+{
+	FILE *fd;
+	item_t *item;
+	char *home, buf[PATH_MAX];
+
+	/*pdebug("Entering dump_queue();", DEBUG);*/
+
+	if (!(home = getenv("HOME")))
+	{
+		pdebug("No HOME directory found. Cannot dump queue.", DEBUG);
+		return;
+	}
+
+	snprintf(buf, sizeof(buf), "%s/.audacious/scrobblerqueue.txt", home);
+
+	if (!(fd = fopen(buf, "w")))
+	{
+		pdebug(fmt_vastr("Failure opening %s", buf), DEBUG);
+		return;
+	}
+
+	pdebug(fmt_vastr("Opening %s", buf), DEBUG);
+
+	q_peekall(1);
+
+	while ((item = q_peekall(0))) {
+		fprintf(fd, "%s %s %s %s %s %s\n",
+					I_ARTIST(item),
+					I_TITLE(item),
+					I_LEN(item),
+					I_TIME(item),
+					I_ALBUM(item),
+					I_MB(item));
+	}
+
+	fclose(fd);
+}
+
+/* This was made public */
+
+void sc_cleaner(void)
+{
+	if(sc_submit_url != NULL)
+		free(sc_submit_url);
+	if(sc_username != NULL)
+		free(sc_username);
+	if(sc_password != NULL)
+		free(sc_password);
+	if(sc_challenge_hash != NULL)
+		free(sc_challenge_hash);
+	if(sc_srv_res != NULL)
+		free(sc_srv_res);
+	if(sc_major_error != NULL)
+		free(sc_major_error);
+	dump_queue();
+	q_free();
+	pdebug("scrobbler shutting down", DEBUG);
+}
+
+static void sc_checkhandshake(void)
+{
+	int wait;
+
+	if (sc_hs_status)
+		return;
+	if (sc_hs_timeout < time(NULL))
+	{
+		sc_handshake();
+
+		if(sc_hs_errors)
+		{
+			if(sc_hs_errors < 5)
+				/* Retry after 60 seconds */
+				wait = 60;
+			else
+				wait = /* sc_submit_interval + */
+					( ((sc_hs_errors - 5) < 7) ?
+					(60 << (sc_hs_errors-5)) :
+					7200 );
+			sc_hs_timeout = time(NULL) + wait;
+			pdebug(fmt_vastr("Error while handshaking. Retrying "
+				"after %d seconds.", wait), DEBUG);
+		}
+	}
+}
+
+/**** Public *****/
+
+/* Called at session startup*/
+
+void sc_init(char *uname, char *pwd)
+{
+	sc_hs_status = sc_hs_timeout = sc_hs_errors = sc_submit_timeout =
+		sc_srv_res_size = sc_giveup = sc_major_error_present =
+		sc_bad_users = sc_sb_errors = 0;
+	sc_submit_interval = 100;
+
+	sc_submit_url = sc_username = sc_password = sc_srv_res =
+		sc_challenge_hash = sc_major_error = NULL;
+	sc_username = strdup(uname);
+	sc_password = strdup(pwd);
+	read_cache();
+	pdebug("scrobbler starting up", DEBUG);
+}
+
+void sc_addentry(GMutex *mutex, metatag_t *meta, int len)
+{
+	g_mutex_lock(mutex);
+	q_put(meta, len);
+	/*
+	 * This will help make sure the queue will be saved on a nasty
+	 * segfault...
+	 */
+	dump_queue();
+	g_mutex_unlock(mutex);
+}
+
+/* Call periodically from the plugin */
+int sc_idle(GMutex *mutex)
+{
+	sc_checkhandshake();
+	if (sc_hs_status)
+		sc_handlequeue(mutex);
+
+	return sc_giveup;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/scrobbler.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,11 @@
+#ifndef NET_H
+#define NET_H 1
+
+int sc_idle(GMutex *);
+void sc_init(char *, char *);
+void sc_addentry(GMutex *, metatag_t *, int);
+void sc_cleaner(void);
+int sc_catch_error(void);
+char *sc_fetch_error(void);
+void sc_clear_error(void);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/Makefile.in	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,13 @@
+include ../../../../mk/rules.mk
+include ../../../../mk/objective.mk
+
+OBJECTIVE_LIBS_NOINST = libmetatag.a
+
+LIBADD = $(MUSICBRAINZ_LIBS)
+
+SOURCES = ape.c cdaudio.c id3genres.c id3v1.c id3v2.c itunes.c tags.c \
+	unicode.c vorbis.c wma.c
+
+CFLAGS += -fPIC -DPIC $(GTK_CFLAGS) $(MUSICBRAINZ_CFLAGS) -I../../../../intl -I../../../..
+
+OBJECTS = ${SOURCES:.c=.o}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/ape.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,187 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/ape.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+
+static ape_t *readItems(VFSFile *fp, int version)
+{
+	ape_t *ape = calloc(sizeof(ape_t), 1);
+	unsigned char *tag_buffer = NULL, *bp, cToInt[4];
+	int size, start, i;
+	
+	ape->version = version;
+
+	/*
+	 * Now for the actual reading.
+	 * APE2 and APE1 tags are identical for all the structure we care about.
+	 */
+
+	vfs_fread(cToInt, 1, 4, fp);
+	size = le2int(cToInt);
+	vfs_fread(cToInt, 1, 4, fp);
+	ape->numitems = le2int(cToInt);
+	/* pdebug(fmt_vastr("tag size: %d", size));
+	pdebug(fmt_vastr("items: %d", ape->numitems)); */
+	vfs_fread(cToInt, 1, 4, fp);
+	/* Is it a footer?  Header? */
+	if((cToInt[3] & 0x20) == 0x0 || version == 1000)
+		start = 8 - size;
+	else
+		start = 8;
+	vfs_fseek(fp, start, SEEK_CUR);
+
+	tag_buffer = realloc(tag_buffer, size);
+	vfs_fread(tag_buffer, 1, size, fp);
+	bp = tag_buffer;
+	
+	ape->items = realloc(ape->items,
+			(ape->numitems) * sizeof(apefielddata_t *));
+	
+	for(i = 0; i < ape->numitems && strncmp(bp, "APETAGEX", 8) != 0; i++)
+	{
+		apefielddata_t *field = calloc(sizeof(apefielddata_t), 1);
+		
+		memcpy(cToInt, bp, 4);		
+		bp += 8;		
+		field->len = le2int(cToInt);
+		field->name = malloc(strlen(bp) + 1);
+		strcpy(field->name, bp);
+		bp += strlen(bp) + 1;
+		field->data = malloc(field->len + 1);
+		memcpy(field->data, bp, field->len);
+		*(field->data + field->len) = '\0';
+		bp += field->len;
+		
+		ape->items[i] = field;		
+	}
+	if(i < ape->numitems && strncmp(bp, "APETAGEX", 8) == 0)
+	{
+		ape->numitems = i;
+		ape->items = realloc(ape->items,
+			(ape->numitems) * sizeof(apefielddata_t *));
+	}
+		
+
+	free(tag_buffer);
+	
+	return ape;
+}
+
+int findAPE(VFSFile *fp)
+{
+	unsigned char *tag_buffer, *bp, cToInt[4];
+	int pb = 0, status = 0, pos = 0, i, ape_version;
+	
+	/* Find the tag header or footer and point the file pointer there. */
+	tag_buffer = malloc(BUFFER_SIZE);
+	pb += vfs_fread(tag_buffer, 1, BUFFER_SIZE, fp);
+	bp = tag_buffer;
+	while(status == 0)
+	{
+		for(i = 0; i < BUFFER_SIZE - 8 && status == 0; i++)
+		{
+			bp++;
+			if(!strncmp(bp, "APETAGEX", 8))
+				status = 1;
+		}
+		if(status == 1 || feof(fp))
+			break;
+		memmove(tag_buffer, tag_buffer + BUFFER_SIZE - 7, 7);
+		pos += BUFFER_SIZE - 7;
+		pb += vfs_fread(tag_buffer + 7, 1, BUFFER_SIZE - 7, fp);
+		bp = tag_buffer;
+	}
+	if(status == 1)
+	{
+		vfs_fseek(fp, pos + (bp - tag_buffer) + 8, SEEK_SET);
+
+		free(tag_buffer);
+		
+		/* Get the tag version. */
+		vfs_fread(cToInt, 1, 4, fp);
+		ape_version = le2int(cToInt);
+		pdebug(ape_version == 1000 ? "Found APE1 tag..." :
+			ape_version == 2000 ? "Found APE2 tag..." :
+			"Found unknown APE tag...", META_DEBUG);
+		
+		return ape_version;
+	}
+	else
+	{
+		free(tag_buffer);
+		return 0;
+	}
+}
+
+ape_t *readAPE(char *filename)
+{
+	VFSFile *fp;
+	ape_t *ape;
+	int status;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	status = findAPE(fp);
+	if(status == 0)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	ape = readItems(fp, status);
+	
+	vfs_fclose(fp);
+	
+	return ape;
+}
+
+void freeAPE(ape_t *ape)
+{
+	int i;
+	
+	for(i = 0; i < ape->numitems; i++)
+	{
+		apefielddata_t *field;
+		
+		field = ape->items[i];
+		free(field->name);
+		free(field->data);
+		free(field);
+	}
+	free(ape->items);
+	free(ape);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/cdaudio.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,153 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <musicbrainz/mb_c.h>
+#include "include/cdaudio.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+
+#ifdef MAKE_BMP
+#include <libaudacious/vfs.h>
+#define vfs_fopen vfs_fopen
+#define vfs_fclose vfs_fclose
+#define vfs_fread vfs_fread
+#define vfs_fseek vfs_fseek
+#define ftell vfs_ftell
+#define VFSFile VFSFile
+#endif
+
+/*
+ * Determining if a CD is being played is left up to the reader.
+ *
+ 
+int cdaudio_find(char *filename)
+{
+	We've got to find a way to ensure this works on all platforms. 
+	
+	return !(fmt_strcasecmp(strrchr(filename, '.') + 1, "cda"));
+}
+*/
+
+cdaudio_t *readCDAudio(char *filename, char track)
+{
+	int retVal;
+	musicbrainz_t mb;
+	char *tmp;
+	cdaudio_t *musicbrainz = calloc(sizeof(cdaudio_t), 1);
+	
+	memset(musicbrainz, 0, sizeof(cdaudio_t));
+	
+	tmp = malloc(BUFFER_SIZE / 4 + 1);
+	mb = mb_New();
+	mb_SetDevice(mb, filename);
+	pdebug("Submitting query to MusicBrainz...", META_DEBUG);
+	retVal = mb_Query(mb, MBQ_GetCDInfo);
+	if(retVal == 0)
+	{
+#ifdef META_DEBUG
+		char error[129] = "";
+		pdebug("ERROR: Query failed.", META_DEBUG);
+		mb_GetQueryError(mb, error, 128);
+		pdebug(fmt_vastr("REASON: %s", error), META_DEBUG);
+#endif
+		mb_Delete(mb);
+		free(tmp);
+		free(musicbrainz);
+		return NULL;
+	}
+	pdebug("Selecting result...", META_DEBUG);
+	retVal = mb_Select1(mb, MBS_SelectAlbum, 1);
+	if(retVal == 0)
+	{
+		pdebug("ERROR: Album select failed.", META_DEBUG);
+		mb_Delete(mb);
+		free(tmp);
+		free(musicbrainz);
+		return NULL;
+	}
+	pdebug("Extracting MusicBrainz data from result...", META_DEBUG);
+	memset(tmp, '\0', BUFFER_SIZE / 4 + 1);
+	retVal = mb_GetResultData(mb, MBE_AlbumGetAlbumName, tmp, BUFFER_SIZE / 4);
+	if(retVal == 0)
+	{
+		pdebug("ERROR: Album title not found.", META_DEBUG);
+		musicbrainz->album = calloc(1, 1);
+	}
+	else
+	{
+		musicbrainz->album = malloc(strlen(tmp) + 1);
+		strcpy(musicbrainz->album, tmp);
+	}
+	memset(tmp, '\0', BUFFER_SIZE / 4 + 1);
+	retVal = mb_GetResultData1(mb, MBE_AlbumGetArtistName, tmp, BUFFER_SIZE / 4, track);
+	if(retVal == 0)
+	{
+		pdebug("ERROR: Artist name not found.", META_DEBUG);
+		musicbrainz->artist = calloc(1, 1);
+	}
+	else
+	{
+		musicbrainz->artist = malloc(strlen(tmp) + 1);
+		strcpy(musicbrainz->artist, tmp);
+	}
+	memset(tmp, '\0', BUFFER_SIZE / 4 + 1);
+	retVal = mb_GetResultData1(mb, MBE_AlbumGetTrackName, tmp, BUFFER_SIZE / 4, track);
+	if(retVal == 0)
+	{
+		pdebug("ERROR: Track title not found.", META_DEBUG);
+		musicbrainz->title = calloc(1, 1);
+	}
+	else
+	{
+		musicbrainz->title = malloc(strlen(tmp) + 1);
+		strcpy(musicbrainz->title, tmp);
+	}
+	memset(tmp, '\0', BUFFER_SIZE / 4 + 1);
+	retVal = mb_GetResultData1(mb, MBE_AlbumGetTrackId, tmp, BUFFER_SIZE / 4, track);
+	if(retVal == 0)
+	{
+		pdebug("ERROR: MBID not found.", META_DEBUG);
+		musicbrainz->mbid = calloc(1, 1);
+	}
+	else
+	{
+		musicbrainz->mbid = malloc(64);
+		mb_GetIDFromURL(mb, tmp, musicbrainz->mbid, 64);
+	}
+	mb_Delete(mb);
+	free(tmp);
+	
+	return musicbrainz;
+}
+
+void freeCDAudio(cdaudio_t *musicbrainz)
+{
+	free(musicbrainz->mbid);
+	free(musicbrainz->title);
+	free(musicbrainz->artist);
+	free(musicbrainz->album);
+	free(musicbrainz);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/id3genres.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,79 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+ 
+const char *genre_list[148] =
+{
+	/* The following genres are defined in ID3v1 */
+	"Blues", 		"Classic Rock",		"Country",
+	"Dance",		"Disco",		"Funk",
+	"Grunge",		"Hip-Hop",		"Jazz",
+	"Metal",		"New Age",		"Oldies",
+	"Other",		"Pop",			"R&B",
+	"Rap",			"Reggae",		"Rock",
+	"Techno",		"Industrial",		"Alternative",
+	"Ska",			"Death Metal",		"Pranks",
+	"Soundtrack",		"Euro-Techno",		"Ambient",
+	"Trip-Hop",		"Vocal",		"Jazz+Funk",
+	"Fusion",		"Trance",		"Classical",
+	"Instrumental",		"Acid",			"House",
+	"Game",			"Sound Clip",		"Gospel",
+	"Noise",		"AlternRock",		"Bass",
+	"Soul",			"Punk",			"Space",
+	"Meditative",		"Instrumental Pop",	"Instrumental Rock",
+	"Ethnic",		"Gothic",		"Darkwave",
+	"Techno-Industrial",	"Electronic",		"Pop-Folk",
+	"Eurodance",		"Dream",		"Southern Rock",
+	"Comedy",		"Cult",			"Gangsta",
+	"Top 40",		"Christian Rap",	"Pop/Funk",
+	"Jungle",		"Native American",	"Cabaret",
+	"New Wave",		"Psychedelic",		"Rave",
+	"Showtunes",		"Trailer",		"Lo-Fi",
+	"Tribal",		"Acid Punk",		"Acid Jazz",
+	"Polka",		"Retro",		"Musical",
+	"Rock & Roll",		"Hard Rock",
+	/* The following genres are defined in early versions of Winamp */
+	"Folk",			"Folk-Rock",		"National Folk",
+	"Swing",		"Fast Fusion",		"Bebob",
+	"Latin",		"Revival",		"Celtic",
+	"Bluegrass",		"Avantgarde",		"Gothic Rock",
+	"Progressive Rock",	"Psychedelic Rock",	"Symphonic Rock",
+	"Slow Rock",		"Big Band",		"Chorus",
+	"Easy Listening",	"Acoustic",		"Humour",
+	"Speech",		"Chanson",		"Opera",
+	"Chamber Music",	"Sonata",		"Symphony",
+	"Booty Bass",		"Primus",		"Porn Groove",
+	"Satire",
+	/* By Winamp 1.70, the following were added. */
+	"Slow Jam",		"Club",			"Tango",
+	"Samba",		"Folklore",
+	/* By Winamp 1.90, the following were added. */
+	"Ballad",		"Power Ballad",		"Rhythmic Soul",
+	"Freestyle",		"Duet",			"Punk Rock",
+	"Drum Solo",		"A capella",		"Euro-House",
+	"Dance Hall",		"Goa",			"Drum & Bass",
+	"Club-House",		"Hardcore",		"Terror",
+	"Indie",		"BritPop",		"Negerpunk",
+	"Polsk Punk",		"Beat",			"Christian Gangsta Rap",
+	"Heavy Metal",		"Black Metal",		"Crossover",
+	"Contemporary Christian",			"Christian Rock",
+	/* The following were added in Winamp 1.91 */
+	"Merengue",		"Salsa",		"Trash Metal",
+	"Anime",		"JPop",			"SynthPop"
+};
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/id3v1.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,149 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/id3v1.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+
+static void cleanID3v1(unsigned char *data, size_t size)
+{
+	int i;
+	
+	for(i = size - 1; i > -1; i--)
+	{
+		if(data[i] == ' ') data[i] = '\0';
+		else break;
+	}
+}
+
+static void cleanComment(unsigned char *data)
+{
+	int i;
+	
+	for(i = 27; i > -1; i--)
+	{
+		if(data[i] == ' ' || data[i] == '\0') data[i] = '\0';
+		else break;
+	}
+}
+
+int findID3v1(VFSFile *fp)
+{
+	char tag_id[4] = "";
+	
+	vfs_fread(tag_id, 1, 3, fp);
+	
+	if(!strncmp(tag_id, "TAG", 3))
+		return 1;
+	else
+		return 0;
+}
+
+id3v1_t *readID3v1(char *filename)
+{
+	VFSFile *fp;
+	id3v1_t *id3v1 = NULL;
+	int status;
+	
+	fp = vfs_fopen(filename, "rb");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+
+	pdebug("Searching for tag...", META_DEBUG);
+	vfs_fseek(fp, -128, SEEK_END);
+	status = findID3v1(fp);
+	if(status)
+	{
+		unsigned char *data;
+		
+		id3v1 = calloc(sizeof(id3v1_t), 1);
+		data = malloc(31);
+		*(data + 30) = '\0';
+		vfs_fread(data, 1, 30, fp);
+		cleanID3v1(data, 30);
+		// id3v1->title = realloc(id3v1->title, 31);
+		if(strcmp(data, ""))
+			iso88591_to_utf8(data, strlen(data), &id3v1->title);
+		else
+			id3v1->title = NULL;
+		vfs_fread(data, 1, 30, fp);
+		cleanID3v1(data, 30);
+		if(strcmp(data, ""))
+			iso88591_to_utf8(data, strlen(data), &id3v1->artist);
+		else
+			id3v1->artist = NULL;
+		vfs_fread(data, 1, 30, fp);
+		cleanID3v1(data, 30);
+		if(strcmp(data, ""))
+			iso88591_to_utf8(data, strlen(data), &id3v1->album);
+		else
+			id3v1->album = NULL;
+		data = realloc(data, 5);
+		*(data + 4) = '\0';
+		vfs_fread(data, 1, 4, fp);
+		cleanID3v1(data, 4);
+		if(strcmp(data, ""))
+			iso88591_to_utf8(data, strlen(data), &id3v1->year);
+		else
+			id3v1->year = NULL;
+		data = realloc(data, 31);
+		*(data + 30) = '\0';
+		vfs_fread(data, 1, 30, fp);
+		cleanComment(data);
+		id3v1->comment = realloc(id3v1->comment, 31);
+		memset(id3v1->comment, 0, 31);
+		memcpy(id3v1->comment, data, 30);
+		if(data[28] == '\0' && data[29] != '\0')
+			id3v1->track = data[29];
+		else
+			id3v1->track = 255;
+		free(data);
+		vfs_fread(&id3v1->genre, 1, 1, fp);
+	}
+	
+	vfs_fclose(fp);
+	
+	return id3v1;
+}
+
+void freeID3v1(id3v1_t *id3v1)
+{
+	if(id3v1->title != NULL)
+		free(id3v1->title);
+	if(id3v1->artist != NULL)
+		free(id3v1->artist);
+	if(id3v1->album != NULL)
+		free(id3v1->album);
+	if(id3v1->year != NULL)
+		free(id3v1->year);
+	free(id3v1->comment);
+	free(id3v1);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/id3v2.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,708 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/id3v2.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+
+id3_lookup_t id3v22_lookup[] = 
+{
+	{"BUF", ID3V22_BUF}, {"CNT", ID3V22_CNT}, {"COM", ID3V22_COM}, 
+	{"CRA", ID3V22_CRA}, {"CRM", ID3V22_CRM}, {"ETC", ID3V22_ETC}, 
+	{"EQU", ID3V22_EQU}, {"GEO", ID3V22_GEO}, {"IPL", ID3V22_IPL}, 
+	{"LNK", ID3V22_LNK}, {"MCI", ID3V22_MCI}, {"MLL", ID3V22_MLL}, 
+	{"PIC", ID3V22_PIC}, {"POP", ID3V22_POP}, {"REV", ID3V22_REV}, 
+	{"RVA", ID3V22_RVA}, {"SLT", ID3V22_SLT}, {"STC", ID3V22_STC}, 
+	{"TAL", ID3V22_TAL}, {"TBP", ID3V22_TBP}, {"TCM", ID3V22_TCM}, 
+	{"TCO", ID3V22_TCO}, {"TCR", ID3V22_TCR}, {"TDA", ID3V22_TDA}, 
+	{"TDY", ID3V22_TDY}, {"TEN", ID3V22_TEN}, {"TFT", ID3V22_TFT}, 
+	{"TIM", ID3V22_TIM}, {"TKE", ID3V22_TKE}, {"TLA", ID3V22_TLA}, 
+	{"TLE", ID3V22_TLE}, {"TMT", ID3V22_TMT}, {"TOA", ID3V22_TOA}, 
+	{"TOF", ID3V22_TOF}, {"TOL", ID3V22_TOL}, {"TOR", ID3V22_TOR}, 
+	{"TOT", ID3V22_TOT}, {"TP1", ID3V22_TP1}, {"TP2", ID3V22_TP2}, 
+	{"TP3", ID3V22_TP3}, {"TP4", ID3V22_TP4}, {"TPA", ID3V22_TPA}, 
+	{"TPB", ID3V22_TPB}, {"TRC", ID3V22_TRC}, {"TRD", ID3V22_TRD}, 
+	{"TRK", ID3V22_TRK}, {"TSI", ID3V22_TSI}, {"TSS", ID3V22_TSS}, 
+	{"TT1", ID3V22_TT1}, {"TT2", ID3V22_TT2}, {"TT3", ID3V22_TT3}, 
+	{"TXT", ID3V22_TXT}, {"TXX", ID3V22_TXX}, {"TYE", ID3V22_TYE}, 
+	{"UFI", ID3V22_UFI}, {"ULT", ID3V22_ULT}, {"WAF", ID3V22_WAF}, 
+	{"WAR", ID3V22_WAR}, {"WAS", ID3V22_WAS}, {"WCM", ID3V22_WCM}, 
+	{"WCP", ID3V22_WCP}, {"WPB", ID3V22_WPB}, {"WXX", ID3V22_WXX},
+	{NULL, -1}
+};
+
+id3_lookup_t id3v24_lookup[] =
+{
+	{"AENC", ID3V24_AENC}, {"APIC", ID3V24_APIC}, {"ASPI", ID3V24_ASPI}, 
+	{"COMM", ID3V24_COMM}, {"COMR", ID3V24_COMR}, {"ENCR", ID3V24_ENCR}, 
+	{"EQU2", ID3V24_EQU2}, {"ETCO", ID3V24_ETCO}, {"GEOB", ID3V24_GEOB}, 
+	{"GRID", ID3V24_GRID}, {"LINK", ID3V24_LINK}, {"MCDI", ID3V24_MCDI}, 
+	{"MLLT", ID3V24_MLLT}, {"OWNE", ID3V24_OWNE}, {"PRIV", ID3V24_PRIV}, 
+	{"PCNT", ID3V24_PCNT}, {"POPM", ID3V24_POPM}, {"POSS", ID3V24_POSS}, 
+	{"RBUF", ID3V24_RBUF}, {"RVA2", ID3V24_RVA2}, {"RVRB", ID3V24_RVRB}, 
+	{"SEEK", ID3V24_SEEK}, {"SIGN", ID3V24_SIGN}, {"SYLT", ID3V24_SYLT}, 
+	{"SYTC", ID3V24_SYTC}, {"TALB", ID3V24_TALB}, {"TBPM", ID3V24_TBPM}, 
+	{"TCOM", ID3V24_TCOM}, {"TCON", ID3V24_TCON}, {"TCOP", ID3V24_TCOP}, 
+	{"TDEN", ID3V24_TDEN}, {"TDLY", ID3V24_TDLY}, {"TDOR", ID3V24_TDOR}, 
+	{"TDRC", ID3V24_TDRC}, {"TDRL", ID3V24_TDRL}, {"TDTG", ID3V24_TDTG}, 
+	{"TENC", ID3V24_TENC}, {"TEXT", ID3V24_TEXT}, {"TFLT", ID3V24_TFLT}, 
+	{"TIPL", ID3V24_TIPL}, {"TIT1", ID3V24_TIT1}, {"TIT2", ID3V24_TIT2}, 
+	{"TIT3", ID3V24_TIT3}, {"TKEY", ID3V24_TKEY}, {"TLAN", ID3V24_TLAN}, 
+	{"TLEN", ID3V24_TLEN}, {"TMCL", ID3V24_TMCL}, {"TMED", ID3V24_TMED}, 
+	{"TMOO", ID3V24_TMOO}, {"TOAL", ID3V24_TOAL}, {"TOFN", ID3V24_TOFN}, 
+	{"TOLY", ID3V24_TOLY}, {"TOPE", ID3V24_TOPE}, {"TOWN", ID3V24_TOWN}, 
+	{"TPE1", ID3V24_TPE1}, {"TPE2", ID3V24_TPE2}, {"TPE3", ID3V24_TPE3}, 
+	{"TPE4", ID3V24_TPE4}, {"TPOS", ID3V24_TPOS}, {"TPRO", ID3V24_TPRO}, 
+	{"TPUB", ID3V24_TPUB}, {"TRCK", ID3V24_TRCK}, {"TRSN", ID3V24_TRSN}, 
+	{"TRSO", ID3V24_TRSO}, {"TSOA", ID3V24_TSOA}, {"TSOP", ID3V24_TSOP}, 
+	{"TSOT", ID3V24_TSOT}, {"TSRC", ID3V24_TSRC}, {"TSSE", ID3V24_TSSE}, 
+	{"TSST", ID3V24_TSST}, {"TXXX", ID3V24_TXXX}, {"UFID", ID3V24_UFID}, 
+	{"USER", ID3V24_USER}, {"USLT", ID3V24_USLT}, {"WCOM", ID3V24_WCOM}, 
+	{"WCOP", ID3V24_WCOP}, {"WOAF", ID3V24_WOAF}, {"WOAR", ID3V24_WOAR}, 
+	{"WOAS", ID3V24_WOAS}, {"WORS", ID3V24_WORS}, {"WPAY", ID3V24_WPAY}, 
+	{"WPUB", ID3V24_WPUB}, {"WXXX", ID3V24_WXXX}, {NULL, -1}
+};
+
+id3_lookup_t id3v23_lookup[] = 
+{
+	{"AENC", ID3V23_AENC}, {"APIC", ID3V23_APIC}, {"COMM", ID3V23_COMM}, 
+	{"COMR", ID3V23_COMR}, {"ENCR", ID3V23_ENCR}, {"EQUA", ID3V23_EQUA}, 
+	{"ETCO", ID3V23_ETCO}, {"GEOB", ID3V23_GEOB}, {"GRID", ID3V23_GRID}, 
+	{"IPLS", ID3V23_IPLS}, {"LINK", ID3V23_LINK}, {"MCDI", ID3V23_MCDI}, 
+	{"MLLT", ID3V23_MLLT}, {"OWNE", ID3V23_OWNE}, {"PRIV", ID3V23_PRIV}, 
+	{"PCNT", ID3V23_PCNT}, {"POPM", ID3V23_POPM}, {"POSS", ID3V23_POSS}, 
+	{"RBUF", ID3V23_RBUF}, {"RVAD", ID3V23_RVAD}, {"RVRB", ID3V23_RVRB}, 
+	{"SYLT", ID3V23_SYLT}, {"SYTC", ID3V23_SYTC}, {"TALB", ID3V23_TALB}, 
+	{"TBPM", ID3V23_TBPM}, {"TCOM", ID3V23_TCOM}, {"TCON", ID3V23_TCON}, 
+	{"TCOP", ID3V23_TCOP}, {"TDAT", ID3V23_TDAT}, {"TDLY", ID3V23_TDLY}, 
+	{"TENC", ID3V23_TENC}, {"TEXT", ID3V23_TEXT}, {"TFLT", ID3V23_TFLT}, 
+	{"TIME", ID3V23_TIME}, {"TIT1", ID3V23_TIT1}, {"TIT2", ID3V23_TIT2}, 
+	{"TIT3", ID3V23_TIT3}, {"TKEY", ID3V23_TKEY}, {"TLAN", ID3V23_TLAN}, 
+	{"TLEN", ID3V23_TLEN}, {"TMED", ID3V23_TMED}, {"TOAL", ID3V23_TOAL}, 
+	{"TOFN", ID3V23_TOFN}, {"TOLY", ID3V23_TOLY}, {"TOPE", ID3V23_TOPE}, 
+	{"TORY", ID3V23_TORY}, {"TOWN", ID3V23_TOWN}, {"TPE1", ID3V23_TPE1}, 
+	{"TPE2", ID3V23_TPE2}, {"TPE3", ID3V23_TPE3}, {"TPE4", ID3V23_TPE4}, 
+	{"TPOS", ID3V23_TPOS}, {"TPUB", ID3V23_TPUB}, {"TRCK", ID3V23_TRCK}, 
+	{"TRDA", ID3V23_TRDA}, {"TRSN", ID3V23_TRSN}, {"TRSO", ID3V23_TRSO}, 
+	{"TSIZ", ID3V23_TSIZ}, {"TSRC", ID3V23_TSRC}, {"TSSE", ID3V23_TSSE}, 
+	{"TYER", ID3V23_TYER}, {"TXXX", ID3V23_TXXX}, {"UFID", ID3V23_UFID}, 
+	{"USER", ID3V23_USER}, {"USLT", ID3V23_USLT}, {"WCOM", ID3V23_WCOM}, 
+	{"WCOP", ID3V23_WCOP}, {"WOAF", ID3V23_WOAF}, {"WOAR", ID3V23_WOAR}, 
+	{"WOAS", ID3V23_WOAS}, {"WORS", ID3V23_WORS}, {"WPAY", ID3V23_WPAY}, 
+	{"WPUB", ID3V23_WPUB}, {"WXXX", ID3V23_WXXX}, {NULL, -1}
+};
+
+
+typedef struct
+{
+	unsigned char *check;
+	int count;
+} resync_t;
+
+typedef struct
+{
+	int unsync, extended, size;
+	char version[2];
+} id3header_t;
+
+static resync_t *checkunsync(char *syncCheck, int size)
+{
+	int i, j;
+	resync_t *sync;
+	
+	sync = malloc(sizeof(resync_t));
+
+	sync->check = syncCheck;
+	sync->count = 0;
+	
+	if(size == 0)
+		size = strlen(sync->check);
+
+	for(i = 0; i < size; i++)
+	{
+		if(sync->check[i] == 0xFF && sync->check[i + 1] == 0x00)
+		{
+			for(j = i + 1; j < size - 1; j++)
+				syncCheck[j] = syncCheck[j + 1];
+			sync->check[j] = '\0';
+			sync->count++;
+		}
+	}
+
+	return sync;
+}
+
+static void unsync(char *data, char *bp)
+{
+	resync_t *unsynced;
+	char *syncFix = NULL;
+	int i;
+	
+	unsynced = checkunsync(data, 0);
+	while(unsynced->count > 0)
+	{
+		if(syncFix != NULL)
+			syncFix = realloc(syncFix, unsynced->count);
+		else
+			syncFix = malloc(unsynced->count);
+		memcpy(syncFix, bp, unsynced->count);
+		bp += unsynced->count;
+		for(i = 0; i < unsynced->count; i++)
+			data[4 - unsynced->count + i] = syncFix[i];
+		free(unsynced);
+		unsynced = checkunsync(data, 0);
+	}
+	free(unsynced);
+	free(syncFix);
+}
+
+/*
+ * Header:
+ *
+ * identifier: 3 bytes "ID3" ("3DI" if footer)
+ * version: 2 bytes
+ * flags: 1 byte
+ * tag size: 4 bytes
+ */
+static id3header_t *read_header(VFSFile *fp)
+{
+	id3header_t *id3_data = calloc(sizeof(id3header_t), 1);
+	char id3_flags, cToInt[4];
+	int bottom = 0;
+	
+	vfs_fread(cToInt, 1, 3, fp);
+	if(strncmp(cToInt, "3DI", 3) == 0)
+		bottom = 1;
+	vfs_fread(id3_data->version, 1, 2, fp);
+	vfs_fread(&id3_flags, 1, 1, fp);
+	if((id3_flags & 0x80) == 0x80)
+		id3_data->unsync = 1;
+	if((id3_flags & 0x40) == 0x40 && id3_data->version[0] > 0x02)
+		id3_data->extended = 1;
+	vfs_fread(cToInt, 1, 4, fp);
+	id3_data->size = synchsafe2int(cToInt);
+	if(bottom == 1)
+		vfs_fseek(fp, -10 - id3_data->size, SEEK_CUR);
+	
+#ifdef META_DEBUG
+	if(id3_data->version[0] == 0x04)
+	{
+		pdebug("Version: ID3v2.4", META_DEBUG);
+	}
+	else if(id3_data->version[0] == 0x03)
+	{
+		pdebug("Version: ID3v2.3", META_DEBUG);
+	}
+	else if(id3_data->version[0] == 0x02)
+	{
+		pdebug("Version: ID3v2.2", META_DEBUG);
+	}
+#endif
+	if(id3_data->version[0] < 0x02 || id3_data->version[0] > 0x04)
+	{
+		free(id3_data);
+		return NULL;
+	}
+		
+	return id3_data;
+}
+
+static int id3_lookupframe(char *tag, int tagver)
+{
+	int i;
+
+	switch (tagver) {
+	case ID3v22:
+		for (i = 0; id3v22_lookup[i].frameid; i++)
+			if (!strcmp(tag, id3v22_lookup[i].frameid))
+				return id3v22_lookup[i].code;
+		return -1;
+		break;
+	case ID3v23:
+		for (i = 0; id3v23_lookup[i].frameid; i++)
+			if (!strcmp(tag, id3v23_lookup[i].frameid))
+				return id3v23_lookup[i].code;
+		return -1;
+		break;
+	case ID3v24:
+		for (i = 0; id3v23_lookup[i].frameid; i++)
+			if (!strcmp(tag, id3v24_lookup[i].frameid))
+				return id3v24_lookup[i].code;
+		return -1;
+		break;
+	}
+	return -1;
+}
+
+static framedata_t *parseFrame(char **bp, char *end, id3header_t *id3_data)
+{
+	static unsigned char frameid[5];
+	unsigned char frameflags[2] = "", cToInt[5];
+	int framesize, frameidcode;
+	framedata_t *framedata;
+
+	/* TODO: Unsync, decompress, decrypt, grouping, data length */
+	switch (id3_data->version[0]) {
+	case 2:
+		if (end - *bp < 6)
+			return NULL;
+		frameid[3] = 0;
+		memcpy(frameid, *bp, 3);
+		*bp += 3;
+		frameidcode = id3_lookupframe(frameid, ID3v22);
+		memcpy(cToInt, *bp, 3);
+		cToInt[3] = 0;
+		if(id3_data->unsync)
+			unsync(cToInt, *bp);
+		framesize = be24int(cToInt);
+		*bp += 3;
+		break;
+	case 3:
+		if (end - *bp < 10)
+			return NULL;
+		frameid[4] = 0;
+		memcpy(frameid, *bp, 4);
+		*bp += 4;
+		frameidcode = id3_lookupframe(frameid, ID3v23);
+		memcpy(cToInt, *bp, 4);
+		if(id3_data->unsync)
+			unsync(cToInt, *bp);
+		framesize = be2int(cToInt);
+		*bp += 4;
+
+		memcpy(frameflags, *bp, 2);
+		*bp += 2;
+		break;
+	case 4:
+		if (end - *bp < 10)
+			return NULL;
+		frameid[4] = 0;
+		memcpy(frameid, *bp, 4);
+		*bp += 4;
+		frameidcode = id3_lookupframe(frameid, ID3v24);
+		memcpy(cToInt, *bp, 4);
+		framesize = synchsafe2int(cToInt);
+		*bp += 4;
+
+		memcpy(frameflags, *bp, 2);
+		*bp += 2;
+		break;
+	default:
+		/* Should not be reached */
+		return NULL;
+	}
+
+	/* printf("found id:%s, size:%-8d\n", frameid, framesize); */
+	if (framesize > end - *bp)
+		return NULL;
+	framedata = calloc(sizeof(framedata_t), 1);
+	framedata->frameid = frameidcode;
+	if(id3_data->unsync)
+		frameflags[1] |= 0x02;
+	framedata->flags = malloc(2);
+	memcpy(framedata->flags, frameflags, 2);
+	if(id3_data->version[0] == 0x04)
+	{
+		/*
+		 * 4 bytes extra for compression or original size
+		 * 1 byte extra for encyption
+		 * 1 byte extra for grouping
+		 */
+		if((frameflags[1] & 0x08) == 0x08 ||
+			(frameflags[1] & 0x01) == 0x01)
+		{
+			*bp += 4;
+			framesize -= 4;
+		}
+		if((frameflags[1] & 0x04) == 0x04)
+		{
+			*bp++;
+			framesize--;
+		}
+		if((frameflags[1] & 0x40) == 0x40)
+		{
+			*bp++;
+			framesize--;
+		}
+	}
+	else if(id3_data->version[0] == 0x03)
+	{
+		/*
+		 * 4 bytes extra for compression or original size
+		 * 1 byte extra for encyption
+		 * 1 byte extra for grouping
+		 */
+		if((frameflags[1] & 0x80) == 0x80)
+		{
+			char tmp[4];
+			
+			memcpy(tmp, *bp, 4);
+			*bp += 4;
+			if((frameflags[1] & 0x02) == 0x02)
+				unsync(tmp, *bp);
+			framesize -= 4;
+		}
+		if((frameflags[1] & 0x40) == 0x40)
+		{
+			*bp++;
+			framesize--;
+		}
+		if((frameflags[1] & 0x20) == 0x20)
+		{
+			*bp++;
+			framesize--;
+		}
+	}
+	framedata->len = framesize;
+	framedata->data = malloc(framesize);
+	memcpy(framedata->data, *bp, framesize);
+	*bp += framesize;
+	
+	/* Parse text appropriately to UTF-8. */
+	if(frameid[0] == 'T' && strcmp(frameid, "TXXX") &&
+		strcmp(frameid, "TXX"))
+	{
+		unsigned char *ptr, *data = NULL, *utf = NULL;
+		int encoding;
+		
+		ptr = framedata->data;
+
+		if(framedata->len == 0)
+		{
+			framedata->data = realloc(framedata->data, 1);
+			strcpy(framedata->data, "\0");
+			return framedata;
+		}
+		
+		encoding = *(ptr++);
+		data = realloc(data, framedata->len);
+		*(data + framedata->len - 1) = '\0';
+		memcpy(data, ptr, framedata->len - 1);
+		if((framedata->flags[1] & 0x02) == 0x02)
+		{
+			resync_t *unsync = checkunsync(data, framedata->len);
+			framedata->len -= unsync->count;
+			free(unsync);
+		}
+		if(encoding == 0x00)
+		{
+			if(utf != NULL)
+				free(utf);
+			iso88591_to_utf8(data, framedata->len - 1, &utf);
+		}
+		else if(encoding == 0x01)
+		{
+			if(utf != NULL)
+				free(utf);
+			utf16bom_to_utf8(data, framedata->len - 1, &utf);
+		}
+		else if(encoding == 0x02)
+		{
+			if(utf != NULL)
+				free(utf);
+			utf16be_to_utf8(data, framedata->len - 1, &utf);
+		}
+		else if(encoding == 0x03)
+		{
+			utf = realloc(utf, framedata->len);
+			strcpy(utf, data);
+		}
+		framedata->len = strlen(utf) + 1;
+		framedata->data = realloc(framedata->data, framedata->len);
+		strcpy(framedata->data, utf);
+		framedata->data[framedata->len - 1] = '\0';
+		free(utf);
+		free(data);
+	}
+	/* Or unsync. */
+	else
+	{
+		unsigned char *data = NULL, *ptr;
+		
+		ptr = framedata->data;
+		
+		data = realloc(data, framedata->len);
+		// *(data + framedata->len - 1) = '\0';
+		memcpy(data, ptr, framedata->len);
+		if((framedata->flags[1] & 0x02) == 0x02)
+		{
+			resync_t *unsync = checkunsync(data, framedata->len);
+			framedata->len -= unsync->count;
+			free(unsync);
+		}
+		framedata->data = realloc(framedata->data, framedata->len);
+		memcpy(framedata->data, data, framedata->len);
+		free(data);
+	}
+	
+	return framedata;
+}
+
+static id3v2_t *readFrames(char *bp, char *end, id3header_t *id3_data)
+{
+	id3v2_t *id3v2 = calloc(sizeof(id3v2_t), 1);
+
+	while(bp < end)
+	{
+		framedata_t *framedata = NULL;
+
+		if(*bp == '\0')
+			break;
+		
+		framedata = parseFrame(&bp, end, id3_data);
+		
+		id3v2->items = realloc(id3v2->items, (id3v2->numitems + 1) * 
+			sizeof(framedata_t *));
+		id3v2->items[id3v2->numitems++] = framedata;
+	}
+	id3v2->version = id3_data->version[0];
+	
+	return id3v2;
+}
+
+/*unsigned char *ID3v2_parseText(framedata_t *frame)
+{
+	unsigned char *data = NULL, *utf = NULL, *ptr;
+	char encoding;
+	
+	ptr = frame->data;
+	
+	encoding = *(ptr++);
+	data = realloc(data, frame->len);
+	memset(data, '\0', frame->len);
+	memcpy(data, ptr, frame->len - 1);
+	if((frame->flags[1] & 0x02) == 0x02)
+	{
+		resync_t *unsync = checkunsync(data, 0);
+		free(unsync);
+	}
+	if(encoding == 0x00)
+	{
+		if(utf != NULL)
+			free(utf);
+		iso88591_to_utf8(data, &utf);
+	}
+	else if(encoding == 0x01)
+	{
+		if(utf != NULL)
+			free(utf);
+		utf16bom_to_utf8(data, frame->len - 1, &utf);
+	}
+	else if(encoding == 0x02)
+	{
+		if(utf != NULL)
+			free(utf);
+		utf16be_to_utf8(data, frame->len - 1, &utf);
+	}
+	else if(encoding == 0x03)
+	{
+		utf = realloc(utf, strlen(data) + 1);
+		strcpy(utf, data);
+	}
+	free(data);
+	
+	return utf;
+}
+
+unsigned char *ID3v2_getData(framedata_t *frame)
+{
+	unsigned char *data = NULL, *ptr;
+	
+	ptr = frame->data;
+	
+	data = realloc(data, frame->len + 1);
+	memset(data, '\0', frame->len + 1);
+	memcpy(data, ptr, frame->len);
+	if((frame->flags[1] & 0x02) == 0x02)
+	{
+		resync_t *unsync = checkunsync(data, frame->len);
+		free(unsync);
+	}
+	
+	return data;
+}*/
+
+int findID3v2(VFSFile *fp)
+{
+	unsigned char tag_buffer[BUFFER_SIZE], *bp = tag_buffer;
+	int pos, search = -1, i, status = 0, charsRead;
+	
+	charsRead = vfs_fread(tag_buffer, 1, 10, fp);
+	pos = 0;
+	bp = tag_buffer;
+	while(status == 0 && !feof(fp))
+	{
+		if(search == -1)
+		{
+			if((strncmp(bp, "ID3", 3) == 0 ||
+				strncmp(bp, "3DI", 3) == 0))
+					status = 1;
+			else
+			{
+				vfs_fseek(fp, 3, SEEK_END);
+				charsRead = vfs_fread(tag_buffer, 1, 3, fp);
+				search = -2;
+			}
+		}
+		else
+		{
+			if(search == -2)
+			{
+				bp = tag_buffer;
+				pos = vfs_ftell(fp);
+				if((strncmp(bp, "ID3", 3) == 0 ||
+					strncmp(bp, "3DI", 3) == 0)) status = 1;
+				search = 1;
+			}
+			if(status != 1)
+			{
+				pos = vfs_ftell(fp) - BUFFER_SIZE;
+				vfs_fseek(fp, pos, SEEK_SET);
+				charsRead = vfs_fread(tag_buffer, 1, BUFFER_SIZE,
+						fp);
+
+				bp = tag_buffer;
+				for(i = 0; i < charsRead - 3 && status == 0;
+					i++)
+				{
+					bp++;
+					if((strncmp(bp, "ID3", 3) == 0 ||
+						strncmp(bp, "3DI", 3) == 0))
+							status = 1;
+				}
+				if(status == 1)
+					pos += bp - tag_buffer;
+				pos -= BUFFER_SIZE - 9;
+				if((pos < -BUFFER_SIZE + 9 || ferror(fp)) &&
+					status != 1)
+						status = -1;
+			}
+		}
+		/*
+		 * An ID3v2 tag can be detected with the following pattern:
+		 *
+		 * $49 44 33 yy yy xx zz zz zz zz
+		 *
+		 * Where yy is less than $FF, xx is the 'flags' byte and zz is 
+		 * less than $80.
+		 */
+		if(status == 1 && *(bp + 3) < 0xFF && *(bp + 4) < 0xFF &&
+			*(bp + 6) < 0x80 && *(bp + 7) < 0x80 &&
+			*(bp + 8) < 0x80 && *(bp + 9) < 0x80)
+				status = 1;
+		else if(status != -1)
+			status = 0;
+		if(search == 0)
+			search = -1;
+	}
+	if(status < 0 || feof(fp))
+		return -1;
+	else
+		return pos;
+}
+
+id3v2_t *readID3v2(char *filename)
+{
+	VFSFile *fp;
+	id3v2_t *id3v2 = NULL;
+	int pos;
+	
+	fp = vfs_fopen(filename, "rb");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+
+	pdebug("Searching for tag...", META_DEBUG);
+	pos = findID3v2(fp);
+	if(pos > -1)
+	{
+		id3header_t *id3_data;
+		char *tag_buffer, *bp;
+		
+		/* Found the tag. */
+		vfs_fseek(fp, pos, SEEK_SET);
+		pdebug("Found ID3v2 tag...", META_DEBUG);
+		/* Read the header */
+		id3_data = read_header(fp);
+		if(id3_data == NULL)
+		{
+			pdebug("Or not.", META_DEBUG);
+			vfs_fclose(fp);
+			return NULL;
+		}
+		/* Read tag into buffer */
+		tag_buffer = malloc(id3_data->size);
+		vfs_fread(tag_buffer, 1, id3_data->size, fp);
+		bp = tag_buffer;
+		/* Skip extended header */
+		if(id3_data->extended)
+		{
+			char cToInt[4];
+			
+			memcpy(cToInt, bp, 4);
+			bp += 4;
+			if(id3_data->version[0] == 0x03 &&
+				id3_data->unsync == 1)
+					unsync(cToInt, bp);
+			if(id3_data->version[0] > 0x03)
+				bp += synchsafe2int(cToInt);
+			else
+				bp += be2int(cToInt);
+		}
+		/* Read frames into id3v2_t */
+		id3v2 = readFrames(bp, tag_buffer + id3_data->size, id3_data);
+
+		free(tag_buffer);
+		free(id3_data);
+	}
+	
+	vfs_fclose(fp);
+	
+	return id3v2;
+}
+
+void freeID3v2(id3v2_t *id3v2)
+{
+	int i;
+	
+	for(i = 0; i < id3v2->numitems; i++)
+	{
+		framedata_t *frame;
+		
+		frame = id3v2->items[i];
+		free(frame->flags);
+		free(frame->data);
+		free(frame);
+	}
+	free(id3v2->items);
+	free(id3v2);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/ape.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,42 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef APE_H
+#define APE_H 1
+typedef struct
+{
+	unsigned int len;
+	unsigned char *data, *name;
+} apefielddata_t;
+
+typedef struct
+{
+	unsigned int numitems, version;
+	apefielddata_t **items;
+} ape_t;
+
+#ifndef MAKE_BMP
+int findAPE(VFSFile *);
+#else
+int findAPE(VFSFile *);
+#endif
+ape_t *readAPE(char *);
+void freeAPE(ape_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/bmp_vfs.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,18 @@
+#ifndef TAG_BMP_VFS_H
+#define TAG_BMP_VFS_H 1
+#include <libaudacious/vfs.h>
+#define fopen(fp, set) vfs_fopen(fp, set); feof_ctr = 1
+#define fclose(fp) vfs_fclose(fp); feof_ctr = 0
+#define fread feof_ctr = vfs_fread
+#define fwrite vfs_fwrite
+#define fseek vfs_fseek
+#define ftell vfs_ftell
+#define feof(fp) feof_ctr == 0
+#define ferror(fp) feof_ctr == 0
+#define FILE VFSFile
+
+/* BMP VFS does not have vfs_feof, so we have a makeshift method. */
+
+static size_t feof_ctr;
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/cdaudio.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,31 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef CDAUDIO_H
+#define CDAUDIO_H 1
+
+typedef struct
+{
+	unsigned char *title, *artist, *album, *mbid;
+} cdaudio_t;
+
+cdaudio_t *readCDAudio(char *, char);
+void freeCDAudio(cdaudio_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/endian.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,51 @@
+/*
+ *   libmetatag: - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef ENDIAN_H
+#define ENDIAN_H 1
+#define le2long(le) (le2long_internal(((unsigned char *)(le))))
+#define le2long_internal(le) \
+((le[0] << 0) | (le[1] << 8) | (le[2] << 16) | (le[3] << 24) \
+(le[4] << 32) | (le[5] << 40) | (le[6] << 48) | (le[7] << 56))
+#define le2int(le) (le2int_internal(((unsigned char *)(le))))
+#define le2int_internal(le) \
+((le[0] << 0) | (le[1] << 8) | (le[2] << 16) | (le[3] << 24))
+#define le2short(le) (le2short_internal(((unsigned char *)(le))))
+#define le2short_internal(le) \
+((le[0] << 0) | (le[1] << 8))
+#define be2int(be) (be2int_internal(((unsigned char *)(be))))
+#define be2int_internal(be) \
+((be[3] << 0) | (be[2] << 8) | (be[1] << 16) | (be[0] << 24))
+#define be24int(be) (be24int_internal(((unsigned char *)(be))))
+#define be24int_internal(be) \
+((be[2] << 0) | (be[1] << 8) | (be[0] << 16))
+#define be2short(be) (be2short_internal(((unsigned char *)(be))))
+#define be2short_internal(be) \
+((be[1] << 0) | (be[0] << 8))
+#define flac2int(flac) (flac2int_internal(((unsigned char *)(flac))))
+#define flac2int_internal(flac) \
+((flac[1] << 16) | (flac[2] << 8) | (flac[3] << 0))
+#define synchsafe2int(synch) (synchsafe2int_internal(((unsigned char *)(synch))))
+#define synchsafe2int_internal(synch) \
+((synch[0] << 21) | (synch[1] << 14) | \
+(synch[2] << 7)   | (synch[3] << 0))
+#define tagid2int(bp) be2int(bp)
+#endif
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/id3v1.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,36 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef ID3V1_H
+#define ID3V1_H 1
+
+typedef struct
+{
+	unsigned char *title, *artist, *album, *year, *comment, track, genre;
+} id3v1_t;
+
+#ifndef MAKE_BMP
+int findID3v1(VFSFile *);
+#else
+int findID3v1(VFSFile *);
+#endif
+id3v1_t *readID3v1(char *);
+void freeID3v1(id3v1_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/id3v2.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,114 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef ID3V2_H
+#define ID3V2_H 1
+
+typedef struct
+{
+	int frameid, len;
+	unsigned char *data, *flags;
+} framedata_t;
+
+typedef struct
+{
+	int numitems, version;
+	framedata_t **items;
+} id3v2_t;
+
+typedef struct
+{
+	char 	*frameid;
+	int	code;
+} id3_lookup_t;
+
+enum
+{
+	ID3v22,
+	ID3v23,
+	ID3v24
+};
+
+enum 
+{
+	ID3V24_AENC, ID3V24_APIC, ID3V24_ASPI, ID3V24_COMM, ID3V24_COMR, 
+	ID3V24_ENCR, ID3V24_EQU2, ID3V24_ETCO, ID3V24_GEOB, ID3V24_GRID, 
+	ID3V24_LINK, ID3V24_MCDI, ID3V24_MLLT, ID3V24_OWNE, ID3V24_PRIV, 
+	ID3V24_PCNT, ID3V24_POPM, ID3V24_POSS, ID3V24_RBUF, ID3V24_RVA2, 
+	ID3V24_RVRB, ID3V24_SEEK, ID3V24_SIGN, ID3V24_SYLT, ID3V24_SYTC, 
+	ID3V24_TALB, ID3V24_TBPM, ID3V24_TCOM, ID3V24_TCON, ID3V24_TCOP, 
+	ID3V24_TDEN, ID3V24_TDLY, ID3V24_TDOR, ID3V24_TDRC, ID3V24_TDRL, 
+	ID3V24_TDTG, ID3V24_TENC, ID3V24_TEXT, ID3V24_TFLT, ID3V24_TIPL, 
+	ID3V24_TIT1, ID3V24_TIT2, ID3V24_TIT3, ID3V24_TKEY, ID3V24_TLAN, 
+	ID3V24_TLEN, ID3V24_TMCL, ID3V24_TMED, ID3V24_TMOO, ID3V24_TOAL, 
+	ID3V24_TOFN, ID3V24_TOLY, ID3V24_TOPE, ID3V24_TOWN, ID3V24_TPE1, 
+	ID3V24_TPE2, ID3V24_TPE3, ID3V24_TPE4, ID3V24_TPOS, ID3V24_TPRO, 
+	ID3V24_TPUB, ID3V24_TRCK, ID3V24_TRSN, ID3V24_TRSO, ID3V24_TSOA, 
+	ID3V24_TSOP, ID3V24_TSOT, ID3V24_TSRC, ID3V24_TSSE, ID3V24_TSST, 
+	ID3V24_TXXX, ID3V24_UFID, ID3V24_USER, ID3V24_USLT, ID3V24_WCOM, 
+	ID3V24_WCOP, ID3V24_WOAF, ID3V24_WOAR, ID3V24_WOAS, ID3V24_WORS, 
+	ID3V24_WPAY, ID3V24_WPUB, ID3V24_WXXX
+};
+
+enum
+{
+	ID3V22_BUF, ID3V22_CNT, ID3V22_COM, ID3V22_CRA, ID3V22_CRM, ID3V22_ETC, 
+	ID3V22_EQU, ID3V22_GEO, ID3V22_IPL, ID3V22_LNK, ID3V22_MCI, ID3V22_MLL, 
+	ID3V22_PIC, ID3V22_POP, ID3V22_REV, ID3V22_RVA, ID3V22_SLT, ID3V22_STC, 
+	ID3V22_TAL, ID3V22_TBP, ID3V22_TCM, ID3V22_TCO, ID3V22_TCR, ID3V22_TDA, 
+	ID3V22_TDY, ID3V22_TEN, ID3V22_TFT, ID3V22_TIM, ID3V22_TKE, ID3V22_TLA, 
+	ID3V22_TLE, ID3V22_TMT, ID3V22_TOA, ID3V22_TOF, ID3V22_TOL, ID3V22_TOR, 
+	ID3V22_TOT, ID3V22_TP1, ID3V22_TP2, ID3V22_TP3, ID3V22_TP4, ID3V22_TPA, 
+	ID3V22_TPB, ID3V22_TRC, ID3V22_TRD, ID3V22_TRK, ID3V22_TSI, ID3V22_TSS, 
+	ID3V22_TT1, ID3V22_TT2, ID3V22_TT3, ID3V22_TXT, ID3V22_TXX, ID3V22_TYE, 
+	ID3V22_UFI, ID3V22_ULT, ID3V22_WAF, ID3V22_WAR, ID3V22_WAS, ID3V22_WCM, 
+	ID3V22_WCP, ID3V22_WPB, ID3V22_WXX 
+};
+
+enum
+{
+	ID3V23_AENC, ID3V23_APIC, ID3V23_COMM, ID3V23_COMR, ID3V23_ENCR, 
+	ID3V23_EQUA, ID3V23_ETCO, ID3V23_GEOB, ID3V23_GRID, ID3V23_IPLS, 
+	ID3V23_LINK, ID3V23_MCDI, ID3V23_MLLT, ID3V23_OWNE, ID3V23_PRIV, 
+	ID3V23_PCNT, ID3V23_POPM, ID3V23_POSS, ID3V23_RBUF, ID3V23_RVAD, 
+	ID3V23_RVRB, ID3V23_SYLT, ID3V23_SYTC, ID3V23_TALB, ID3V23_TBPM, 
+	ID3V23_TCOM, ID3V23_TCON, ID3V23_TCOP, ID3V23_TDAT, ID3V23_TDLY, 
+	ID3V23_TENC, ID3V23_TEXT, ID3V23_TFLT, ID3V23_TIME, ID3V23_TIT1, 
+	ID3V23_TIT2, ID3V23_TIT3, ID3V23_TKEY, ID3V23_TLAN, ID3V23_TLEN, 
+	ID3V23_TMED, ID3V23_TOAL, ID3V23_TOFN, ID3V23_TOLY, ID3V23_TOPE, 
+	ID3V23_TORY, ID3V23_TOWN, ID3V23_TPE1, ID3V23_TPE2, ID3V23_TPE3, 
+	ID3V23_TPE4, ID3V23_TPOS, ID3V23_TPUB, ID3V23_TRCK, ID3V23_TRDA, 
+	ID3V23_TRSN, ID3V23_TRSO, ID3V23_TSIZ, ID3V23_TSRC, ID3V23_TSSE, 
+	ID3V23_TYER, ID3V23_TXXX, ID3V23_UFID, ID3V23_USER, ID3V23_USLT, 
+	ID3V23_WCOM, ID3V23_WCOP, ID3V23_WOAF, ID3V23_WOAR, ID3V23_WOAS, 
+	ID3V23_WORS, ID3V23_WPAY, ID3V23_WPUB, ID3V23_WXXX 
+};
+
+
+unsigned char *ID3v2_parseText(framedata_t *);
+unsigned char *ID3v2_getData(framedata_t *);
+#ifndef MAKE_BMP
+int findID3v2(VFSFile *);
+#else
+int findID3v2(VFSFile *);
+#endif
+id3v2_t *readID3v2(char *);
+void freeID3v2(id3v2_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/itunes.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,36 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef ITUNES_H
+#define ITUNES_H 1
+typedef struct
+{
+	unsigned char	*title, *artist, *album, *genre, *year, *copyright,
+			track, maxtrack, disc, maxdisc;
+} itunes_t;
+
+#ifndef MAKE_BMP
+int findiTunes(VFSFile *);
+#else
+int findiTunes(VFSFile *);
+#endif
+itunes_t *readiTunes(char *);
+void freeiTunes(itunes_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/tags.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,77 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef METATAGS_H
+#define METATAGS_H 1
+
+#include <libaudacious/vfs.h>
+
+#include "wma.h"
+#include "id3v2.h"
+#include "id3v1.h"
+#include "vorbis.h"
+#include "itunes.h"
+#include "ape.h"
+#include "cdaudio.h"
+
+extern const char *genre_list[148];
+
+/*
+ * Note: This struct has some signs to determine what tags a file has
+ * and it is left to the program to interpret them. The main unsigned chars
+ * are merely used as references directly to something in one of the substructs.
+ *
+ */
+
+typedef struct {
+	unsigned char	*artist,
+			*title,
+			*mb,
+			*album,
+			*year,
+			*track,
+			*genre;
+	int		has_wma,
+			has_id3v1,
+			has_id3v2,
+			has_ape,
+			has_vorbis,
+			has_flac,
+			has_oggflac,
+			has_speex,
+			has_itunes,
+			has_cdaudio,
+			prefer_ape;
+	wma_t		*wma;
+	id3v1_t		*id3v1;
+	id3v2_t		*id3v2;
+	ape_t		*ape;
+	vorbis_t	*vorbis,
+			*flac,
+			*oggflac,
+			*speex;
+	itunes_t	*itunes;
+	cdaudio_t	*cdaudio;
+} metatag_t;
+
+void get_tag_data(metatag_t *, char *, int);
+metatag_t *metatag_new(void);
+void metatag_delete(metatag_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/unicode.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,29 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef UNICODE_H
+#define UNICODE_H 1
+wchar_t *utf8_to_wchar(unsigned char *, size_t);
+unsigned char *wchar_to_utf8(wchar_t *, size_t);
+void iso88591_to_utf8(unsigned char *, size_t, unsigned char **);
+void utf16bom_to_utf8(unsigned char *, size_t, unsigned char **);
+void utf16be_to_utf8(unsigned char *, size_t, unsigned char **);
+void utf16le_to_utf8(unsigned char *, size_t, unsigned char **);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/vorbis.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,58 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef VORBIS_H
+#define VORBIS_H 1
+
+#define READ_VORBIS 1
+#define READ_FLAC 2
+#define READ_OGGFLAC 3
+#define READ_SPEEX 4
+
+typedef struct
+{
+	unsigned int len;
+	unsigned char *data, *name;
+} vorbisfielddata_t;
+
+typedef struct
+{
+	unsigned int numitems, vendorlen;
+	unsigned char *vendor;
+	vorbisfielddata_t **items;
+} vorbis_t;
+
+#ifndef MAKE_BMP
+int findVorbis(VFSFile *);
+int findFlac(VFSFile *);
+int findOggFlac(VFSFile *);
+int findSpeex(VFSFile *);
+#else
+int findVorbis(VFSFile *);
+int findFlac(VFSFile *);
+int findOggFlac(VFSFile *);
+int findSpeex(VFSFile *);
+#endif
+vorbis_t *readVorbis(char *);
+vorbis_t *readFlac(char *);
+vorbis_t *readOggFlac(char *);
+vorbis_t *readSpeex(char *);
+void freeVorbis(vorbis_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/include/wma.h	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,42 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#ifndef WMA_H
+#define WMA_H 1
+typedef struct
+{
+	unsigned char *data, *name;
+	int type;
+} attribute_t;
+
+typedef struct
+{
+	unsigned int numitems;
+	attribute_t **items;
+} wma_t;
+
+#ifndef MAKE_BMP
+int findWMA(VFSFile *);
+#else
+int findWMA(VFSFile *);
+#endif
+wma_t *readWMA(char *);
+void freeWMA(wma_t *);
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/itunes.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,303 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/itunes.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+#define NAME_ATOM	((0xa9 << 24) | ('n' << 16) | ('a' << 8) | ('m' << 0))
+#define ARTIST_ATOM	((0xa9 << 24) | ('A' << 16) | ('R' << 8) | ('T' << 0))
+#define ALBUM_ATOM	((0xa9 << 24) | ('a' << 16) | ('l' << 8) | ('b' << 0))
+#define YEAR_ATOM	((0xa9 << 24) | ('d' << 16) | ('a' << 8) | ('y' << 0))
+#define GENRE_ATOM	(('g' << 24) | ('n' << 16) | ('r' << 8) | ('e' << 0))
+#define TRACK_ATOM	(('t' << 24) | ('r' << 16) | ('k' << 8) | ('n' << 0))
+#define DISC_ATOM	(('d' << 24) | ('i' << 16) | ('s' << 8) | ('k' << 0))
+#define COPYRIGHT_ATOM	(('c' << 24) | ('p' << 16) | ('r' << 8) | ('t' << 0))
+
+static itunes_t *readAtoms(VFSFile *fp)
+{
+	itunes_t *itunes = calloc(sizeof(itunes_t), 1);
+	unsigned char *tag_buffer, *bp, cToInt[4];
+	int size, meta_size;
+	
+	vfs_fread(cToInt, 1, 4, fp);
+	size = be2int(cToInt) - 4;
+	tag_buffer = malloc(size);
+	vfs_fread(tag_buffer, 1, size, fp);
+	bp = tag_buffer;
+	bp += 8;
+	while(bp - tag_buffer < size)
+	{
+		unsigned char **tag_data = NULL;
+		switch(tagid2int(bp))
+		{
+			case NAME_ATOM:
+				tag_data = &itunes->title;
+				break;
+			case ARTIST_ATOM:
+				tag_data = &itunes->artist;
+				break;
+			case ALBUM_ATOM:
+				tag_data = &itunes->album;
+				break;
+			/*
+			 * Genre is weird.  I don't know how user input genres
+			 * work. Use at your own risk. Is terminated with an
+			 * extra 0.
+			 */
+			case GENRE_ATOM:
+				tag_data = &itunes->genre;
+				break;
+			case YEAR_ATOM:
+				tag_data = &itunes->year;
+				break;
+			case COPYRIGHT_ATOM:
+				tag_data = &itunes->copyright;
+				break;
+		}
+		if(	tagid2int(bp) == NAME_ATOM ||
+			tagid2int(bp) == ARTIST_ATOM ||
+			tagid2int(bp) == ALBUM_ATOM ||
+			tagid2int(bp) == GENRE_ATOM ||
+			tagid2int(bp) == YEAR_ATOM ||
+			tagid2int(bp) == COPYRIGHT_ATOM)
+		{
+			bp += 4;
+			memcpy(cToInt, bp, 4);
+			meta_size = be2int(cToInt) - 16;
+			bp += 16;
+			*tag_data = realloc(*tag_data, meta_size + 1);
+			*(*tag_data + meta_size) = '\0';
+			strncpy(*tag_data, bp, meta_size);
+			bp += meta_size;
+		}
+		/*
+		 * Track number is easier, but unverified.
+		 * Disc number is similar.  Doesn't skip at end though.
+		 *
+		 * Assuming max of what a char can hold.
+		 */
+		else if(tagid2int(bp) == TRACK_ATOM ||
+			tagid2int(bp) == DISC_ATOM)
+		{
+			unsigned char *tag_num_data, *tag_num_max_data, skip;
+			if(tagid2int(bp) == TRACK_ATOM)
+			{
+				tag_num_data = &itunes->track;
+				tag_num_max_data = &itunes->maxtrack;
+				skip = 2;
+			}
+			else if(tagid2int(bp) == DISC_ATOM)
+			{
+				tag_num_data = &itunes->disc;
+				tag_num_max_data = &itunes->maxdisc;
+				skip = 0;
+			}
+			bp += 4;
+			memcpy(cToInt, bp, 4);
+			meta_size = be2int(cToInt) - 16;
+			bp += 19;
+			*tag_num_data = *bp;
+			bp += 2;
+			*tag_num_max_data = *bp;
+			bp += 1 + skip;
+		}
+		/*
+		 * TODO:
+		 *
+		 * I'd like to handle rtng (Rating) but I don't know how.
+		 * What are the xxIDs? aART?
+		 * Where is Grouping, BPM, Composer?
+		 *
+		 * In any case, do not handle anything else.
+		 */
+		else
+		{
+			bp -= 4;
+			memcpy(cToInt, bp, 4);
+			meta_size = be2int(cToInt);
+			bp += meta_size;
+		}
+		bp += 4;
+	}
+	
+	free(tag_buffer);
+	
+	return itunes;
+}
+
+int findiTunes(VFSFile *fp)
+{
+	unsigned char *tag_buffer, *bp, cToInt[4], *start_pos;
+	int parent_size, atom_size, pos = 0;
+	
+	/*
+	 * Find the ILST atom and point the file pointer there and return
+	 * the atom size.
+	 *
+	 * Please note that this could easily not work (especially when not
+	 * encoded with iTunes) as this is mainly based off of a reference
+	 * file encoded by iTunes and the QTFileFormat documentation released
+	 * by Apple.  It's not based off any official documentation of M4A.
+	 *
+	 * As a result of not being based off official documentation, this is
+	 * EXTREMELY likely to return 0 (i.e. no data found).
+	 *
+	 * First we assume that ftyp is the first atom, and M4A is the type.
+	 */
+	vfs_fread(cToInt, 1, 4, fp);
+	atom_size = be2int(cToInt) - 4;
+	tag_buffer = malloc(8);
+	vfs_fread(tag_buffer, 1, 8, fp);
+	if(strncmp(tag_buffer, "ftypM4A ", 8))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	vfs_fseek(fp, -8, SEEK_CUR);
+	tag_buffer = realloc(tag_buffer, atom_size);
+	vfs_fread(tag_buffer, 1, atom_size, fp);
+	/* Now keep skipping until we hit a moov container atom */
+	while(!feof(fp))
+	{
+		vfs_fread(cToInt, 1, 4, fp);
+		atom_size = be2int(cToInt) - 4;
+		tag_buffer = realloc(tag_buffer, atom_size);
+		pos = ftell(fp);
+		vfs_fread(tag_buffer, 1, atom_size, fp);
+		if(!strncmp(tag_buffer, "moov", 4))
+			break;
+	}
+	if(feof(fp))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	parent_size = atom_size;
+	/* Now looking for child udta atom (NOT in trak) */
+	bp = tag_buffer + 4;
+	while(bp - tag_buffer < parent_size)
+	{
+		memcpy(cToInt, bp, 4);
+		atom_size = be2int(cToInt) - 4;
+		bp += 4;
+		if(!strncmp(bp, "udta", 4))
+			break;
+		bp += atom_size;
+	}
+	if(strncmp(bp, "udta", 4))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	parent_size = atom_size;
+	start_pos = bp;
+	bp += 4;
+	/* Now looking for child meta atom */
+	while(bp - start_pos < parent_size)
+	{
+		memcpy(cToInt, bp, 4);
+		atom_size = be2int(cToInt) - 4;
+		bp += 4;
+		if(!strncmp(bp, "meta", 4))
+			break;
+		bp += atom_size;
+	}
+	if(strncmp(bp, "meta", 4))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	parent_size = atom_size;
+	start_pos = bp;
+	bp += 8;
+	/* Now looking for child ilst atom */
+	while(bp - start_pos < parent_size)
+	{
+		memcpy(cToInt, bp, 4);
+		atom_size = be2int(cToInt) - 4;
+		bp += 4;
+		if(!strncmp(bp, "ilst", 4))
+			break;
+		bp += atom_size;
+	}
+	if(strncmp(bp, "ilst", 4))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	bp -= 4;
+	vfs_fseek(fp, bp - tag_buffer + pos, SEEK_SET);
+	
+	free(tag_buffer);
+	
+	return atom_size;
+}
+
+itunes_t *readiTunes(char *filename)
+{
+	VFSFile *fp;
+	itunes_t *itunes;
+	int status;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	status = findiTunes(fp);
+	if(status == -1)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	itunes = readAtoms(fp);
+	
+	vfs_fclose(fp);
+	
+	return itunes;
+}
+
+void freeiTunes(itunes_t *itunes)
+{
+	if(itunes->title != NULL)
+		free(itunes->title);
+	if(itunes->artist != NULL)
+		free(itunes->artist);
+	if(itunes->album != NULL)
+		free(itunes->album);
+	if(itunes->year != NULL)
+		free(itunes->year);
+	if(itunes->genre != NULL)
+		free(itunes->genre);
+	free(itunes);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/tags.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,820 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libaudacious/vfs.h"
+
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#include "include/tags.h"
+#include "include/endian.h"
+
+void tag_exists(metatag_t *meta, char *filename)
+{
+	VFSFile *fp;
+	int status = 0;
+
+	/* Check for CD Audio 
+	if(findCDAudio(filename))
+	{
+		pdebug("Found CD Audio...", META_DEBUG);
+		tags[0] = CD_AUDIO;
+
+		return tags;
+	}*/
+
+	fp = vfs_fopen(filename, "r");
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+
+		return;
+	}
+
+	/* Check for ID3v1 */
+	vfs_fseek(fp, -128, SEEK_END);
+	if(findID3v1(fp))
+	{
+		pdebug("Found ID3v1 tag...", META_DEBUG);
+		meta->has_id3v1 = 1;
+	}
+
+	/* Check for ID3v2 */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findID3v2(fp);
+	if(status > -1)
+	{
+		pdebug("Found ID3v2 tag...", META_DEBUG);
+		meta->has_id3v2 = 1;
+	}
+	status = 0;
+
+	/* Check for Ogg Vorbis */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findVorbis(fp);
+	if(status > -1)
+	{
+		pdebug("Found Vorbis comment block...", META_DEBUG);
+		meta->has_vorbis = 1;
+	}
+	status = 0;
+
+	/* Check for FLAC */
+	vfs_fseek(fp, 0, SEEK_SET);
+	if(findFlac(fp))
+	{
+		pdebug("Found FLAC tag...", META_DEBUG);
+		meta->has_flac = 1;
+	}
+	
+	/* Check for OggFLAC */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findOggFlac(fp);
+	if(status > -1)
+	{
+		pdebug("Found OggFLAC...", META_DEBUG);
+		meta->has_oggflac = 1;
+	}
+	status = 0;
+
+	/* Check for Speex */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findSpeex(fp);
+	if(status > -1)
+	{
+		pdebug("Found Speex...", META_DEBUG);
+		meta->has_speex = 1;
+	}
+	status = 0;
+
+	/* Check for APE */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findAPE(fp);
+	if(status > 0)
+	{
+		pdebug("Found APE tag...", META_DEBUG);
+		meta->has_ape = 1;
+	}
+	status = 0;
+	
+	/* Check for iTunes M4A */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findiTunes(fp);
+	if(status > -1)
+	{
+		pdebug("Found iTunes tag...", META_DEBUG);
+		meta->has_itunes = 1;
+	}
+
+	/* Check for the devil tag: WMA */
+	vfs_fseek(fp, 0, SEEK_SET);
+	status = findWMA(fp);
+	if(status > -1)
+	{
+		pdebug("Found WMA tag...", META_DEBUG);
+		meta->has_wma = 1;
+	}
+
+	vfs_fclose(fp);
+
+	return;
+}
+
+void metaCD(metatag_t *meta, char *filename, int track)
+{
+	int tmp;
+	
+	pdebug("Getting CD Audio metadata...", META_DEBUG);
+	meta->cdaudio = readCDAudio(filename, track);
+	if(meta->cdaudio == NULL)
+	{
+		pdebug("Error getting metadata", META_DEBUG);
+		
+		return;
+	}
+	
+	meta->has_cdaudio = 1;
+	
+	pdebug("Reading metadata into structs...", META_DEBUG);
+	meta->artist = meta->cdaudio->artist;
+	meta->title = meta->cdaudio->title;
+	meta->mb = realloc(meta->mb, strlen(meta->cdaudio->mbid) + 1);
+	strcpy(meta->mb, meta->cdaudio->mbid);
+	meta->album = meta->cdaudio->album;
+	meta->year = NULL;
+	meta->genre = NULL;
+	/* Special track handling... Yay! */
+	meta->track = realloc(meta->track, 4);
+	tmp = snprintf(meta->track, 3, "%d", track);
+	*(meta->track + tmp) = '\0';
+	
+	return;
+} /* End CD Audio support */
+
+static ape_t *fetchAPE(char *filename)
+{
+	ape_t *ape;
+
+	pdebug("Getting APE tag metadata...", META_DEBUG);
+	ape = readAPE(filename);
+
+	return ape;
+} /* End APE read */
+
+void metaAPE(metatag_t *meta)
+{
+	int i, t = 0;
+	ape_t *ape = meta->ape;
+	
+	for(i = 0; i < ape->numitems; i++)
+	{
+		apefielddata_t *item = ape->items[i];
+		
+		if(!strcmp(item->name, "Title"))
+		{
+			pdebug("Found Title!", META_DEBUG);
+			meta->title = item->data;
+		}
+		else if(!strcmp(item->name, "Artist"))
+		{
+			pdebug("Found Artist!", META_DEBUG);
+			meta->artist = item->data;
+		}
+		else if(strcmp(item->name, "Album") == 0)
+		{
+			pdebug("Found Album!", META_DEBUG);
+			meta->album = item->data;
+		}
+		else if(strcmp(item->name, "Year") == 0)
+		{
+			pdebug("Found Year!", META_DEBUG);
+			meta->year = item->data;
+		}
+		else if(strcmp(item->name, "Genre") == 0)
+		{
+			pdebug("Found Genre!", META_DEBUG);
+			meta->genre = realloc(meta->genre, strlen(item->data)
+				+ 1);
+			strcpy(meta->genre, item->data);
+		}
+		else if(strcmp(item->name, "Track") == 0)
+		{
+			pdebug("Found Track!", META_DEBUG);
+			meta->track = realloc(meta->track, strlen(item->data)
+				+ 1);
+			strcpy(meta->track, item->data);
+		}
+		else if(strcmp(item->name, "Comment") == 0)
+		{
+			unsigned char *comment_value = NULL, *eq_pos, *sep_pos,
+					*subvalue;
+
+			subvalue = item->data;
+			sep_pos = strchr(subvalue, '|');
+			while(sep_pos != NULL || t == 0)
+			{
+				t = 1;
+				if(sep_pos != NULL)
+					*sep_pos = '\0';
+				comment_value = realloc(comment_value,
+							strlen(subvalue) + 1);
+				strcpy(comment_value, subvalue);
+				if(sep_pos != NULL)
+					sep_pos++;
+				eq_pos = strchr(comment_value, '=');
+				if(eq_pos != NULL)
+				{
+					*eq_pos = '\0';
+					eq_pos++;
+					if(!strcmp(comment_value,
+						"musicbrainz_trackid"))
+					{
+						/* ??? */
+						pdebug("Found MusicBrainz Track ID!", META_DEBUG);
+						meta->mb = realloc(meta->mb,
+							strlen(eq_pos) + 1);
+						strcpy(meta->mb, eq_pos);
+						break;
+					}
+				}
+				if(sep_pos != NULL)
+				{
+					t = 0;
+					subvalue = sep_pos;
+					sep_pos = strchr(subvalue, '|');
+				}
+			}
+			t = 0;
+			if(comment_value != NULL)
+				free(comment_value);
+		}
+	}
+} /* End APE parsing */
+
+static vorbis_t *fetchVorbis(char *filename, int tag_type)
+{
+	vorbis_t *comments = NULL;
+
+	/* Several slightly different methods of getting the data... */
+	if(tag_type == READ_VORBIS)
+	{
+		pdebug("Getting Vorbis comment metadata...", META_DEBUG);
+		comments = readVorbis(filename);
+	}
+	else if(tag_type == READ_FLAC)
+	{
+		pdebug("Getting FLAC tag metadata...", META_DEBUG);
+		comments = readFlac(filename);
+	}
+	else if(tag_type == READ_OGGFLAC)
+	{
+		pdebug("Getting OggFLAC tag metadata...", META_DEBUG);
+		comments = readOggFlac(filename);
+	}
+	else if(tag_type == READ_SPEEX)
+	{
+		pdebug("Getting Speex tag metadata...", META_DEBUG);
+		comments = readSpeex(filename);
+	}
+	if(comments == NULL)
+		pdebug("Error in Vorbis Comment read", META_DEBUG);
+	
+	return comments;
+} /* End Vorbis/FLAC/OggFLAC/Speex read */
+
+void metaVorbis(metatag_t *meta)
+{
+	int i;
+	vorbis_t *comments = NULL;
+	
+	/* I'm not expecting more than one vorbis tag */
+	if(meta->has_vorbis)
+		comments = meta->vorbis;
+	else if(meta->has_flac)
+		comments = meta->flac;
+	else if(meta->has_oggflac)
+		comments = meta->oggflac;
+	else if(meta->has_speex)
+		comments = meta->speex;
+	if(comments == NULL)
+		return;
+	
+	for(i = 0; i < comments->numitems; i++)
+	{
+		vorbisfielddata_t *field = comments->items[i];
+		if(!fmt_strcasecmp(field->name, "TITLE"))
+		{
+			pdebug("Found Title!", META_DEBUG);
+			
+			meta->title = field->data;
+		}
+		/* Prefer PERFORMER to ARTIST */
+		else if(!fmt_strcasecmp(field->name, "PERFORMER"))
+		{
+			pdebug("Found Artist!", META_DEBUG);
+
+			meta->artist = field->data;
+		}
+		else if(!fmt_strcasecmp(field->name, "ARTIST") && meta->artist == NULL)
+		{
+			pdebug("Found Artist!", META_DEBUG);
+
+			meta->artist = field->data;
+		}
+		else if(!fmt_strcasecmp(field->name, "ALBUM"))
+		{
+			pdebug("Found Album!", META_DEBUG);
+			
+			meta->album = field->data;
+		}
+		else if(!fmt_strcasecmp(field->name, "MUSICBRAINZ_TRACKID"))
+		{
+			pdebug("Found MusicBrainz Track ID!", META_DEBUG);
+			
+			meta->mb = realloc(meta->mb, strlen(field->data) + 1);
+			memset(meta->mb, '\0', strlen(field->data) + 1);
+			memcpy(meta->mb, field->data, strlen(field->data));
+		}
+		else if(!fmt_strcasecmp(field->name, "GENRE"))
+		{
+			pdebug("Found Genre!", META_DEBUG);
+			
+			meta->genre = realloc(meta->genre, strlen(field->data)
+				+ 1);
+			memset(meta->genre, '\0', strlen(field->data) + 1);
+			memcpy(meta->genre, field->data, strlen(field->data));
+		}
+		else if(!fmt_strcasecmp(field->name, "TRACKNUMBER"))
+		{
+			pdebug("Found Track!", META_DEBUG);
+			
+			meta->track = realloc(meta->track, strlen(field->data)
+				+ 1);
+			memset(meta->track, '\0', strlen(field->data) + 1);
+			memcpy(meta->track, field->data, strlen(field->data));
+		}
+	}
+	
+	return;
+} /* End Vorbis/FLAC/OggFLAC/Speex parsing */
+
+static id3v2_t *fetchID3v2(char *filename)
+{
+	id3v2_t *id3v2;
+
+	pdebug("Getting ID3v2 tag metadata...", META_DEBUG);
+	id3v2 = readID3v2(filename);
+	if(id3v2 == NULL)
+		pdebug("Error in readID3v2()", META_DEBUG);
+
+	return id3v2;
+} /* End ID3v2 read */
+
+void metaID3v2(metatag_t *meta)
+{
+	int i;
+	id3v2_t *id3v2 = meta->id3v2;
+	
+	for(i = 0; i < id3v2->numitems; i++)
+	{
+		unsigned char *data = NULL, *utf = NULL;
+		framedata_t *frame = id3v2->items[i];
+		if(	(id3v2->version == 2 && frame->frameid == ID3V22_TT2) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TIT2) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_TIT2))
+		{
+			pdebug("Found Title!", META_DEBUG);
+			
+			meta->title = frame->data;
+		}
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_TP1) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TPE1) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_TPE1))
+		{
+			pdebug("Found Artist!", META_DEBUG);
+
+			meta->artist = frame->data;
+		}
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_TAL) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TALB) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_TALB))
+		{
+			pdebug("Found Album!", META_DEBUG);
+			
+			meta->album = frame->data;
+		}
+		/* No strict year for ID3v2.4 */
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_TYE) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TYER))
+		{
+			pdebug("Found Year!", META_DEBUG);
+			
+			meta->year = frame->data;
+		}
+		/* Won't translate ID3v1 genres  yet */
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_TCO) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TCON) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_TCON))
+		{
+			pdebug("Found Genre!", META_DEBUG);
+			
+			meta->genre = realloc(meta->genre, frame->len);
+			memset(meta->genre, '\0', frame->len);
+			memcpy(meta->genre, frame->data, frame->len);
+		}
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_TRK) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_TRCK) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_TRCK))
+		{
+			pdebug("Found Track!", META_DEBUG);
+			
+			meta->track = realloc(meta->track, frame->len);
+			memset(meta->track, '\0', frame->len);
+			memcpy(meta->track, frame->data, frame->len);
+		}
+		else if((id3v2->version == 2 && frame->frameid == ID3V22_UFI) ||
+			(id3v2->version == 3 && frame->frameid == ID3V23_UFID) ||
+			(id3v2->version == 4 && frame->frameid == ID3V24_UFID))
+		{
+			data = frame->data;
+			
+			if(!strcmp(data, "http://musicbrainz.org"))
+			{
+				pdebug("Found MusicBrainz Track ID!", META_DEBUG);
+				
+				utf = data + 23;
+
+				meta->mb = realloc(meta->mb, frame->len - 22);
+				memcpy(meta->mb, utf, frame->len - 23);
+				*(meta->mb + frame->len - 23) = 0;
+			}
+		}
+	}
+} /* End ID3v2 parsing */
+
+static itunes_t *fetchiTunes(char *filename)
+{
+	itunes_t *itunes;
+	
+	pdebug("Getting iTunes tag metadata...", META_DEBUG);
+	itunes = readiTunes(filename);
+
+	return itunes;
+} /* End M4A read */
+
+void metaiTunes(metatag_t *meta)
+{
+	itunes_t *itunes = meta->itunes;
+	int tmp;
+	
+	if(itunes->title != NULL)
+	{
+		pdebug("Found Title!", META_DEBUG);
+			
+		meta->title = itunes->title;
+	}
+	if(itunes->artist != NULL)
+	{
+		pdebug("Found Artist!", META_DEBUG);
+			
+		meta->artist = itunes->artist;
+	}
+	if(itunes->album != NULL)
+	{
+		pdebug("Found Album!", META_DEBUG);
+			
+		meta->album = itunes->album;
+	}
+	/* I don't read genre (yet) and I won't read max track number. */
+	if(itunes->track > 0 && itunes->track != 255)
+	{
+		pdebug("Found Track!", META_DEBUG);
+		
+		meta->track = realloc(meta->track, 4);
+		tmp = snprintf(meta->track, 3, "%d", itunes->track);
+		*(meta->track + tmp) = '\0';
+	}
+	if(itunes->year != NULL)
+	{
+		pdebug("Found Year!", META_DEBUG);
+		
+		meta->year = itunes->year;
+	}
+} /* End M4A parsing */
+
+static wma_t *fetchWMA(char *filename)
+{
+	wma_t *wma;
+
+	pdebug("Getting WMA metadata...", META_DEBUG);
+	wma = readWMA(filename);
+
+	return wma;
+} /* End WMA read */
+
+void metaWMA(metatag_t *meta)
+{
+	wma_t *wma = meta->wma;
+	int i, tmp;
+
+	for(i = 0; i < wma->numitems; i++)
+	{
+		attribute_t *attribute = wma->items[i];
+		
+		if(!strcmp(attribute->name, "Title"))
+		{
+			pdebug("Found Title!", META_DEBUG);
+
+			meta->title = attribute->data;
+		}
+		else if(!strcmp(attribute->name, "Author"))
+		{
+			pdebug("Found Artist!", META_DEBUG);
+
+			meta->artist = attribute->data;
+		}
+		else if(!strcmp(attribute->name, "WM/AlbumTitle"))
+		{
+			pdebug("Found Album!", META_DEBUG);
+
+			meta->album = attribute->data;
+		}
+		else if(!strcmp(attribute->name, "WM/Year"))
+		{
+			pdebug("Found Year!", META_DEBUG);
+
+			meta->year = attribute->data;
+		}
+		else if(!strcmp(attribute->name, "WM/Genre"))
+		{
+			pdebug("Found Genre!", META_DEBUG);
+
+			meta->genre = realloc(meta->genre,
+						strlen(attribute->data) + 1);
+			strcpy(meta->genre, attribute->data);
+		}
+		else if(!strcmp(attribute->name, "WM/TrackNumber"))
+		{
+			pdebug("Found Track!", META_DEBUG);
+
+			meta->track = realloc(meta->track, 4);
+			tmp = snprintf(meta->track, 3, "%d",
+					le2int(attribute->data));
+			*(meta->track + tmp) = '\0';
+		}
+	}
+}
+
+static id3v1_t *fetchID3v1(char *filename)
+{
+	id3v1_t *id3v1;
+	
+	pdebug("Getting ID3v1 tag metadata...", META_DEBUG);
+	id3v1 = readID3v1(filename);
+
+	return id3v1;
+} /* End ID3v1 read */
+
+void metaID3v1(metatag_t *meta)
+{
+	int tmp;
+	id3v1_t *id3v1 = meta->id3v1;
+	
+	if(id3v1->title != NULL)
+	{
+		pdebug("Found Title!", META_DEBUG);
+			
+		meta->title = id3v1->title;
+	}
+	if(id3v1->artist != NULL)
+	{
+		pdebug("Found Artist!", META_DEBUG);
+			
+		meta->artist = id3v1->artist;
+	}
+	if(id3v1->album != NULL)
+	{
+		pdebug("Found Album!", META_DEBUG);
+			
+		meta->album = id3v1->album;
+	}
+	if(id3v1->year != NULL)
+	{
+		pdebug("Found Year!", META_DEBUG);
+		
+		meta->year = id3v1->year;
+	}
+	if(id3v1->track != 255)
+	{
+		pdebug("Found Track!", META_DEBUG);
+		
+		meta->track = realloc(meta->track, 4);
+		tmp = snprintf(meta->track, 3, "%d", id3v1->track);
+		*(meta->track + tmp) = '\0';
+	}
+	/* I assume unassigned genre's are 255 */
+	if(id3v1->genre != 255 && id3v1->genre < sizeof(genre_list)
+		/ sizeof(char *))
+	{
+		pdebug("Found Genre!", META_DEBUG);
+		
+		meta->genre = realloc(meta->genre,
+			strlen(genre_list[id3v1->genre]) + 1);
+		strcpy(meta->genre, genre_list[id3v1->genre]);
+	}
+	/*
+	 * This next one is unofficial, but maybe someone will use it.
+	 * I don't think anyone's going to trigger this by accident.
+	 *
+	 * Specification:
+	 * In comment field (ID3v1 or ID3v1.1):
+	 * 1 byte: \0
+	 * 10 bytes: "MBTRACKID=" identifier
+	 * 16 bytes: binary representation of Track ID
+	 * 1 byte: \0
+	 *
+	 * e.g. for "Missing" on Evanescence's "Bring Me to Life" single
+	 *
+	 * Track ID = 9c2567cb-4a8b-4096-b105-dada6b95a08b
+	 *
+	 * Therefore, comment field would equal:
+	 * "MBID: \156%g\203J\139@\150\177\005\218\218k\149\160\139"
+	 * in ASCII (with three digit decimal escape characters)
+	 */
+	if(!strncmp((id3v1->comment) + 1, "MBTRACKID=", 10))
+	{
+		pdebug("Found MusicBrainz Track ID!", META_DEBUG);
+		
+		meta->mb = realloc(meta->mb, 37);
+		tmp = sprintf(meta->mb,
+	"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+			id3v1->comment[11],
+			id3v1->comment[12],
+			id3v1->comment[13],
+			id3v1->comment[14],
+			id3v1->comment[15],
+			id3v1->comment[16],
+			id3v1->comment[17],
+			id3v1->comment[18],
+			id3v1->comment[19],
+			id3v1->comment[20],
+			id3v1->comment[21],
+			id3v1->comment[22],
+			id3v1->comment[23],
+			id3v1->comment[24],
+			id3v1->comment[25],
+			id3v1->comment[26]);
+		*(meta->mb + tmp) = '\0';
+	}
+} /* End ID3v1 parsing */
+
+void get_tag_data(metatag_t *meta, char *filename, int track)
+{
+	if(track > 0)
+	{
+		metaCD(meta, filename, track);
+		
+		return;
+	}
+	else
+	{
+		tag_exists(meta, filename);
+		if(meta->has_id3v1)
+			meta->id3v1 = fetchID3v1(filename);
+		if(meta->has_id3v2)
+			meta->id3v2 = fetchID3v2(filename);
+		if(meta->has_ape)
+			meta->ape = fetchAPE(filename);
+		if(meta->has_vorbis)
+			meta->vorbis = fetchVorbis(filename, READ_VORBIS);
+		if(meta->has_flac)
+			meta->flac = fetchVorbis(filename, READ_FLAC);
+		if(meta->has_oggflac)
+			meta->oggflac = fetchVorbis(filename, READ_OGGFLAC);
+		if(meta->has_speex)
+			meta->speex = fetchVorbis(filename, READ_SPEEX);
+		if(meta->has_itunes)
+			meta->itunes = fetchiTunes(filename);
+		if(meta->has_wma)
+			meta->wma = fetchWMA(filename);
+	}
+	
+	/*
+	 * This order is rather arbitrary, but puts tags you'd EXPECT to
+	 * be the exclusive tag in the file first (i.e. vorbis and itunes).
+	 * Thus we return afterwards.
+	 */
+	
+	if(meta->has_vorbis || meta->has_flac || meta->has_oggflac ||
+		meta->has_speex)
+	{
+		metaVorbis(meta);
+		
+		return;
+	}
+	else if(meta->has_itunes)
+	{
+		metaiTunes(meta);
+		
+		return;
+	}
+	else if(meta->has_wma)
+	{
+		metaWMA(meta);
+
+		return;
+	}
+	/*
+	 * OK, here's the trick: APE preferred to ID3v2?
+	 * or ID3v2 preferred to APE?  ID3v1 loses regardless.
+	 * Thus it's put first to be overwritten.
+	 */
+	if(meta->has_id3v1)
+		metaID3v1(meta);
+	/* A little dirty for now, but it's not too difficult */
+	if(!meta->prefer_ape)
+	{
+		if(meta->has_ape)
+			metaAPE(meta);
+		if(meta->has_id3v2)
+			metaID3v2(meta);
+	}
+	else if(meta->prefer_ape)
+	{
+		if(meta->has_id3v2)
+			metaID3v2(meta);
+		if(meta->has_ape)
+			metaAPE(meta);
+	}
+	
+	return;
+}
+
+void metatag_delete(metatag_t *meta)
+{
+	/*
+	 * Genre, track, and MBID are the exceptions and must be freed
+	 * (Thanks ID3v1)
+	 */
+	if(meta->track != NULL)
+		free(meta->track);
+	if(meta->genre != NULL)
+		free(meta->genre);
+	if(meta->mb != NULL)
+		free(meta->mb);
+	if(meta->wma != NULL)
+		freeWMA(meta->wma);
+	if(meta->id3v1 != NULL)
+		freeID3v1(meta->id3v1);
+	if(meta->id3v2 != NULL)
+		freeID3v2(meta->id3v2);
+	if(meta->ape != NULL)
+		freeAPE(meta->ape);
+	if(meta->vorbis != NULL)
+		freeVorbis(meta->vorbis);
+	if(meta->flac != NULL)
+		freeVorbis(meta->flac);
+	if(meta->oggflac != NULL)
+		freeVorbis(meta->oggflac);
+	if(meta->speex != NULL)
+		freeVorbis(meta->speex);
+	if(meta->itunes != NULL)
+		freeiTunes(meta->itunes);
+	if(meta->cdaudio != NULL)
+		freeCDAudio(meta->cdaudio);
+	free(meta);
+}
+
+metatag_t *metatag_new(void)
+{
+	metatag_t *ret;
+	
+	if(!(ret = calloc(sizeof(*ret), 1)))
+		return NULL;
+#ifdef PREFER_APE
+	ret->prefer_ape = 1;
+#endif
+	
+	return ret;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/unicode.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,214 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdlib.h>
+#include <wchar.h>
+#include <string.h>
+#include "include/endian.h"
+#include "include/unicode.h"
+
+wchar_t *utf8_to_wchar(unsigned char *utf, size_t memsize)
+{
+	int i, j = 0;
+	wchar_t *mem;
+
+	mem = calloc(sizeof(wchar_t) * (memsize + 1), 1);
+
+	for(i = 0; i < memsize;)
+	{
+		if(utf[i] < 0x80)
+			mem[j++] = utf[i++];
+		else if(utf[i] < 0xE0)
+		{
+			mem[j++] = ((utf[i] & 0x1F) << 6) |
+				(utf[i + 1] & 0x3F);
+			i += 2;
+		}
+		else if(utf[i] < 0xF0)
+		{
+			mem[j++] = ((utf[i] & 0x0F) << 12) |
+				((utf[i + 1] & 0x3F) << 6) |
+				(utf[i + 2] & 0x3F);
+			i += 3;
+		}
+		else if(utf[i] < 0xF8)
+		{
+			mem[j++] = ((utf[i] & 0x07) << 18) |
+				((utf[i + 1] & 0x3F) << 12) |
+				((utf[i + 2] & 0x3F) << 6) |
+				(utf[i + 2] & 0x3F);
+			i += 4;
+		}
+		else if(utf[i] < 0xFC)
+		{
+			mem[j++] = ((utf[i] & 0x03) << 24) |
+				((utf[i + 1] & 0x3F) << 18) |
+				((utf[i + 2] & 0x3F) << 12) |
+				((utf[i + 3] & 0x3F) << 6) |
+				(utf[i + 4] & 0x3F);
+			i += 5;
+		}
+		else if(utf[i] >= 0xFC)
+		{
+			mem[j++] = ((utf[i] & 0x01) << 30) |
+				((utf[i + 1] & 0x3F) << 24) |
+				((utf[i + 2] & 0x3F) << 18) |
+				((utf[i + 3] & 0x3F) << 12) |
+				((utf[i + 4] & 0x3F) << 6) |
+				(utf[i + 5] & 0x3F);
+			i += 6;
+		}
+	}
+
+	mem = realloc(mem, sizeof(wchar_t) * (j + 1));
+
+	return mem;
+}
+
+unsigned char *wchar_to_utf8(wchar_t *wchar, size_t memsize)
+{
+	int i;
+	unsigned char *mem, *ptr;
+	
+	mem = calloc(memsize * 6 + 1, 1);
+	ptr = mem;
+	
+	for(i = 0; i < memsize; i++)
+	{
+		if(wchar[i] < 0x80)
+		{
+			*ptr++ = wchar[i] & 0x7F;
+		}
+		else if(wchar[i] < 0x800)
+		{
+			*ptr++ = 0xC0 | ((wchar[i] >> 6) & 0x1F);
+			*ptr++ = 0x80 | (wchar[i] & 0x3F);
+		}
+		else if(wchar[i] < 0x10000)
+		{
+			*ptr++ = 0xE0 | ((wchar[i] >> 12) & 0x0F);
+			*ptr++ = 0x80 | ((wchar[i] >> 6) & 0x3F);
+			*ptr++ = 0x80 | (wchar[i] & 0x3F);
+		}
+		else if(wchar[i] < 0x200000)
+		{
+			*ptr++ = 0xF0 | ((wchar[i] >> 18) & 0x07);
+			*ptr++ = 0x80 | ((wchar[i] >> 12) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 6) & 0x3F);
+			*ptr++ = 0x80 | (wchar[i] & 0x3F);
+		}
+		else if(wchar[i] < 0x4000000)
+		{
+			*ptr++ = 0xF8 | ((wchar[i] >> 24) & 0x03);
+			*ptr++ = 0x80 | ((wchar[i] >> 18) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 12) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 6) & 0x3F);
+			*ptr++ = 0x80 | (wchar[i] & 0x3F);
+		}
+		else if(wchar[i] < 0x80000000)
+		{
+			*ptr++ = 0xFC | ((wchar[i] >> 30) & 0x01);
+			*ptr++ = 0x80 | ((wchar[i] >> 24) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 18) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 12) & 0x3F);
+			*ptr++ = 0x80 | ((wchar[i] >> 6) & 0x3F);
+			*ptr++ = 0x80 | (wchar[i] & 0x3F);
+		}
+	}
+	
+	mem = realloc(mem, ptr - mem + 1);
+	
+	return mem;
+}
+
+void iso88591_to_utf8(unsigned char *iso, size_t memsize,
+				unsigned char **utf)
+{
+	int i;
+	wchar_t *wchar;
+
+	wchar = calloc(sizeof(wchar_t) * (memsize + 1), 1);
+	for(i = 0; i < memsize; i++) wchar[i] = iso[i];
+	*utf = wchar_to_utf8(wchar, memsize);
+	free(wchar);
+}
+
+void utf16bom_to_utf8(unsigned char *utf16, size_t memsize,
+				unsigned char **utf)
+{
+	wchar_t *wchar;
+	unsigned char utf16char[2];
+	int endian = 0, i;
+
+	wchar = calloc(sizeof(wchar_t) * memsize / 2 - 1, 1);
+	for(i = 0; i < memsize; i += 2)
+	{
+		if(i == 0)
+		{
+			if(utf16[i] == 0xFF) endian = 0;
+			else if(utf16[i] == 0xFE) endian = 1;
+		}
+		else
+		{
+			utf16char[0] = utf16[i];
+			utf16char[1] = utf16[i + 1];
+			if(endian == 1)      wchar[i / 2 - 1] = be2short(utf16char);
+			else if(endian == 0) wchar[i / 2 - 1] = le2short(utf16char);
+		}
+	}
+	*utf = wchar_to_utf8(wchar, memsize / 2 - 1);
+	free(wchar);
+}
+
+void utf16be_to_utf8(unsigned char *utf16, size_t memsize,
+				unsigned char **utf)
+{
+	wchar_t *wchar;
+	unsigned char utf16char[2];
+	int i;
+
+	wchar = calloc(sizeof(wchar_t) * (memsize / 2), 1);
+	for(i = 0; i < memsize; i += 2)
+	{
+		utf16char[0] = utf16[i];
+		utf16char[1] = utf16[i + 1];
+		wchar[i / 2] = be2short(utf16char);
+	}
+	*utf = wchar_to_utf8(wchar, memsize / 2);
+	free(wchar);
+}
+
+void utf16le_to_utf8(unsigned char *utf16, size_t memsize,
+				unsigned char **utf)
+{
+	wchar_t *wchar;
+	unsigned char utf16char[2];
+	int i;
+
+	wchar = calloc(sizeof(wchar_t) * (memsize / 2), 1);
+	for(i = 0; i < memsize; i += 2)
+	{
+		utf16char[0] = utf16[i];
+		utf16char[1] = utf16[i + 1];
+		wchar[i / 2] = le2short(utf16char);
+	}
+	*utf = wchar_to_utf8(wchar, memsize / 2);
+	free(wchar);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/vorbis.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,413 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2003, 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+ 
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/vorbis.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define BUFFER_SIZE 4096
+
+static vorbis_t *readComments(VFSFile *fp)
+{
+	vorbis_t *comments = calloc(sizeof(vorbis_t), 1);
+	unsigned char cToInt[4];
+	int i, lines, j = 0;
+	
+	vfs_fread(cToInt, 1, 4, fp);
+	comments->vendorlen = le2int(cToInt);
+	comments->vendor = malloc(comments->vendorlen);
+	vfs_fread(comments->vendor, 1, comments->vendorlen, fp);
+	vfs_fread(cToInt, 1, 4, fp);
+	lines = comments->numitems = le2int(cToInt);
+	comments->items = realloc(comments->items,
+		(comments->numitems) * sizeof(vorbisfielddata_t *));
+	for(i = 0; i < lines; i++)
+	{
+		unsigned char *data, *dp;
+		vorbisfielddata_t *fielddata =
+			calloc(sizeof(vorbisfielddata_t), 1);
+		
+		vfs_fread(cToInt, 1, 4, fp);
+		fielddata->len = le2int(cToInt);
+		data = malloc(fielddata->len);
+		vfs_fread(data, 1, fielddata->len, fp);
+		dp = strchr(data, '=');
+		if(dp == NULL)
+		{
+			pdebug("No '=' in comment!", META_DEBUG);
+			comments->numitems--;
+			free(data);
+			continue;
+		}
+		*dp = '\0';
+		dp++;
+		fielddata->name = malloc(strlen(data) + 1);
+		fielddata->data = malloc(fielddata->len - strlen(data));
+		*(fielddata->data + fielddata->len - strlen(data) - 1) = '\0';
+		strcpy(fielddata->name, data);
+		strncpy(fielddata->data, dp, fielddata->len - strlen(data) - 1);
+		
+		comments->items[j++] = fielddata;
+
+		free(data);
+	}
+	
+	return comments;
+}
+
+int findVorbis(VFSFile *fp)
+{
+	char tag_id[5] = "";
+	unsigned char *tag_buffer, *bp, vorbis_type;
+	int status = 0, pos = -1;
+	
+	vfs_fread(tag_id, 1, 4, fp);
+	if(strcmp(tag_id, "OggS"))
+		return -1;
+	tag_buffer = malloc(23);
+	vfs_fread(tag_buffer, 1, 23, fp);
+	bp = tag_buffer;
+	while(status == 0)
+	{
+		unsigned char segments, *lacing;
+		unsigned int pagelen = 0, i;
+		bp += 22;
+		segments = *bp;
+		lacing = malloc(segments);
+		vfs_fread(lacing, 1, segments, fp);
+		for(i = 0; i < segments; i++)
+			pagelen += lacing[i];
+		tag_buffer = realloc(tag_buffer, pagelen);
+		vfs_fread(tag_buffer, 1, pagelen, fp);
+		bp = tag_buffer;
+		for(i = 0; i < segments && status == 0;)
+		{
+			if(strncmp(bp + 1, "vorbis", 6) == 0)
+			{
+				vorbis_type = *bp;
+				if(vorbis_type == 0x03)
+				{
+					pos = ftell(fp) - pagelen +
+						(bp - tag_buffer);
+					status = 1;
+				}
+			}
+			bp += lacing[i++];
+		}
+		if(status == 1 || feof(fp))
+		{
+			free(lacing);
+			break;
+		}
+		tag_buffer = realloc(tag_buffer, 27);
+		vfs_fread(tag_buffer, 1, 27, fp);
+		bp = tag_buffer + 4;
+		free(lacing);
+	}
+
+	free(tag_buffer);
+	
+	if(feof(fp))
+		return -1;
+	else
+		return pos;
+}
+
+int findFlac(VFSFile *fp)
+{
+	unsigned char tag_id[5] = "";
+	int pos;
+	
+	vfs_fread(tag_id, 1, 4, fp);
+	if(strcmp(tag_id, "fLaC"))
+		return 0;
+	while(1)
+	{
+		vfs_fread(tag_id, 1, 4, fp);
+		if((tag_id[0] & 0x7F) == 4)
+			return 1;
+		else if((tag_id[0] & 0x80) == 0x80)
+			return 0;
+		else if(feof(fp))
+			return 0;
+		else
+		{
+			pos = flac2int(tag_id);
+			vfs_fseek(fp, pos, SEEK_CUR);
+		}
+	}
+}
+
+int findOggFlac(VFSFile *fp)
+{
+	char tag_id[5] = "";
+	unsigned char *tag_buffer, *bp;
+	int status = 0, pos = -1;
+	
+	vfs_fread(tag_id, 1, 4, fp);
+	if(strcmp(tag_id, "OggS"))
+		return -1;
+	/* I assume first page always has only "fLaC" */
+	tag_buffer = malloc(28);
+	vfs_fread(tag_buffer, 1, 28, fp);
+	bp = tag_buffer + 24;
+	if(strncmp(bp, "fLaC", 4))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	tag_buffer = realloc(tag_buffer, 27);
+	vfs_fread(tag_buffer, 1, 27, fp);
+	bp = tag_buffer + 4;
+	while(status == 0)
+	{
+		unsigned char segments, *lacing = NULL;
+		unsigned int pagelen = 0, i;
+		bp += 22;
+		segments = *bp;
+		lacing = realloc(lacing, segments);
+		vfs_fread(lacing, 1, segments, fp);
+		for(i = 0; i < segments; i++)
+			pagelen += lacing[i];
+		tag_buffer = realloc(tag_buffer, pagelen);
+		vfs_fread(tag_buffer, 1, pagelen, fp);
+		bp = tag_buffer;
+		for(i = 0; i < segments && status == 0;)
+		{
+			if((bp[0] & 0x7F) == 4)
+			{
+				pos = ftell(fp) - pagelen +
+					(bp - tag_buffer);
+				status = 1;
+			}
+			else if((tag_id[0] & 0x80) == 0x80)
+			{
+				free(tag_buffer);
+				free(lacing);
+				return -1;
+			}
+			else
+				bp += lacing[i++];
+		}
+		if(status == 1 || feof(fp))
+			break;
+		tag_buffer = realloc(tag_buffer, 27);
+		vfs_fread(tag_buffer, 1, 27, fp);
+		bp = tag_buffer + 4;
+		free(lacing);
+	}
+	
+	free(tag_buffer);
+	
+	if(feof(fp))
+		return -1;
+	else
+		return pos;
+}
+
+int findSpeex(VFSFile *fp)
+{
+	char tag_id[5] = "";
+	unsigned char *tag_buffer, *bp, segments, *lacing = NULL;
+	unsigned int pagelen = 0, i;
+	int pos = -1;
+	
+	vfs_fread(tag_id, 1, 4, fp);
+	if(strcmp(tag_id, "OggS"))
+		return -1;
+	tag_buffer = malloc(23);
+	vfs_fread(tag_buffer, 1, 23, fp);
+	bp = tag_buffer + 22;
+	segments = *bp;
+	lacing = malloc(segments);
+	vfs_fread(lacing, 1, segments, fp);
+	for(i = 0; i < segments; i++)
+		pagelen += lacing[i];
+	tag_buffer = realloc(tag_buffer, pagelen);
+	vfs_fread(tag_buffer, 1, pagelen, fp);
+	bp = tag_buffer;
+	if(strncmp(bp, "Speex   ", 8))
+	{
+		free(lacing);
+		free(tag_buffer);
+		return -1;
+	}
+	tag_buffer = realloc(tag_buffer, 27);
+	vfs_fread(tag_buffer, 1, 27, fp);
+	bp = tag_buffer + 26;
+	segments = *bp;
+	lacing = realloc(lacing, segments);
+	vfs_fread(lacing, 1, segments, fp);
+	pos = ftell(fp);
+	
+	free(tag_buffer);
+	free(lacing);
+	
+	if(feof(fp))
+		return -1;
+	else
+		return pos;
+}
+
+vorbis_t *readVorbis(char *filename)
+{
+	VFSFile *fp;
+	vorbis_t *comments;
+	int pos;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	pos = findVorbis(fp);
+	if(pos < 0)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	vfs_fseek(fp, pos + 7, SEEK_SET);
+	comments = readComments(fp);
+	
+	vfs_fclose(fp);
+	
+	return comments;
+}
+
+vorbis_t *readFlac(char *filename)
+{
+	VFSFile *fp;
+	vorbis_t *comments;
+	int status;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	status = findFlac(fp);
+	if(!status)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	comments = readComments(fp);
+	
+	vfs_fclose(fp);
+	
+	return comments;
+}
+
+vorbis_t *readOggFlac(char *filename)
+{
+	VFSFile *fp;
+	vorbis_t *comments;
+	int pos;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	pos = findOggFlac(fp);
+	if(pos < 0)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	vfs_fseek(fp, pos + 4, SEEK_SET);
+	comments = readComments(fp);
+	
+	vfs_fclose(fp);
+	
+	return comments;
+}
+
+vorbis_t *readSpeex(char *filename)
+{
+	VFSFile *fp;
+	vorbis_t *comments;
+	int pos;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	pos = findSpeex(fp);
+	if(pos < 0)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	vfs_fseek(fp, pos, SEEK_SET);
+	comments = readComments(fp);
+	
+	vfs_fclose(fp);
+	
+	return comments;
+}
+
+void freeVorbis(vorbis_t *comments)
+{
+	int i;
+	
+	for(i = 0; i < comments->numitems; i++)
+	{
+		vorbisfielddata_t *field;
+		
+		field = comments->items[i];
+		free(field->data);
+		free(field->name);
+		free(field);
+	}
+	free(comments->items);
+	free(comments->vendor);
+	free(comments);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/tags/wma.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,305 @@
+/*
+ *   libmetatag - A media file tag-reader library
+ *   Copyright (C) 2004  Pipian
+ *
+ *   This library is free software; you can redistribute it and/or
+ *   modify it under the terms of the GNU Lesser General Public
+ *   License as published by the Free Software Foundation; either
+ *   version 2.1 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
+ *   Lesser General Public License for more details.
+ *
+ *   You should have received a copy of the GNU Lesser 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
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "include/bmp_vfs.h"
+#include "include/wma.h"
+#include "include/endian.h"
+#include "../fmt.h"
+#include "../config.h"
+#include "include/unicode.h"
+#define WMA_GUID	(unsigned char [16]) \
+		      {	0x30, 0x26, 0xB2, 0x75, \
+			0x8E, 0x66, \
+			0xCF, 0x11, \
+			0xA6, 0xD9, \
+			0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }
+#define CONTENT_GUID	(unsigned char [16]) \
+		      {	0x33, 0x26, 0xB2, 0x75, \
+			0x8E, 0x66, \
+			0xCF, 0x11, \
+			0xA6, 0xD9, \
+			0x00, 0xAA, 0x00, 0x62, 0xCE, 0x6C }
+#define EXTENDED_GUID	(unsigned char [16]) \
+		      {	0x40, 0xA4, 0xD0, 0xD2, \
+			0x07, 0xE3, \
+			0xD2, 0x11, \
+			0x97, 0xF0, \
+			0x00, 0xA0, 0xC9, 0x5E, 0xA8, 0x50 }
+#define BUFFER_SIZE 4096
+
+static wma_t *readAttributes(VFSFile *fp, int pos)
+{
+	wma_t *wma = calloc(sizeof(wma_t), 1);
+	attribute_t *attribute;
+	unsigned char *tag_buffer = NULL, *bp, cToInt[8], *data = NULL;
+	int title_size, author_size, copyright_size, desc_size, rating_size,
+		size, primary_items, i;
+
+	vfs_fseek(fp, pos, SEEK_SET);
+	vfs_fread(cToInt, 1, 8, fp);
+	/* Yes, it's 64 bits in size, but I'm lazy and don't want to adjust. */
+	size = le2int(cToInt);
+	tag_buffer = malloc(size - 24);
+	vfs_fread(tag_buffer, 1, size - 24, fp);
+	bp = tag_buffer;
+	title_size = le2short(bp);
+	bp += 2;
+	author_size = le2short(bp);
+	bp += 2;
+	copyright_size = le2short(bp);
+	bp += 2;
+	desc_size = le2short(bp);
+	bp += 2;
+	rating_size = le2short(bp);
+	bp += 2;
+	if(title_size > 0)
+	{
+		attribute = calloc(sizeof(attribute_t), 1);
+		wma->items = realloc(wma->items,
+				(wma->numitems + 1) * sizeof(attribute_t *));
+		attribute->name = malloc(6);
+		strcpy(attribute->name, "Title");
+		data = malloc(title_size);
+		memcpy(data, bp, title_size);
+		bp += title_size;
+		utf16le_to_utf8(data, title_size, &attribute->data);
+		attribute->type = 0;
+		wma->items[wma->numitems++] = attribute;
+		free(data);
+	}
+	if(author_size > 0)
+	{
+		attribute = calloc(sizeof(attribute_t), 1);
+		wma->items = realloc(wma->items,
+				(wma->numitems + 1) * sizeof(attribute_t *));
+		attribute->name = malloc(7);
+		strcpy(attribute->name, "Author");
+		data = malloc(author_size);
+		memcpy(data, bp, author_size);
+		bp += author_size;
+		utf16le_to_utf8(data, author_size, &attribute->data);
+		attribute->type = 0;
+		wma->items[wma->numitems++] = attribute;
+		free(data);
+	}
+	if(copyright_size > 0)
+	{
+		attribute = calloc(sizeof(attribute_t), 1);
+		wma->items = realloc(wma->items,
+				(wma->numitems + 1) * sizeof(attribute_t *));
+		attribute->name = malloc(10);
+		strcpy(attribute->name, "Copyright");
+		data = malloc(copyright_size);
+		memcpy(data, bp, copyright_size);
+		bp += copyright_size;
+		utf16le_to_utf8(data, copyright_size, &attribute->data);
+		attribute->type = 0;
+		wma->items[wma->numitems++] = attribute;
+		free(data);
+	}
+	if(desc_size > 0)
+	{
+		attribute = calloc(sizeof(attribute_t), 1);
+		wma->items = realloc(wma->items,
+				(wma->numitems + 1) * sizeof(attribute_t *));
+		attribute->name = malloc(12);
+		strcpy(attribute->name, "Description");
+		data = malloc(desc_size);
+		memcpy(data, bp, desc_size);
+		bp += desc_size;
+		utf16le_to_utf8(data, desc_size, &attribute->data);
+		attribute->type = 0;
+		wma->items[wma->numitems++] = attribute;
+		free(data);
+	}
+	if(rating_size > 0)
+	{
+		attribute = calloc(sizeof(attribute_t), 1);
+		wma->items = realloc(wma->items,
+				(wma->numitems + 1) * sizeof(attribute_t *));
+		attribute->name = malloc(7);
+		strcpy(attribute->name, "Rating");
+		data = malloc(rating_size);
+		memcpy(data, bp, rating_size);
+		bp += rating_size;
+		utf16le_to_utf8(data, desc_size, &attribute->data);
+		attribute->type = 0;
+		wma->items[wma->numitems++] = attribute;
+		free(data);
+	}
+	primary_items = wma->numitems;
+
+	vfs_fread(tag_buffer, 16, 1, fp);
+	if(memcmp(tag_buffer, EXTENDED_GUID, 16))
+	{
+		free(tag_buffer);
+		return wma;
+	}
+	vfs_fread(cToInt, 8, 1, fp);
+	/*
+	 * Another 64-bit breakage. If you've got that large an amount of
+	 * metadata, you've got a problem.
+	 */
+	size = le2int(cToInt);
+	tag_buffer = realloc(tag_buffer, size);
+	vfs_fread(tag_buffer, size, 1, fp);
+	bp = tag_buffer;
+	memcpy(cToInt, bp, 2);
+	wma->numitems += le2short(cToInt);
+	wma->items = realloc(wma->items,
+			wma->numitems * sizeof(attribute_t *));
+	bp += 2;
+	for(i = primary_items; i < wma->numitems; i++)
+	{
+		int type;
+
+		attribute = calloc(sizeof(attribute_t), 1);
+
+		memcpy(cToInt, bp, 2);
+		size = le2short(cToInt);
+		bp += 2;
+		data = malloc(size);
+		memcpy(data, bp, size);
+		utf16le_to_utf8(data, size, &attribute->name);
+		bp += size;
+		memcpy(cToInt, bp, 2);
+		type = le2short(cToInt);
+		attribute->type = type;
+		bp += 2;
+		memcpy(cToInt, bp, 2);
+		size = le2short(cToInt);
+		bp += 2;
+		data = realloc(data, size);
+		memcpy(data, bp, size);
+		switch(type)
+		{
+			/* Type 0 is Little-endian UTF16 */
+			case 0:
+				utf16le_to_utf8(data, size, &attribute->data);
+				break;
+			/*
+			 * Type 1 is binary
+			 * Type 2 is boolean
+			 * Type 3 is 32-bit integer (signed?)
+			 * Type 4 is double
+			 */
+			case 1:
+			case 2:
+			case 3:
+			case 4:
+			default:
+				attribute->data = malloc(size);
+				memcpy(attribute->data, data, size);
+		}
+		bp += size;
+		
+		wma->items[i] = attribute;		
+	}
+
+	free(tag_buffer);
+	
+	return wma;
+}
+
+int findWMA(VFSFile *fp)
+{
+	unsigned char *tag_buffer, *bp;
+	
+	tag_buffer = malloc(BUFFER_SIZE);
+	vfs_fread(tag_buffer, 1, BUFFER_SIZE, fp);
+	bp = tag_buffer;
+	if(memcmp(bp, WMA_GUID, 16))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	bp += 30;
+	if(memcmp(bp, CONTENT_GUID, 16))
+	{
+		free(tag_buffer);
+		return -1;
+	}
+	/*
+	 * It's stupid to reject if no Extended Content GUID
+	 * is found...  This code is in here in case though...
+	 *
+	bp += 16;
+	memcpy(cToInt, bp, 8);
+	size = le2long(cToInt);
+	bp += size - 16;
+	if(!memcmp(bp, EXTENDED_GUID, 16))
+	{
+		free(tag_buffer);
+		return 0;
+	}
+	*/
+	free(tag_buffer);
+	return bp - tag_buffer + 16;
+}
+
+wma_t *readWMA(char *filename)
+{
+	VFSFile *fp;
+	wma_t *wma;
+	int status;
+	
+	fp = vfs_fopen(filename, "r");
+
+	if(!fp)
+	{
+		pdebug("Couldn't open file!", META_DEBUG);
+		return NULL;
+	}
+	
+	vfs_fseek(fp, 0, SEEK_SET);
+	
+	pdebug("Searching for tag...", META_DEBUG);
+	status = findWMA(fp);
+	if(status == 0)
+	{
+		vfs_fclose(fp);
+		return NULL;
+	}
+	wma = readAttributes(fp, status);
+	
+	vfs_fclose(fp);
+	
+	return wma;
+}
+
+void freeWMA(wma_t *wma)
+{
+	int i;
+	
+	for(i = 0; i < wma->numitems; i++)
+	{
+		attribute_t *attribute;
+		
+		attribute = wma->items[i];
+		free(attribute->name);
+		free(attribute->data);
+		free(attribute);
+	}
+	free(wma->items);
+	free(wma);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Plugins/General/scrobbler/xmms_scrobbler.c	Sat Feb 25 12:11:20 2006 -0800
@@ -0,0 +1,493 @@
+#include <audacious/plugin.h>
+#include <libaudacious/configdb.h>
+#include <libaudacious/beepctrl.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <wchar.h>
+#include <sys/time.h>
+
+#include "tags/include/tags.h"
+#include "scrobbler.h"
+#include "gtkstuff.h"
+#include "config.h"
+#include "fmt.h"
+#include "tags/include/unicode.h"
+
+#define XS_CS xmms_scrobbler.xmms_session
+
+typedef struct submit_t
+{
+	int dosubmit, pos_c, len;
+} submit_t;
+
+static void init(void);
+static void cleanup(void);
+static void *xs_thread(void *);
+static void *hs_thread(void *);
+static int going;
+
+static GThread *pt_scrobbler;
+static GMutex *m_scrobbler;
+static GThread *pt_handshake;
+
+static GeneralPlugin xmms_scrobbler =
+{
+	NULL,
+	NULL,
+	-1,
+	NULL,
+	init,
+	about_show,
+	configure_dialog,
+	cleanup
+};
+
+static void init(void)
+{
+	char *username = NULL, *password = NULL;
+	ConfigDb *cfgfile;
+	going = 1;
+	GError **moo = NULL;
+
+	if ((cfgfile = bmp_cfg_db_open()) != NULL) {
+		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "username",
+				&username);
+		bmp_cfg_db_get_string(cfgfile, "audioscrobbler", "password",
+				&password);
+		bmp_cfg_db_close(cfgfile);
+	}
+	if ((!username || !password) || (!*username || !*password)) {
+		pdebug("username/password not found - not starting",
+			DEBUG);
+		going = 0;
+		return;
+	}
+	sc_init(username, password);
+	g_free(username);
+	g_free(password);
+	m_scrobbler = g_mutex_new();
+	if ((pt_scrobbler = g_thread_create(xs_thread, m_scrobbler, TRUE, moo)) == NULL) {
+		pdebug(fmt_vastr("Error creating scrobbler thread: %s", moo), DEBUG);
+		going = 0;
+		return;
+	}
+	init_errorbox_done();
+	if ((pt_handshake = g_thread_create(hs_thread, m_scrobbler, TRUE, NULL)) == NULL) {
+		pdebug("Error creating handshake thread", DEBUG);
+		going = 0;
+		return;
+	}
+	pdebug("plugin started", DEBUG);
+}
+
+static void cleanup(void)
+{
+	if (!going)
+		return;
+	pdebug("about to lock mutex", DEBUG);
+	g_mutex_lock(m_scrobbler);
+	pdebug("locked mutex", DEBUG);
+	going = 0;
+	g_mutex_unlock(m_scrobbler);
+	pdebug("joining threads", DEBUG);
+	g_thread_join(pt_scrobbler);
+
+	g_thread_join(pt_handshake);
+
+	sc_cleaner();
+}
+
+static char ishttp(const char *a)
+{
+	char *tmp, *bp;
+	int status = 0;
+
+	if (!a || !*a)
+		return 0;
+
+	tmp = strdup(a);
+	for (bp = tmp; *bp; bp++)
+		*bp = toupper(*bp);
+	if (strstr(tmp, "HTTP://"))
+		status = -1;
+	free(tmp);
+	return status;
+}
+
+/* Following code thanks to nosuke 
+ *
+ * It should probably be cleaned up
+ */
+static submit_t get_song_status(void)
+{
+	static int pos_c, playlistlen_c, playtime_c, time_c,
+		pos_p = 0, playlistlen_p = 0, playtime_p = 0,
+		playtime_i = 0, time_i = 0,
+		playtime_ofs = 0;
+	static char *file_c = NULL, *file_p = NULL; 
+
+	static enum playstatus {
+		ps_stop, ps_play, ps_pause
+	} ps_c, ps_p = ps_stop;
+
+	static int submitted = 0, changed, seeked, repeat,
+		filechanged, rewind, len = 0;
+
+	static enum state {
+		start, stop, pause, restart, playing, pausing, stopping
+	} playstate;
+
+	submit_t dosubmit;
+
+	struct timeval timetmp;
+
+	/* clear dosubmit */
+	dosubmit.dosubmit = dosubmit.pos_c = dosubmit.len = 0;
+
+	/* current music number */
+	pos_c = xmms_remote_get_playlist_pos(XS_CS);
+	/* current file name */
+	file_c = xmms_remote_get_playlist_file(XS_CS, pos_c); 
+	/* total number */
+	playlistlen_c = xmms_remote_get_playlist_length(XS_CS);
+	/* current playtime */
+	playtime_c = xmms_remote_get_output_time(XS_CS); 
+	/* total length */
+	len = xmms_remote_get_playlist_time(XS_CS, pos_c); 
+
+	/* current time (ms) */
+	gettimeofday(&timetmp, NULL);
+	time_c = timetmp.tv_sec * 1000 + timetmp.tv_usec / 1000; 
+
+	/* current status */
+	if( xmms_remote_is_paused(XS_CS) ) {
+		ps_c = ps_pause;
+	}else if( xmms_remote_is_playing(XS_CS) ) {
+		ps_c = ps_play;
+	}else{
+		ps_c = ps_stop;
+	}
+
+	/* repeat setting */
+	repeat = xmms_remote_is_repeat(XS_CS);
+
+	/*
+#ifdef MAKE_XMMS
+	// advance setting (required xmms-1.2.11 or over)
+	advance = xmms_remote_is_advance(XS_CS);
+#else
+	advance = 1;
+#endif
+	*/
+
+	if( ps_p == ps_stop && ps_c == ps_stop )        playstate = stopping;
+	else if( ps_p == ps_stop && ps_c == ps_play )   playstate = start;
+	/* else if( ps_p == ps_stop && ps_c == ps_pause ) ; */
+	else if( ps_p == ps_play && ps_c == ps_play )   playstate = playing;
+	else if( ps_p == ps_play && ps_c == ps_stop )   playstate = stop;
+	else if( ps_p == ps_play && ps_c == ps_pause )  playstate = pause;
+	else if( ps_p == ps_pause && ps_c == ps_pause ) playstate = pausing;
+	else if( ps_p == ps_pause && ps_c == ps_play )  playstate = restart;
+	else if( ps_p == ps_pause && ps_c == ps_stop )  playstate = stop;
+	else playstate = stopping;
+
+	/* filename has changed */
+	if( !(file_p == NULL && file_c == NULL) &&
+	    ((file_p == NULL && file_c != NULL) ||
+		 (file_p != NULL && file_c == NULL) ||
+		 (file_p != NULL && file_c != NULL && strcmp(file_c, file_p))) ){
+		filechanged = 1;
+		pdebug("*** filechange ***", SUB_DEBUG);
+	}else{
+		filechanged = 0;
+	}
+	if( file_c == NULL ){ len = 0; }
+
+	/* whole rewind has occurred (maybe) */
+	if( len != 0 && len - (playtime_p - playtime_c) < 3000 ){
+		rewind = 1;
+		pdebug("*** rewind ***", SUB_DEBUG);
+	}else{
+		rewind = 0;
+	}
+
+
+	changed = 0;
+	seeked = 0;
+
+	switch( playstate ){
+	case start:
+	  pdebug("*** START ***", SUB_DEBUG);
+	  break;
+	case stop:
+	  pdebug("*** STOP ***", SUB_DEBUG);
+	  len = 0;
+	  break;
+	case pause: 
+	  pdebug("*** PAUSE ***", SUB_DEBUG);
+	  playtime_ofs += playtime_c - playtime_i; /* save playtime */
+	  break;
+	case restart: 
+	  pdebug("*** RESTART ***", SUB_DEBUG);
+	  playtime_i  = playtime_c; /* restore playtime */
+	  break;
+	case playing:
+	  if( (playtime_c < playtime_p) || /* back */
+		  ( (playtime_c - playtime_i) - (time_c - time_i) > 3000 )
+	          /* forward */
+		  ) { 
+		seeked = 1;
+	  }
+
+	  if( filechanged || /* filename has changed */
+		  ( !filechanged && /* filename has not changed... */
+			/* (( rewind && (repeat && (!advance ||
+	                   (pos_c == 0 && playlistlen_c == 1 )))) || */
+			/* looping with only one file */
+			(( pos_c == 0 && playlistlen_c == 1 && repeat
+				&& rewind ) || 
+			 /* looping? */
+			 ( pos_p == pos_c && rewind ) ||
+			 
+			 ( pos_p != pos_c && seeked ) ||
+			 /* skip from current music to next music, 
+			    which has the same filename as previous one */
+			 ( pos_p < pos_c && playtime_c < playtime_p ) || 
+			 /* current song has removed from playlist 
+			    but the next (following) song has the same
+	                    filename */
+			 ( playlistlen_p > playlistlen_c
+				&& playtime_c < playtime_p )))){
+		pdebug("*** CHANGE ***",SUB_DEBUG);
+		pdebug(fmt_vastr(" filechanged = %d",filechanged),SUB_DEBUG);
+		pdebug(fmt_vastr(" pos_c = %d",pos_c),SUB_DEBUG);
+		pdebug(fmt_vastr(" pos_p = %d",pos_p),SUB_DEBUG);
+		pdebug(fmt_vastr(" rewind = %d", rewind),SUB_DEBUG);
+		pdebug(fmt_vastr(" seeked = %d", seeked),SUB_DEBUG);
+		pdebug(fmt_vastr(" playtime_c = %d", playtime_c),SUB_DEBUG);
+		pdebug(fmt_vastr(" playtime_p = %d", playtime_p),SUB_DEBUG);
+		pdebug(fmt_vastr(" playlistlen_c = %d", playlistlen_p),
+			SUB_DEBUG);
+		pdebug(fmt_vastr(" playlistlen_p = %d", playlistlen_p),
+			SUB_DEBUG);
+		changed = 1;
+		seeked = 0;
+	  }else if( seeked ) { 
+		seeked = 1;
+		pdebug("*** SEEK ***", SUB_DEBUG);
+	  }
+
+	  break;
+	case pausing: 
+	  if(playtime_c != playtime_p){
+		pdebug("*** SEEK ***", SUB_DEBUG);
+		seeked = 1;
+	  }
+	  break;
+	case stopping:
+	  len = 0;
+	  break;
+	default:
+	  pdebug("*** unknown state tranfer!!! ***", SUB_DEBUG);
+	  break;
+	}
+
+	
+	if( playstate == start || changed || (seeked && !submitted) ){
+	  /* reset counter */
+	  pdebug(" <<< reset counter >>>", SUB_DEBUG);
+
+	  submitted = 0;
+	  playtime_ofs = 0;
+	  playtime_i = playtime_c;
+	  time_i = time_c;
+
+	}else{
+	  /* check playtime for submitting */
+	  if( !submitted ){
+		if( len > 30 * 1000 &&
+			/* len < 30 *60 * 1000 &&  // crazy rule!!! */
+			( 
+			 (playtime_ofs + playtime_c - playtime_i > len / 2) ||
+			 (playtime_ofs + playtime_c - playtime_i > 240 * 1000) 
+			 /* (playtime_c - playtime_i > 10 * 1000)// for debug */
+			 )){
+		  pdebug("*** submitting requirements are satisfied.",
+			SUB_DEBUG);
+		  pdebug(fmt_vastr("    len = %d, playtime = %d",
+			len / 1000, (playtime_c - playtime_i)/1000 ),
+			SUB_DEBUG);
+		  submitted = 1;
+		  dosubmit.dosubmit = 1;
+	          dosubmit.pos_c = pos_c;
+	          dosubmit.len = len;
+		}
+	  }
+	}
+
+	/* keep current value for next iteration */
+	ps_p = ps_c;
+	file_p = file_c;
+	playtime_p = playtime_c;
+	pos_p = pos_c;
+	playlistlen_p = playlistlen_c;
+
+	return dosubmit;
+}
+
+static void *xs_thread(void *data)
+{
+	int run = 1, i;
+	char *charpos, *dirname;
+	gboolean direxists;
+	submit_t dosubmit;
+	
+	while (run) {
+		/* Error catching */
+		if(sc_catch_error())
+		{
+			errorbox_show(sc_fetch_error());
+			if(get_errorbox_done())
+			{
+				init_errorbox_done();
+				sc_clear_error();
+			}
+		}
+
+		/* Check for ability to submit */
+		dosubmit = get_song_status();
+
+		if(dosubmit.dosubmit) {
+			char *fname, /**title, *artist,*/ *tmp = NULL; /**sep*/
+			int track = 0;
+			metatag_t *meta;
+
+			pdebug("Submitting song.", DEBUG);
+			
+			meta = metatag_new();
+
+			fname = xmms_remote_get_playlist_file(0,dosubmit.pos_c);
+			if (ishttp(fname)) {
+				g_free(fname);
+				continue;
+			}
+			charpos = strrchr(fname, '.');
+			if(charpos != NULL &&
+				!fmt_strncasecmp(charpos + 1, "cda", 3))
+			{
+				ConfigDb *cfgfile;
+
+				if ((cfgfile = bmp_cfg_db_open())
+					!= NULL)
+				{
+					char *direntry = calloc(32, 1);
+
+					dirname = fname;
+					tmp = strrchr(fname, '.');
+					*tmp = '\0';
+					track = (char)atoi(tmp - 2);
+					pdebug(fmt_vastr("Track: %d", track),
+						DEBUG);
+					tmp = strrchr(dirname, '/');
+					*(tmp + 1) = '\0';
+					direxists = bmp_cfg_db_get_string(
+						cfgfile, "CDDA",
+						"directory", &fname);
+					for(i = 0; direxists == TRUE
+						&& strcmp(dirname, fname) == 0;)
+					{
+						i++;
+						snprintf(direntry, 31,
+							"directory%d", i);
+						g_free(fname);
+						direxists =
+							bmp_cfg_db_get_string(
+							  cfgfile, "CDDA",
+							  direntry, &fname);
+					}
+					if(i > 0)
+					{
+						snprintf(direntry, 31,
+							"device%d", i);
+					}
+					else
+					{
+						snprintf(direntry, 31,
+							"device");
+					}
+					g_free(fname);
+					bmp_cfg_db_get_string(cfgfile, "CDDA",
+						direntry, &fname);
+					bmp_cfg_db_close(cfgfile);
+					free(direntry);
+					pdebug(fmt_vastr("CD Device: %s",
+						fname), DEBUG);
+				}
+			}
+
+			pdebug(fmt_vastr("get_tag_data, %s", fname), DEBUG);
+			get_tag_data(meta, fname, track);
+
+			if(meta->artist != NULL && meta->title != NULL)
+			{
+				pdebug(fmt_vastr(
+					"submitting artist: %s, title: %s",
+					meta->artist, meta->title), DEBUG);
+				sc_addentry(m_scrobbler, meta,
+					dosubmit.len/1000);
+			}
+			else
+				pdebug("couldn't determine artist - "
+						"title, not submitting",
+						DEBUG);
+			/* g_free(tmp); */
+			g_free(fname);
+			metatag_delete(meta);
+		}
+		g_mutex_lock(m_scrobbler);
+		run = going;
+		g_mutex_unlock(m_scrobbler);
+		usleep(100000);
+	}
+	pdebug("scrobbler thread: exiting", DEBUG);
+	g_thread_exit(NULL);
+
+	return NULL;
+}
+
+static void *hs_thread(void *data)
+{
+	int run = 1;
+	
+	while(run)
+	{
+		if(sc_idle(m_scrobbler))
+		{
+			pdebug("Giving up due to fatal error", DEBUG);
+			g_mutex_lock(m_scrobbler);
+			going = 0;
+			g_mutex_lock(m_scrobbler);
+		}
+		g_mutex_lock(m_scrobbler);
+		run = going;
+		g_mutex_unlock(m_scrobbler);
+		sleep(1);
+	}
+	pdebug("handshake thread: exiting", DEBUG);
+	g_thread_exit(NULL);
+
+	return NULL;
+}
+
+GeneralPlugin *get_gplugin_info(void)
+{
+	xmms_scrobbler.description = g_strdup_printf(_("Scrobbler Plugin"));
+	return &xmms_scrobbler;
+}
--- a/configure.ac	Sat Feb 25 09:36:36 2006 -0800
+++ b/configure.ac	Sat Feb 25 12:11:20 2006 -0800
@@ -728,6 +728,100 @@
     ;;
 esac
 
+dnl *** Scrobbler ***
+
+scrobbler="yes"
+
+dnl Checks for libmusicbrainz
+AC_CHECK_LIB(musicbrainz, mb_GetVersion,,
+             echo "*"
+             echo "*  The MusicBrainz client library needs to be installed "
+             echo "*  to build this plugin. Please download the library from "
+             echo "*  http://www.musicbrainz.org/download "
+	     echo "*"
+	     echo "*  Scrobbler support will not be built."
+             echo "*"
+             scrobbler="no"
+             ,-lstdc++)
+
+OLD_LIBS="$LIBS"
+LIBS="-lmusicbrainz -lstdc++"
+AC_TRY_RUN([
+#include <musicbrainz/mb_c.h>
+
+int main ()
+{
+  int           major, minor, rev;
+  musicbrainz_t o;
+
+  o = mb_New();
+  mb_GetVersion(o, &major, &minor, &rev);
+  mb_Delete(o);
+  if (major >= 2)
+  {
+     return 0;
+  }
+
+  return -1;
+}
+],have_mbver=true,have_mbver=false,have_mbver=false)
+
+AC_MSG_CHECKING([for version >= 2.0.0 in -lmusicbrainz])
+if test "$have_mbver" = "false"; then
+    AC_MSG_RESULT([no])
+    echo "*"
+    echo "*  Version 2.0.0 or higher of the MusicBrainz "
+    echo "*  client library needs to be installed to build this application. "
+    echo "*  Please download the library from: "
+    echo "*  http://musicbrainz.org/products/client/download.html "
+    echo "*"
+    echo "*  Scrobbler support will not be built."
+    echo "*"
+    scrobbler="no"
+else
+    AC_MSG_RESULT([yes])
+    MUSICBRAINZ_LIBS="-lmusicbrainz -lstdc++"
+fi
+LIBS="$OLD_LIBS"
+
+dnl libcurl check
+
+my_cv_curl_vers=NONE
+dnl check is the plain-text version of the required version
+check="7.9.7"
+dnl check_hex must be UPPERCASE if any hex letters are present
+check_hex="070907"
+
+AC_MSG_CHECKING([for curl >= $check])
+
+if eval curl-config --version 2>/dev/null >/dev/null; then
+        ver=`curl-config --version | sed -e "s/libcurl //g"`
+        hex_ver=`curl-config --vernum | tr 'a-f' 'A-F'`
+        ok=`echo "ibase=16; if($hex_ver>=$check_hex) $hex_ver else 0" | bc`
+
+        if test x$ok != x0; then
+                my_cv_curl_vers="$ver"
+                AC_MSG_RESULT([$my_cv_curl_vers])
+		CURL_LIBS=`curl-config --libs`
+		CURL_CFLAGS=`curl-config --cflags`
+        else
+                AC_MSG_RESULT([no])
+                AC_MSG_WARN([$ver is too old. Need version $check or higher. Scrobbler support will not be built.])
+        fi
+else
+        AC_MSG_RESULT([no])
+        AC_MSG_WARN([curl-config was not found. Scrobbler support will not be built.])
+fi
+
+if test "x$scrobbler" = "xyes"; then
+	GENERAL_PLUGINS="$GENERAL_PLUGINS scrobbler"
+fi
+
+AC_SUBST(CURL_CFLAGS)
+AC_SUBST(CURL_LIBS)
+AC_SUBST(MUSICBRAINZ_LIBS)
+
+dnl *** End of Scrobbler checks ***
 
 AC_SUBST(ARCH_DEFINES)
 AM_CONDITIONAL(ARCH_X86, test "x$arch_type" = "xix86")
@@ -773,9 +867,6 @@
 
 AC_SUBST(BEEP_DEFINES)
 
-PLUGIN_LDFLAGS='-module -avoid-version -export-symbols-regex "get_.plugin_info"'
-AC_SUBST(PLUGIN_LDFLAGS)
-
 AC_SUBST(beepdir)
 AC_SUBST(plugindir)
 
@@ -835,6 +926,8 @@
 	Plugins/General/song_change/Makefile
 	Plugins/General/lirc/Makefile
 	Plugins/General/inetctl/Makefile
+	Plugins/General/scrobbler/Makefile
+	Plugins/General/scrobbler/tags/Makefile
 	Plugins/Effect/Makefile
 	Plugins/Effect/ladspa/Makefile
         po/Makefile.in
@@ -903,6 +996,7 @@
 echo "  Song Change:                            yes"
 echo "  Internet Control:                       yes"
 echo "  LIRC:                                   $have_lirc"
+echo "  AudioScrobbler Client:                  $scrobbler"
 echo
 echo "  Effect"
 echo "  ------"
--- a/mk/rules.mk.in	Sat Feb 25 09:36:36 2006 -0800
+++ b/mk/rules.mk.in	Sat Feb 25 12:11:20 2006 -0800
@@ -23,13 +23,6 @@
 build_triplet = @build@
 host_triplet = @host@
 target_triplet = @target@
-DIST_COMMON = README $(am__configure_deps) $(srcdir)/Makefile.am \
-	$(srcdir)/Makefile.in $(srcdir)/audacious.1.in \
-	$(srcdir)/audacious.pc.in $(srcdir)/audacious.spec.in \
-	$(srcdir)/config.h.in $(top_srcdir)/configure \
-	$(top_srcdir)/intl/Makefile.in ABOUT-NLS AUTHORS COPYING \
-	ChangeLog INSTALL NEWS compile config.guess config.rpath \
-	config.sub depcomp install-sh ltmain.sh missing mkinstalldirs
 subdir = .
 ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
 CONFIG_HEADER = config.h
@@ -37,12 +30,6 @@
 	intl/Makefile
 SOURCES =
 DIST_SOURCES =
-RECURSIVE_TARGETS = all-recursive check-recursive dvi-recursive \
-	html-recursive info-recursive install-data-recursive \
-	install-exec-recursive install-info-recursive \
-	install-recursive installcheck-recursive installdirs-recursive \
-	pdf-recursive ps-recursive uninstall-info-recursive \
-	uninstall-recursive
 man1dir = $(mandir)/man1
 am__installdirs = "$(DESTDIR)$(man1dir)" "$(DESTDIR)$(pkgconfigdir)"
 NROFF = nroff
@@ -312,14 +299,6 @@
 target_cpu = @target_cpu@
 target_os = @target_os@
 target_vendor = @target_vendor@
-ACLOCAL_AMFLAGS = -I m4
-SUBDIRS = intl libaudacious audacious Plugins po icons skin
-man_MANS = audacious.1
-pkgconfigdir = $(libdir)/pkgconfig
-pkgconfig_DATA = audacious.pc
-EXTRA_DIST = \
-  FAQ \
-  audacious.spec \
-  README.bmp \
-  $(pkgconfig_DATA) \
-  $(wildcard m4/*.m4)
+CURL_CFLAGS = @CURL_CFLAGS@
+CURL_LIBS = @CURL_LIBS@
+MUSICBRAINZ_LIBS = @MUSICBRAINZ_LIBS@