From 69635022133488e6b19569fb59b16c4658a244eb Mon Sep 17 00:00:00 2001
From: Eric Wong <normalperson@yhbt.net>
Date: Thu, 16 Mar 2006 06:52:46 +0000
Subject: merge with mpd/trunk up to r3925

git-svn-id: https://svn.musicpd.org/mpd/trunk@3926 09075e82-0dd4-0310-85a5-a0d7c8717e4f
---
 src/inputPlugins/_flac_common.c     | 195 +++++++++++++++++
 src/inputPlugins/_flac_common.h     |  77 +++++++
 src/inputPlugins/_ogg_common.c      |  65 ++++++
 src/inputPlugins/_ogg_common.h      |  35 +++
 src/inputPlugins/aac_plugin.c       |   2 +
 src/inputPlugins/audiofile_plugin.c |   2 +
 src/inputPlugins/flac_plugin.c      | 258 +++-------------------
 src/inputPlugins/mod_plugin.c       |   2 +
 src/inputPlugins/mp3_plugin.c       |   2 +
 src/inputPlugins/mp4_plugin.c       |   2 +
 src/inputPlugins/mpc_plugin.c       |   2 +
 src/inputPlugins/ogg_plugin.c       | 420 -----------------------------------
 src/inputPlugins/oggflac_plugin.c   | 426 ++++++++++++++++++++++++++++++++++++
 src/inputPlugins/oggvorbis_plugin.c | 426 ++++++++++++++++++++++++++++++++++++
 14 files changed, 1268 insertions(+), 646 deletions(-)
 create mode 100644 src/inputPlugins/_flac_common.c
 create mode 100644 src/inputPlugins/_flac_common.h
 create mode 100644 src/inputPlugins/_ogg_common.c
 create mode 100644 src/inputPlugins/_ogg_common.h
 delete mode 100644 src/inputPlugins/ogg_plugin.c
 create mode 100644 src/inputPlugins/oggflac_plugin.c
 create mode 100644 src/inputPlugins/oggvorbis_plugin.c

(limited to 'src/inputPlugins')

diff --git a/src/inputPlugins/_flac_common.c b/src/inputPlugins/_flac_common.c
new file mode 100644
index 000000000..f6a470ed2
--- /dev/null
+++ b/src/inputPlugins/_flac_common.c
@@ -0,0 +1,195 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * Common data structures and functions used by FLAC and OggFLAC
+ * (c) 2005 by Eric Wong <normalperson@yhbt.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../inputPlugin.h"
+
+#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC)
+
+#include "_flac_common.h"
+
+#include "../log.h"
+#include "../tag.h"
+#include "../inputStream.h"
+#include "../outputBuffer.h"
+#include "../decode.h"
+#include "../replayGain.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <FLAC/format.h>
+#include <FLAC/metadata.h>
+
+void init_FlacData (FlacData * data, OutputBuffer * cb,
+		DecoderControl * dc, InputStream * inStream)
+{
+	data->chunk_length = 0;
+	data->time = 0;
+	data->position = 0;
+	data->bitRate = 0;
+	data->cb = cb;
+	data->dc = dc;
+	data->inStream = inStream;
+	data->replayGainInfo = NULL;
+	data->tag = NULL;
+}
+
+static int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block,
+		char * cmnt, float * fl)
+{
+	int offset = FLAC__metadata_object_vorbiscomment_find_entry_from(
+			block,0,cmnt);
+
+	if(offset >= 0) {
+		size_t pos = strlen(cmnt)+1; /* 1 is for '=' */
+		int len = block->data.vorbis_comment.comments[offset].length
+				-pos;
+		if(len > 0) {
+			unsigned char tmp;
+			unsigned char * dup = &(block->data.vorbis_comment.
+						comments[offset].entry[pos]);
+			tmp = dup[len];
+			dup[len] = '\0';
+			*fl = atof((char *)dup);
+			dup[len] = tmp;
+			
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+/* replaygain stuff by AliasMrJones */
+static void flacParseReplayGain(const FLAC__StreamMetadata *block,
+				FlacData * data) {
+	unsigned int found = 0;
+
+	if (data->replayGainInfo)
+		freeReplayGainInfo(data->replayGainInfo);
+
+	data->replayGainInfo = newReplayGainInfo();
+
+	found &= flacFindVorbisCommentFloat(block,"replaygain_album_gain",
+					&data->replayGainInfo->albumGain);
+	found &= flacFindVorbisCommentFloat(block,"replaygain_album_peak",
+					&data->replayGainInfo->albumPeak);
+	found &= flacFindVorbisCommentFloat(block,"replaygain_track_gain",
+					&data->replayGainInfo->trackGain);
+	found &= flacFindVorbisCommentFloat(block,"replaygain_track_peak",
+					&data->replayGainInfo->trackPeak);
+
+	if (!found) {
+		freeReplayGainInfo(data->replayGainInfo);
+		data->replayGainInfo = NULL;
+	}
+}
+
+/* tracknumber is used in VCs, MPD uses "track" ..., all the other
+ * tag names match */
+static const char * VORBIS_COMMENT_TRACK_KEY = "tracknumber";
+
+static unsigned int commentMatchesAddToTag(
+		const FLAC__StreamMetadata_VorbisComment_Entry * entry,
+		unsigned int itemType,
+		MpdTag ** tag)
+{
+	const char * str = (itemType == TAG_ITEM_TRACK) ?
+			VORBIS_COMMENT_TRACK_KEY : mpdTagItemKeys[itemType];
+	size_t slen = strlen(str);
+	int vlen = entry->length - slen - 1;
+
+	if ((vlen > 0) && (0 == strncasecmp(str,(char *)entry->entry, slen))
+				&& (*(entry->entry + slen) == '=')) {
+		if (!*tag)
+			*tag = newMpdTag();
+		
+		addItemToMpdTagWithLen(*tag, itemType, 
+				(char *)(entry->entry+slen + 1), vlen);
+		
+		return 1;
+	}
+	
+	return 0;
+}
+		
+MpdTag * copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, 
+		MpdTag * tag)
+{
+	unsigned int i, j;
+	FLAC__StreamMetadata_VorbisComment_Entry *comments;
+	
+	comments = block->data.vorbis_comment.comments;
+	
+	for (i = block->data.vorbis_comment.num_comments; i != 0; --i) {
+		for (j = TAG_NUM_OF_ITEM_TYPES; j--; ) {
+			if (commentMatchesAddToTag(comments, j, &tag))
+				break;
+		}
+		comments++;
+	}
+
+	return tag;
+}
+
+void flac_metadata_common_cb(const FLAC__StreamMetadata *block, FlacData *data)
+{
+	DecoderControl *dc = data->dc;
+	const FLAC__StreamMetadata_StreamInfo *si = &(block->data.stream_info);
+
+	switch(block->type) {
+	case FLAC__METADATA_TYPE_STREAMINFO:
+		dc->audioFormat.bits = si->bits_per_sample;
+		dc->audioFormat.sampleRate = si->sample_rate;
+		dc->audioFormat.channels = si->channels;
+		dc->totalTime = ((float)si->total_samples) / (si->sample_rate);
+		getOutputAudioFormat(&(dc->audioFormat),
+				&(data->cb->audioFormat));
+		break;
+	case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+		flacParseReplayGain(block,data);
+        default:
+                break;
+	}
+}
+
+void flac_error_common_cb(	const char * plugin,
+				const FLAC__StreamDecoderErrorStatus status,
+				FlacData *data)
+{
+	if(data->dc->stop) return;
+
+	switch(status) {
+	case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
+		ERROR("%s lost sync\n", plugin);
+		break;
+	case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
+		ERROR("bad %s header\n", plugin);
+		break;
+	case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
+		ERROR("%s crc mismatch\n", plugin);
+		break;
+	default:
+		ERROR("unknown %s error\n",plugin);
+	}
+}
+
+#endif /* HAVE_FLAC || HAVE_OGGFLAC */
diff --git a/src/inputPlugins/_flac_common.h b/src/inputPlugins/_flac_common.h
new file mode 100644
index 000000000..830d3bfc0
--- /dev/null
+++ b/src/inputPlugins/_flac_common.h
@@ -0,0 +1,77 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * Common data structures and functions used by FLAC and OggFLAC
+ * (c) 2005 by Eric Wong <normalperson@yhbt.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _FLAC_COMMON_H
+#define _FLAC_COMMON_H
+
+#include "../inputPlugin.h"
+
+#if defined(HAVE_FLAC) || defined(HAVE_OGGFLAC)
+
+#include "../tag.h"
+#include "../inputStream.h"
+#include "../outputBuffer.h"
+#include "../decode.h"
+#include <FLAC/seekable_stream_decoder.h>
+#include <FLAC/metadata.h>
+
+#define FLAC_CHUNK_SIZE 4080
+
+typedef struct {
+	unsigned char chunk[FLAC_CHUNK_SIZE];
+	int chunk_length;
+	float time;
+	int bitRate;
+	FLAC__uint64 position;
+	OutputBuffer * cb;
+	DecoderControl * dc;
+	InputStream * inStream;
+	ReplayGainInfo * replayGainInfo;
+	MpdTag * tag;
+} FlacData;
+
+/* initializes a given FlacData struct */
+void init_FlacData (FlacData * data, OutputBuffer * cb,
+		DecoderControl * dc, InputStream * inStream);
+void flac_metadata_common_cb(	const FLAC__StreamMetadata *block,
+				FlacData *data);
+void flac_error_common_cb(	const char * plugin,
+				FLAC__StreamDecoderErrorStatus status,
+				FlacData *data);
+
+MpdTag * copyVorbisCommentBlockToMpdTag(const FLAC__StreamMetadata * block, 
+		MpdTag * tag);
+
+static inline int flacSendChunk(FlacData * data)
+{
+	if (sendDataToOutputBuffer(data->cb, NULL, data->dc, 1, data->chunk,
+			data->chunk_length, data->time, data->bitRate,
+			data->replayGainInfo) == OUTPUT_BUFFER_DC_STOP)
+		return -1;
+
+	return 0;
+}
+
+
+#endif /* HAVE_FLAC || HAVE_OGGFLAC */
+
+#endif /* _FLAC_COMMON_H */
+
diff --git a/src/inputPlugins/_ogg_common.c b/src/inputPlugins/_ogg_common.c
new file mode 100644
index 000000000..fb0bb19e0
--- /dev/null
+++ b/src/inputPlugins/_ogg_common.c
@@ -0,0 +1,65 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ * (c) 2005 by Eric Wong <normalperson@yhbt.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../inputPlugin.h"
+
+#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS)
+
+#include "../utils.h"
+#include "_ogg_common.h"
+
+#include <string.h>
+
+ogg_stream_type ogg_stream_type_detect(InputStream * inStream)
+{
+	/* oggflac detection based on code in ogg123 and this post
+	 * http://lists.xiph.org/pipermail/flac/2004-December/000393.html
+	 * ogg123 trunk still doesn't have this patch as of June 2005 */
+	unsigned char buf[41];
+	size_t r, to_read = 41;
+
+	seekInputStream(inStream, 0, SEEK_SET);
+	
+	while (to_read) {
+		r = readFromInputStream(inStream, buf, 1, to_read);
+		if (r < 0)
+			break;
+		to_read -= r;
+		if (!r && !inputStreamAtEOF(inStream)) 
+			my_usleep(10000);
+		else
+			break;
+	}
+	
+	seekInputStream(inStream, 0, SEEK_SET);
+	
+	if (r >= 32 && memcmp(buf, "OggS", 4) == 0 && (
+				(memcmp(buf+29, "FLAC", 4) == 0
+					&& memcmp(buf+37, "fLaC", 4) == 0)
+				|| (memcmp(buf+28, "FLAC", 4) == 0)
+				|| (memcmp(buf+28, "fLaC", 4) == 0))) {
+		return FLAC;
+	}
+	return VORBIS;
+}
+
+#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */
+
diff --git a/src/inputPlugins/_ogg_common.h b/src/inputPlugins/_ogg_common.h
new file mode 100644
index 000000000..4cd7fda41
--- /dev/null
+++ b/src/inputPlugins/_ogg_common.h
@@ -0,0 +1,35 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * Common functions used for Ogg data streams (Ogg-Vorbis and OggFLAC)
+ * (c) 2005 by Eric Wong <normalperson@yhbt.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _OGG_COMMON_H
+#define _OGG_COMMON_H
+
+#include "../inputPlugin.h"
+
+#if defined(HAVE_OGGFLAC) || defined(HAVE_OGGVORBIS)
+
+typedef enum _ogg_stream_type { VORBIS, FLAC } ogg_stream_type;
+
+ogg_stream_type ogg_stream_type_detect(InputStream * inStream);
+
+#endif /* defined(HAVE_OGGFLAC || defined(HAVE_OGGVORBIS) */
+
+#endif /* _OGG_COMMON_H */
diff --git a/src/inputPlugins/aac_plugin.c b/src/inputPlugins/aac_plugin.c
index e368f3089..3fb5f4742 100644
--- a/src/inputPlugins/aac_plugin.c
+++ b/src/inputPlugins/aac_plugin.c
@@ -418,6 +418,7 @@ InputPlugin aacPlugin =
 {
         "aac",
         NULL,
+        NULL,
 	NULL,
 	NULL,
         aac_decode,
@@ -435,6 +436,7 @@ InputPlugin aacPlugin =
         NULL,
         NULL,
         NULL,
+        NULL,
 	NULL,
 	NULL,
         0,
diff --git a/src/inputPlugins/audiofile_plugin.c b/src/inputPlugins/audiofile_plugin.c
index adc279b60..d212eaf47 100644
--- a/src/inputPlugins/audiofile_plugin.c
+++ b/src/inputPlugins/audiofile_plugin.c
@@ -163,6 +163,7 @@ InputPlugin audiofilePlugin =
         NULL,
 	NULL,
 	NULL,
+	NULL,
         audiofile_decode,
         audiofileTagDup,
         INPUT_PLUGIN_STREAM_FILE,
@@ -176,6 +177,7 @@ InputPlugin audiofilePlugin =
 {
         NULL,
         NULL,
+	NULL,
         NULL,
         NULL,
 	NULL,
diff --git a/src/inputPlugins/flac_plugin.c b/src/inputPlugins/flac_plugin.c
index 455394888..242dc6f22 100644
--- a/src/inputPlugins/flac_plugin.c
+++ b/src/inputPlugins/flac_plugin.c
@@ -20,6 +20,8 @@
 
 #ifdef HAVE_FLAC
 
+#include "_flac_common.h"
+
 #include "../utils.h"
 #include "../log.h"
 #include "../pcm_utils.h"
@@ -34,22 +36,8 @@
 #include <FLAC/seekable_stream_decoder.h>
 #include <FLAC/metadata.h>
 
-typedef struct {
-#define FLAC_CHUNK_SIZE 4080
-	unsigned char chunk[FLAC_CHUNK_SIZE];
-	int chunk_length;
-	float time;
-	int bitRate;
-	FLAC__uint64 position;
-	OutputBuffer * cb;
-	DecoderControl * dc;
-        InputStream * inStream;
-	ReplayGainInfo * replayGainInfo;
-} FlacData;
-
 /* this code is based on flac123, from flac-tools */
 
-int flacSendChunk(FlacData * data);
 void flacError(const FLAC__SeekableStreamDecoder *, 
                 FLAC__StreamDecoderErrorStatus, void *);
 void flacPrintErroredState(FLAC__SeekableStreamDecoderState state);
@@ -75,14 +63,7 @@ int flac_decode(OutputBuffer * cb, DecoderControl * dc, InputStream * inStream)
 	int status = 1;
         int ret =0;
 
-	data.chunk_length = 0;
-	data.time = 0;
-	data.position = 0;
-	data.bitRate = 0;
-	data.cb = cb;
-	data.dc = dc;
-	data.inStream = inStream;
-	data.replayGainInfo = NULL;
+	init_FlacData(&data, cb, dc, inStream);
 
 	if(!(flacDec = FLAC__seekable_stream_decoder_new())) {
                 ret = -1;
@@ -176,19 +157,16 @@ int flac_decode(OutputBuffer * cb, DecoderControl * dc, InputStream * inStream)
                 dc->seek = 0;
         } */
 	
-	if(dc->stop) {
-		dc->state = DECODE_STATE_STOP;
-		dc->stop = 0;
-	}
-	else dc->state = DECODE_STATE_STOP;
+	dc->state = DECODE_STATE_STOP;
+	dc->stop = 0;
 
 fail:
 	if(data.replayGainInfo) freeReplayGainInfo(data.replayGainInfo);
 
-	closeInputStream(inStream);
-	
 	if(flacDec) FLAC__seekable_stream_decoder_delete(flacDec);
 
+	closeInputStream(inStream);
+	
 	return ret;
 }
 
@@ -250,35 +228,17 @@ FLAC__SeekableStreamDecoderLengthStatus flacLength(
 }
 
 FLAC__bool flacEOF(const FLAC__SeekableStreamDecoder * flacDec, void * fdata) {
-	FlacData * data = (FlacData *) fdata;
-
-        switch(inputStreamAtEOF(data->inStream)) {
-        case 1:
+ 	FlacData * data = (FlacData *) fdata;
+	
+	if (inputStreamAtEOF(data->inStream) == 1)
                 return true;
-        default:
-                return false;
-        }
+	return false;
 }
 
 void flacError(const FLAC__SeekableStreamDecoder *dec, 
                 FLAC__StreamDecoderErrorStatus status, void *fdata) 
 {
-	FlacData * data = (FlacData *) fdata;
-	if(data->dc->stop) return;
-
-	switch(status) {
-	case FLAC__STREAM_DECODER_ERROR_STATUS_LOST_SYNC:
-		ERROR("flac lost sync\n");
-		break;
-	case FLAC__STREAM_DECODER_ERROR_STATUS_BAD_HEADER:
-		ERROR("bad header\n");
-		break;
-	case FLAC__STREAM_DECODER_ERROR_STATUS_FRAME_CRC_MISMATCH:
-		ERROR("crc mismatch\n");
-		break;
-	default:
-		ERROR("unknown flac error\n");
-	}
+	flac_error_common_cb("flac",status,(FlacData *) fdata);
 }
 
 void flacPrintErroredState(FLAC__SeekableStreamDecoderState state) 
@@ -312,93 +272,10 @@ void flacPrintErroredState(FLAC__SeekableStreamDecoderState state)
 	}
 }
 
-int flacFindVorbisCommentFloat(const FLAC__StreamMetadata * block, char * cmnt, 
-                float * fl)
-{
-        int offset = FLAC__metadata_object_vorbiscomment_find_entry_from(
-                        block,0,cmnt);
-
-        if(offset >= 0) {
-                int pos = strlen(cmnt)+1; /* 1 is for '=' */
-                int len = block->data.vorbis_comment.comments[offset].length
-                                -pos;
-                if(len > 0) {
-                        char * dup = malloc(len+1);
-                        memcpy(dup,&(block->data.vorbis_comment.comments[offset].entry[pos]),len);
-                        dup[len] = '\0';
-                        *fl = atof(dup);
-                        free(dup);
-                        return 1;
-                }
-        }
-
-        return 0;
-}
-
-/* replaygain stuff by AliasMrJones */
-void flacParseReplayGain(const FLAC__StreamMetadata *block, FlacData * data) {
-	int found = 0;
-
-	if(NULL != data->replayGainInfo) {
-		freeReplayGainInfo(data->replayGainInfo);
-		data->replayGainInfo = NULL;
-	}
-
-	data->replayGainInfo = newReplayGainInfo();
-
-        found &= flacFindVorbisCommentFloat(block,"replaygain_album_gain",
-					&data->replayGainInfo->albumGain);
-        found &= flacFindVorbisCommentFloat(block,"replaygain_album_peak",
-                                	&data->replayGainInfo->albumPeak);
-        found &= flacFindVorbisCommentFloat(block,"replaygain_track_gain",
-					&data->replayGainInfo->trackGain);
-        found &= flacFindVorbisCommentFloat(block,"replaygain_track_peak",
-                                	&data->replayGainInfo->trackPeak);
-
-	if(!found) {
-		freeReplayGainInfo(data->replayGainInfo);
-		data->replayGainInfo = NULL;
-	}
-}
-
 void flacMetadata(const FLAC__SeekableStreamDecoder *dec, 
                 const FLAC__StreamMetadata *block, void *vdata) 
 {
-	FlacData * data = (FlacData *)vdata;
-
-        switch(block->type) {
-        case FLAC__METADATA_TYPE_STREAMINFO:
-		data->dc->audioFormat.bits = 
-                                block->data.stream_info.bits_per_sample;
-		data->dc->audioFormat.sampleRate = 
-                                block->data.stream_info.sample_rate;
-		data->dc->audioFormat.channels = 
-                                block->data.stream_info.channels;
-		data->dc->totalTime = 
-                                ((float)block->data.stream_info.total_samples)/
-			        data->dc->audioFormat.sampleRate;
-                getOutputAudioFormat(&(data->dc->audioFormat),
-                                &(data->cb->audioFormat));
-                break;
-        case FLAC__METADATA_TYPE_VORBIS_COMMENT:
-                flacParseReplayGain(block,data);
-        default:
-                break;
-        }
-}
-
-int flacSendChunk(FlacData * data) {
-	switch(sendDataToOutputBuffer(data->cb, NULL, data->dc, 1, data->chunk,
-			data->chunk_length, data->time, data->bitRate,
-			data->replayGainInfo)) 
-	{
-	case OUTPUT_BUFFER_DC_STOP:
-		return -1;
-	default:
-		return 0;
-	}
-
-	return 0;
+	flac_metadata_common_cb(block, (FlacData *)vdata);
 }
 
 FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__SeekableStreamDecoder *dec,
@@ -447,79 +324,6 @@ FLAC__StreamDecoderWriteStatus flacWrite(const FLAC__SeekableStreamDecoder *dec,
 	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
 }
 
-static int commentMatchesAddToTag(
-		char * str,
-		FLAC__StreamMetadata_VorbisComment_Entry * entry,
-		int itemType,
-		MpdTag ** tag)
-{
-	int slen = strlen(str);
-	int vlen = entry->length - slen;
-
-	if( vlen <= 0 ) return 0;
-
-	if( 0 == strncasecmp(str, entry->entry, slen) ) {
-		if(*tag == NULL) *tag = newMpdTag();
-		addItemToMpdTagWithLen(*tag, itemType, 
-				entry->entry+slen, vlen);
-		return 1;
-	}
-
-	return 0;
-}
-		
-
-static MpdTag * copyVorbisCommentBlockToMpdTag(FLAC__StreamMetadata * block, 
-		MpdTag * tag)
-{
-	int i;
-
-	for(i = 0; i < block->data.vorbis_comment.num_comments; i++) {
-		if(commentMatchesAddToTag(
-				"artist=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_ARTIST,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"title=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_TITLE,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"album=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_ALBUM,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"tracknumber=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_TRACK,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"genre=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_GENRE,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"date=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_DATE,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"composer=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_COMPOSER,
-				&tag));
-		else if(commentMatchesAddToTag(
-				"performer=", 
-				block->data.vorbis_comment.comments+i,
-				TAG_ITEM_PERFORMER,
-				&tag));
-	}
-
-	return tag;
-}
-
 MpdTag * flacMetadataDup(char * file, int * vorbisCommentFound) {
 	MpdTag * ret = NULL;
 	FLAC__Metadata_SimpleIterator * it;
@@ -594,30 +398,32 @@ char * flac_mime_types[] = {"application/x-flac", NULL};
 
 InputPlugin flacPlugin = 
 {
-        "flac",
-        NULL,
+	"flac",
+	NULL,
 	NULL,
-        flac_decode,
 	NULL,
-        flacTagDup,
-        INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
-        flacSuffixes,
-        flac_mime_types
+	flac_decode,
+	NULL,
+	flacTagDup,
+	INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
+	flacSuffixes,
+	flac_mime_types
 };
 
-#else
+#else /* !HAVE_FLAC */
 
 InputPlugin flacPlugin =
-{       
-        NULL,
-        NULL,
-        NULL,
-        NULL,
+{
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	0,
 	NULL,
 	NULL,
-        0,
-        NULL,
-        NULL,
 };
 
-#endif
+#endif /* HAVE_FLAC */
diff --git a/src/inputPlugins/mod_plugin.c b/src/inputPlugins/mod_plugin.c
index 547f4af4a..8b5a245a0 100644
--- a/src/inputPlugins/mod_plugin.c
+++ b/src/inputPlugins/mod_plugin.c
@@ -259,6 +259,7 @@ InputPlugin modPlugin =
         NULL,
 	mod_finishMikMod,
 	NULL,
+	NULL,
         mod_decode,
         modTagDup,
         INPUT_PLUGIN_STREAM_FILE,
@@ -273,6 +274,7 @@ InputPlugin modPlugin =
         NULL,
         NULL,
         NULL,
+	NULL,
         NULL,
 	NULL,
 	NULL,
diff --git a/src/inputPlugins/mp3_plugin.c b/src/inputPlugins/mp3_plugin.c
index 0657f25b1..8a73ed85d 100644
--- a/src/inputPlugins/mp3_plugin.c
+++ b/src/inputPlugins/mp3_plugin.c
@@ -820,6 +820,7 @@ InputPlugin mp3Plugin =
 	"mp3",
 	NULL,
 	NULL,
+	NULL,
 	mp3_decode,
 	NULL,
 	mp3_tagDup,
@@ -837,6 +838,7 @@ InputPlugin mp3Plugin =
 	NULL,
 	NULL,
 	NULL,
+	NULL,
 	0,
 	NULL,
 	NULL
diff --git a/src/inputPlugins/mp4_plugin.c b/src/inputPlugins/mp4_plugin.c
index a1af848c5..e41c054c0 100644
--- a/src/inputPlugins/mp4_plugin.c
+++ b/src/inputPlugins/mp4_plugin.c
@@ -431,6 +431,7 @@ InputPlugin mp4Plugin =
         NULL,
 	NULL,
 	NULL,
+	NULL,
         mp4_decode,
         mp4TagDup,
         INPUT_PLUGIN_STREAM_FILE,
@@ -447,6 +448,7 @@ InputPlugin mp4Plugin =
         NULL,
 	NULL,
 	NULL,
+	NULL,
         NULL,
         0,
         NULL,
diff --git a/src/inputPlugins/mpc_plugin.c b/src/inputPlugins/mpc_plugin.c
index 335c5c368..01a165c44 100644
--- a/src/inputPlugins/mpc_plugin.c
+++ b/src/inputPlugins/mpc_plugin.c
@@ -336,6 +336,7 @@ InputPlugin mpcPlugin =
         "mpc",
 	NULL,
 	NULL,
+	NULL,
         mpc_decode,
         NULL,
         mpcTagDup,
@@ -351,6 +352,7 @@ InputPlugin mpcPlugin =
 	NULL,
 	NULL,
 	NULL,
+	NULL,
         NULL,
         NULL,
         NULL,
diff --git a/src/inputPlugins/ogg_plugin.c b/src/inputPlugins/ogg_plugin.c
deleted file mode 100644
index abadca388..000000000
--- a/src/inputPlugins/ogg_plugin.c
+++ /dev/null
@@ -1,420 +0,0 @@
-/* the Music Player Daemon (MPD)
- * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
- * This project's homepage is: http://www.musicpd.org
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
- */
-
-#include "../inputPlugin.h"
-
-#ifdef HAVE_OGG
-
-#include "../utils.h"
-#include "../audio.h"
-#include "../log.h"
-#include "../pcm_utils.h"
-#include "../inputStream.h"
-#include "../outputBuffer.h"
-#include "../replayGain.h"
-
-#include <stdio.h>
-#include <unistd.h>
-#include <stdlib.h>
-#include <string.h>
-
-#ifndef HAVE_TREMOR
-#include <vorbis/vorbisfile.h>
-#else
-#include <tremor/ivorbisfile.h>
-/* Macros to make Tremor's API look like libogg. Tremor always
-   returns host-byte-order 16-bit signed data, and uses integer
-   milliseconds where libogg uses double seconds. 
-*/
-#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
-        ov_read(VF, BUFFER, LENGTH, BITSTREAM)
-#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
-#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
-#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
-#endif /* HAVE_TREMOR */
-
-#include <errno.h>
-
-#ifdef WORDS_BIGENDIAN
-#define OGG_DECODE_USE_BIGENDIAN	1
-#else
-#define OGG_DECODE_USE_BIGENDIAN	0
-#endif
-
-typedef struct _OggCallbackData {
-        InputStream * inStream;
-        DecoderControl * dc;
-} OggCallbackData;
-
-size_t ogg_read_cb(void * ptr, size_t size, size_t nmemb, void * vdata)
-{
-	size_t ret = 0;
-        OggCallbackData * data = (OggCallbackData *)vdata;
-
-        while(1) {
-	        ret = readFromInputStream(data->inStream,ptr,size,nmemb);
-                if(ret == 0 && !inputStreamAtEOF(data->inStream) && 
-                                !data->dc->stop) 
-                {
-                        my_usleep(10000);
-                }
-                else break;
-        }
-        errno = 0;
-	/*if(ret<0) errno = ((InputStream *)inStream)->error;*/
-
-	return ret;
-}
-
-int ogg_seek_cb(void * vdata, ogg_int64_t offset, int whence) {
-        OggCallbackData * data = (OggCallbackData *)vdata;
-
-	return seekInputStream(data->inStream,offset,whence);
-}
-
-int ogg_close_cb(void * vdata) {
-        OggCallbackData * data = (OggCallbackData *)vdata;
-
-	return closeInputStream(data->inStream);
-}
-
-long ogg_tell_cb(void * vdata) {
-        OggCallbackData * data = (OggCallbackData *)vdata;
-
-	return (long)(data->inStream->offset);
-}
-
-char * ogg_parseComment(char * comment, char * needle) {
-        int len = strlen(needle);
-
-        if(strncasecmp(comment, needle, len) == 0 && *(comment+len) == '=') {
-		return comment+len+1;
-	}
-
-        return NULL;
-}
-
-void ogg_getReplayGainInfo(char ** comments, ReplayGainInfo ** infoPtr) {
-        char * temp;
-	int found = 0;
-
-	if(*infoPtr) freeReplayGainInfo(*infoPtr);
-	*infoPtr = newReplayGainInfo();
-
-        while(*comments) {
-                if((temp = ogg_parseComment(*comments,"replaygain_track_gain"))) 
-                {
-                        (*infoPtr)->trackGain = atof(temp);
-			found = 1;
-                }
-                else if((temp = ogg_parseComment(*comments,
-                                        "replaygain_album_gain"))) 
-                {
-                        (*infoPtr)->albumGain = atof(temp);
-			found = 1;
-                }
-                else if((temp = ogg_parseComment(*comments,
-                                        "replaygain_track_peak"))) 
-                {
-                        (*infoPtr)->trackPeak = atof(temp);
-			found = 1;
-                }
-                else if((temp = ogg_parseComment(*comments,
-                                        "replaygain_album_peak"))) 
-                {
-                        (*infoPtr)->albumPeak = atof(temp);
-			found = 1;
-                }
-
-                comments++;
-        }
-
-	if(!found) {
-		freeReplayGainInfo(*infoPtr);
-		*infoPtr = NULL;
-	}
-}
-
-MpdTag * oggCommentsParse(char ** comments) {
-	MpdTag * ret = NULL;
-	char * temp;
-
-	while(*comments) {
-                if((temp = ogg_parseComment(*comments,"artist"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_ARTIST, temp);
-		} 
-                else if((temp = ogg_parseComment(*comments,"title"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_TITLE, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"album"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_ALBUM, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"tracknumber"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_TRACK, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"genre"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_GENRE, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"date"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_DATE, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"composer"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_COMPOSER, temp);
-		}
-                else if((temp = ogg_parseComment(*comments,"performer"))) {
-			if(!ret) ret = newMpdTag();
-			addItemToMpdTag(ret, TAG_ITEM_PERFORMER, temp);
-		}
-
-		comments++;
-	}
-
-	return ret;
-}
-
-void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char * streamName,
-		char ** comments) 
-{
-	MpdTag * tag;
-
-	tag = oggCommentsParse(comments);
-	if(!tag && streamName) {
-		tag = newMpdTag();
-	}
-	if(!tag) return;
-
-	/*if(tag->artist) printf("Artist: %s\n", tag->artist);
-	if(tag->album) printf("Album: %s\n", tag->album);
-	if(tag->track) printf("Track: %s\n", tag->track);
-	if(tag->title) printf("Title: %s\n", tag->title);*/
-
-	if(streamName) {
-		clearItemsFromMpdTag(tag, TAG_ITEM_NAME);
-		addItemToMpdTag(tag, TAG_ITEM_NAME, streamName);
-	}
-
-	copyMpdTagToOutputBuffer(cb, tag);
-
-	freeMpdTag(tag);
-}
-
-int ogg_decode(OutputBuffer * cb, DecoderControl * dc, InputStream * inStream)
-{
-	OggVorbis_File vf;
-	ov_callbacks callbacks;
-        OggCallbackData data;
-	int current_section;
-	int prev_section = -1;
-	int eof = 0;
-	long ret;
-#define OGG_CHUNK_SIZE 4096
-	char chunk[OGG_CHUNK_SIZE];
-	int chunkpos = 0;
-	long bitRate = 0;
-	long test;
-        ReplayGainInfo * replayGainInfo = NULL;
-	char ** comments;
-
-        data.inStream = inStream;
-        data.dc = dc;
-
-	callbacks.read_func = ogg_read_cb;
-	callbacks.seek_func = ogg_seek_cb;
-	callbacks.close_func = ogg_close_cb;
-	callbacks.tell_func = ogg_tell_cb;
-	
-	if((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
-		closeInputStream(inStream);
-		if(!dc->stop) {
-		        ERROR("Error decoding Ogg Vorbis stream: ");
-			switch(ret) {
-			case OV_EREAD:
-				ERROR("read error\n");
-				break;
-			case OV_ENOTVORBIS:
-				ERROR("not vorbis stream\n");
-				break;
-			case OV_EVERSION:
-				ERROR("vorbis version mismatch\n");
-				break;
-			case OV_EBADHEADER:
-				ERROR("invalid vorbis header\n");
-				break;
-			case OV_EFAULT:
-				ERROR("internal logic error\n");
-				break;
-			default:
-				ERROR("unknown error\n");
-				break;
-			}
-                        return -1;
-                }
-                else {
-                        dc->state = DECODE_STATE_STOP;
-                        dc->stop = 0;
-                }
-                return 0;
-	}
-	
-	dc->totalTime = ov_time_total(&vf,-1);
-       	if(dc->totalTime < 0) dc->totalTime = 0;
-
-	dc->audioFormat.bits = 16;
-
-	while(!eof) {
-		if(dc->seek) {
-			if(0 == ov_time_seek_page(&vf,dc->seekWhere)) {
-                                clearOutputBuffer(cb);
-			        chunkpos = 0;
-                        }
-                        else dc->seekError = 1;
-			dc->seek = 0;
-		}
-		ret = ov_read(&vf, chunk+chunkpos, 
-				OGG_CHUNK_SIZE-chunkpos,
-				OGG_DECODE_USE_BIGENDIAN,
-				2, 1, &current_section);
-
-		if(current_section!=prev_section) {
-			/*printf("new song!\n");*/
-			vorbis_info *vi=ov_info(&vf,-1);
-			dc->audioFormat.channels = vi->channels;
-			dc->audioFormat.sampleRate = vi->rate;
-			if(dc->state == DECODE_STATE_START) {
-        			getOutputAudioFormat(&(dc->audioFormat),
-					&(cb->audioFormat));
-				dc->state = DECODE_STATE_DECODE;
-			}
-			comments = ov_comment(&vf, -1)->user_comments;
-			putOggCommentsIntoOutputBuffer(cb, inStream->metaName,
-					comments);
-        		ogg_getReplayGainInfo(comments, &replayGainInfo);
-		}
-
-		prev_section = current_section;
-
-		if(ret <= 0 && ret != OV_HOLE) {
-			eof = 1;
-			break;
-		}
-                if(ret == OV_HOLE) ret = 0;
-
-		chunkpos+=ret;
-
-		if(chunkpos >= OGG_CHUNK_SIZE) {
-			if((test = ov_bitrate_instant(&vf))>0) {
-				bitRate = test/1000;
-			}
-			sendDataToOutputBuffer(cb, inStream, dc, 
-						inStream->seekable,  
-                                        	chunk, chunkpos, 
-						ov_pcm_tell(&vf)/
-						dc->audioFormat.sampleRate,
-						bitRate,
-						replayGainInfo);
-			chunkpos = 0;
-			if(dc->stop) break;
-		}
-	}
-
-	if(!dc->stop && chunkpos > 0) {
-		sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable,
-				chunk, chunkpos,
-				ov_time_tell(&vf), bitRate, replayGainInfo);
-	}
-
-	if(replayGainInfo) freeReplayGainInfo(replayGainInfo);
-
-	ov_clear(&vf);
-
-	flushOutputBuffer(cb);
-
-	if(dc->stop) {
-		dc->state = DECODE_STATE_STOP;
-		dc->stop = 0;
-	}
-	else dc->state = DECODE_STATE_STOP;
-
-	return 0;
-}
-
-MpdTag * oggTagDup(char * file) {
-	MpdTag * ret = NULL;
-	FILE * fp;
-	OggVorbis_File vf;
-
-	fp = fopen(file,"r"); 
-	if(!fp)
-	{
-		DEBUG("oggTagDup: Failed to open file: '%s', %s\n", file, strerror(errno));
-		 return NULL;
-	}
-	if(ov_open(fp,&vf,NULL,0)<0) {
-		fclose(fp);
-		return NULL;
-	}
-
-	ret = oggCommentsParse(ov_comment(&vf,-1)->user_comments);
-
-	if(!ret) ret = newMpdTag();
-	ret->time = (int)(ov_time_total(&vf,-1)+0.5);
-
-	ov_clear(&vf);
-
-	return ret;	
-}
-
-char * oggSuffixes[] = {"ogg", NULL};
-char * oggMimeTypes[] = {"application/ogg", NULL};
-
-InputPlugin oggPlugin =
-{
-        "ogg",
-	NULL,
-	NULL,
-        ogg_decode,
-        NULL,
-        oggTagDup,
-        INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
-        oggSuffixes,
-        oggMimeTypes
-};
-
-#else
-
-InputPlugin oggPlugin = 
-{
-	NULL,
-	NULL,
-	NULL,
-        NULL,
-        NULL,
-        NULL,
-        0,
-        NULL,
-        NULL
-};
-
-#endif
diff --git a/src/inputPlugins/oggflac_plugin.c b/src/inputPlugins/oggflac_plugin.c
new file mode 100644
index 000000000..25f9a3cdb
--- /dev/null
+++ b/src/inputPlugins/oggflac_plugin.c
@@ -0,0 +1,426 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * OggFLAC support (half-stolen from flac_plugin.c :))
+ * (c) 2005 by Eric Wong <normalperson@yhbt.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#include "../inputPlugin.h"
+
+#ifdef HAVE_OGGFLAC
+
+#include "_flac_common.h"
+#include "_ogg_common.h"
+
+#include "../utils.h"
+#include "../log.h"
+#include "../pcm_utils.h"
+#include "../inputStream.h"
+#include "../outputBuffer.h"
+#include "../replayGain.h"
+#include "../audio.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <OggFLAC/seekable_stream_decoder.h>
+#include <FLAC/metadata.h>
+
+static void oggflac_cleanup(InputStream * inStream,
+		FlacData * data,
+		OggFLAC__SeekableStreamDecoder * decoder)
+{
+	if (data->replayGainInfo)
+		freeReplayGainInfo(data->replayGainInfo);
+	if (decoder)
+		OggFLAC__seekable_stream_decoder_delete(decoder);
+	closeInputStream(inStream);
+}
+
+static OggFLAC__SeekableStreamDecoderReadStatus of_read_cb(
+                const OggFLAC__SeekableStreamDecoder * decoder,
+		FLAC__byte buf[], unsigned * bytes, void * fdata) {
+	FlacData * data = (FlacData *) fdata;
+	size_t r;
+
+	while (1) {
+		r = readFromInputStream(data->inStream,(void *)buf,1,*bytes);
+		if (r == 0 && !inputStreamAtEOF(data->inStream) &&
+				!data->dc->stop)
+			my_usleep(10000);
+		else
+			break;
+	}
+	*bytes = r;
+        
+	if (r == 0 && !inputStreamAtEOF(data->inStream) && !data->dc->stop)
+		return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR;
+        
+        return OggFLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK;
+}
+
+static OggFLAC__SeekableStreamDecoderSeekStatus of_seek_cb(
+                const OggFLAC__SeekableStreamDecoder * decoder, 
+                FLAC__uint64 offset, void * fdata) 
+{
+	FlacData * data = (FlacData *) fdata;
+
+        if(seekInputStream(data->inStream,offset,SEEK_SET)<0) {
+                return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR;
+        }
+
+        return OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK;
+}
+
+static OggFLAC__SeekableStreamDecoderTellStatus of_tell_cb(
+                const OggFLAC__SeekableStreamDecoder * decoder, 
+                FLAC__uint64 * offset, void * fdata) 
+{
+	FlacData * data = (FlacData *) fdata;
+
+        *offset = (long)(data->inStream->offset);
+
+        return OggFLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK;
+}
+
+static OggFLAC__SeekableStreamDecoderLengthStatus of_length_cb(
+                const OggFLAC__SeekableStreamDecoder * decoder, 
+                FLAC__uint64 * length, void * fdata)
+{
+	FlacData * data = (FlacData *) fdata;
+
+        *length = (size_t)(data->inStream->size);
+
+        return OggFLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK;
+}
+
+static FLAC__bool of_EOF_cb(const OggFLAC__SeekableStreamDecoder * decoder,
+		void * fdata) {
+	FlacData * data = (FlacData *) fdata;
+
+	if (inputStreamAtEOF(data->inStream) == 1)
+                return true;
+	return false;
+}
+
+static void of_error_cb(const OggFLAC__SeekableStreamDecoder * decoder, 
+                FLAC__StreamDecoderErrorStatus status, void *fdata) 
+{
+	flac_error_common_cb("oggflac",status,(FlacData *) fdata);
+}
+
+static void oggflacPrintErroredState(OggFLAC__SeekableStreamDecoderState state) 
+{
+	switch(state) {
+	case OggFLAC__SEEKABLE_STREAM_DECODER_MEMORY_ALLOCATION_ERROR:
+		ERROR("oggflac allocation error\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_READ_ERROR:
+		ERROR("oggflac read error\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_SEEK_ERROR:
+		ERROR("oggflac seek error\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_STREAM_DECODER_ERROR:
+		ERROR("oggflac seekable stream error\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_ALREADY_INITIALIZED:
+		ERROR("oggflac decoder already initialized\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_INVALID_CALLBACK:
+		ERROR("invalid oggflac callback\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_UNINITIALIZED:
+		ERROR("oggflac decoder uninitialized\n");
+		break;
+	case OggFLAC__SEEKABLE_STREAM_DECODER_OK:
+	case OggFLAC__SEEKABLE_STREAM_DECODER_SEEKING:
+	case OggFLAC__SEEKABLE_STREAM_DECODER_END_OF_STREAM:
+		break;
+	}
+}
+
+static FLAC__StreamDecoderWriteStatus oggflacWrite(
+		const OggFLAC__SeekableStreamDecoder *decoder,
+                const FLAC__Frame *frame, const FLAC__int32 * const buf[], 
+                void * vdata) 
+{
+	FlacData * data = (FlacData *)vdata;
+	FLAC__uint32 samples = frame->header.blocksize;
+	FLAC__uint16 u16;
+	unsigned char * uc;
+	int c_samp, c_chan, d_samp;
+	int i;
+	float timeChange;
+	const int bytesPerSample = data->dc->audioFormat.bits/8;
+	
+	timeChange = ((float)samples)/frame->header.sample_rate;
+	data->time+= timeChange;
+
+	/* ogg123 uses a complicated method of calculating bitrate
+	 * with averaging which I'm not too fond of.
+	 * (waste of memory/CPU cycles, especially given this is _lossless_)
+	 * a get_decode_position() is not available in OggFLAC, either
+	 *
+	 * this does not give an accurate bitrate:
+	 * (bytes_last_read was set in the read callback)
+	data->bitRate = ((8.0 * data->bytes_last_read *
+				frame->header.sample_rate)
+			/((float)samples * 1000)) + 0.5;
+	*/
+	
+	for(c_samp = d_samp = 0; c_samp < frame->header.blocksize; c_samp++) {
+		for(c_chan = 0; c_chan < frame->header.channels; 
+				c_chan++, d_samp++) {
+			u16 = buf[c_chan][c_samp];
+			uc = (unsigned char *)&u16;
+			for(i=0;i<(data->dc->audioFormat.bits/8);i++) {
+				if(data->chunk_length>=FLAC_CHUNK_SIZE) {
+					if(flacSendChunk(data)<0) {
+						return FLAC__STREAM_DECODER_WRITE_STATUS_ABORT;
+					}
+					data->chunk_length = 0;
+					if(data->dc->seek) {
+						return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+					}
+				}
+				data->chunk[data->chunk_length++] = *(uc++);
+			}
+		}
+	}
+
+	return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+/* used by TagDup */
+static void of_metadata_dup_cb(
+		const OggFLAC__SeekableStreamDecoder * decoder, 
+                const FLAC__StreamMetadata *block, void *vdata) 
+{
+	FlacData * data = (FlacData *)vdata;
+
+        switch(block->type) {
+        case FLAC__METADATA_TYPE_STREAMINFO:
+		if (!data->tag) data->tag = newMpdTag();
+		data->tag->time = ((float)block->data.stream_info.
+					total_samples) /
+					block->data.stream_info.sample_rate +
+					0.5;
+		return;
+        case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+		copyVorbisCommentBlockToMpdTag(block,data->tag);
+	default:
+		break;
+        }
+}
+
+/* used by decode */
+static void of_metadata_decode_cb(const OggFLAC__SeekableStreamDecoder * dec,
+                const FLAC__StreamMetadata *block, void *vdata) 
+{
+	flac_metadata_common_cb(block, (FlacData *)vdata);
+}
+
+static OggFLAC__SeekableStreamDecoder * full_decoder_init_and_read_metadata(
+		FlacData * data,
+		unsigned int metadata_only)
+{
+	OggFLAC__SeekableStreamDecoder * decoder = NULL;
+	unsigned int s = 1;
+
+	if (!(decoder = OggFLAC__seekable_stream_decoder_new()))
+		return NULL;
+
+	if (metadata_only) {
+		s &= OggFLAC__seekable_stream_decoder_set_metadata_callback(
+				decoder, of_metadata_dup_cb);
+		s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(
+				decoder,
+				FLAC__METADATA_TYPE_STREAMINFO);
+	} else {
+		s &= OggFLAC__seekable_stream_decoder_set_metadata_callback(
+				decoder, of_metadata_decode_cb);
+	}
+	
+	s &= OggFLAC__seekable_stream_decoder_set_read_callback(decoder,
+                        of_read_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_seek_callback(decoder,
+                        of_seek_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_tell_callback(decoder,
+                        of_tell_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_length_callback(decoder,
+                        of_length_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_eof_callback(decoder,
+			of_EOF_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_write_callback(decoder,
+                        oggflacWrite);
+	s &= OggFLAC__seekable_stream_decoder_set_metadata_respond(decoder,
+			FLAC__METADATA_TYPE_VORBIS_COMMENT);
+	s &= OggFLAC__seekable_stream_decoder_set_error_callback(decoder,
+                        of_error_cb);
+	s &= OggFLAC__seekable_stream_decoder_set_client_data(decoder,
+                        (void *)data);
+
+	if (!s) {
+		ERROR("oggflac problem before init()\n");
+		goto fail;
+	}
+	if (OggFLAC__seekable_stream_decoder_init(decoder) !=
+				OggFLAC__SEEKABLE_STREAM_DECODER_OK)
+	{
+		ERROR("oggflac problem doing init()\n");
+		goto fail;
+	}
+	if (!OggFLAC__seekable_stream_decoder_process_until_end_of_metadata(
+				decoder)) {
+		ERROR("oggflac problem reading metadata\n");
+		goto fail;
+	}
+
+	return decoder; 
+
+fail:
+	oggflacPrintErroredState(
+			OggFLAC__seekable_stream_decoder_get_state(decoder));
+	OggFLAC__seekable_stream_decoder_delete(decoder);
+	return NULL;
+}
+
+/* public functions: */
+static MpdTag * oggflac_TagDup(char * file)
+{
+	InputStream inStream;
+	OggFLAC__SeekableStreamDecoder * decoder;
+	FlacData data;
+	
+	if (openInputStream(&inStream, file) < 0)
+		return NULL;
+	if (ogg_stream_type_detect(&inStream) != FLAC) {
+		closeInputStream(&inStream);
+		return NULL;
+	}
+
+	init_FlacData(&data, NULL, NULL, &inStream);
+
+	/* errors here won't matter,
+	 * data.tag will be set or unset, that's all we care about */
+	decoder = full_decoder_init_and_read_metadata(&data,1);
+      
+	oggflac_cleanup(&inStream, &data, decoder);
+	
+	return data.tag;
+}
+
+static unsigned int oggflac_try_decode(InputStream * inStream)
+{
+	return (ogg_stream_type_detect(inStream) == FLAC) ? 1 : 0;
+}
+
+static int oggflac_decode(OutputBuffer * cb, DecoderControl * dc,
+		InputStream * inStream)
+{
+	OggFLAC__SeekableStreamDecoder * decoder = NULL;
+	FlacData data;
+        int ret = 0;
+
+	init_FlacData(&data, cb, dc, inStream);
+
+	if(!(decoder = full_decoder_init_and_read_metadata(&data,0))){
+                ret = -1;
+                goto fail;
+        }
+
+	dc->state = DECODE_STATE_DECODE;
+
+	while(1) {
+		OggFLAC__seekable_stream_decoder_process_single(decoder);
+		if(OggFLAC__seekable_stream_decoder_get_state(decoder)!=
+				OggFLAC__SEEKABLE_STREAM_DECODER_OK)
+		{
+			break;
+		}
+		if(dc->seek) {
+			FLAC__uint64 sampleToSeek = dc->seekWhere*
+					dc->audioFormat.sampleRate+0.5;
+			if(OggFLAC__seekable_stream_decoder_seek_absolute(
+						decoder, sampleToSeek))
+			{
+                                clearOutputBuffer(cb);
+				data.time = ((float)sampleToSeek)/
+					dc->audioFormat.sampleRate;
+				data.position = 0;
+			}
+                        else dc->seekError = 1;
+			dc->seek = 0;
+		}
+	}
+	
+	if(!dc->stop) {
+		oggflacPrintErroredState(
+                        OggFLAC__seekable_stream_decoder_get_state(decoder));
+		OggFLAC__seekable_stream_decoder_finish(decoder);
+	}
+	/* send last little bit */
+	if(data.chunk_length>0 && !dc->stop) {
+		flacSendChunk(&data);
+		flushOutputBuffer(data.cb);
+	}
+
+	dc->state = DECODE_STATE_STOP;
+	dc->stop = 0;
+
+fail:
+	oggflac_cleanup(inStream, &data, decoder);
+
+	return ret;
+}
+
+static char * oggflac_Suffixes[] = {"ogg", NULL};
+static char * oggflac_mime_types[] = {"application/ogg", NULL};
+
+InputPlugin oggflacPlugin = 
+{
+	"oggflac",
+	NULL,
+	NULL,
+	oggflac_try_decode,
+	oggflac_decode,
+	NULL,
+	oggflac_TagDup,
+	INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
+	oggflac_Suffixes,
+	oggflac_mime_types
+};
+
+#else /* !HAVE_FLAC */
+
+InputPlugin oggflacPlugin =
+{
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	0,
+	NULL,
+	NULL,
+};
+
+#endif /* HAVE_OGGFLAC */
+
diff --git a/src/inputPlugins/oggvorbis_plugin.c b/src/inputPlugins/oggvorbis_plugin.c
new file mode 100644
index 000000000..63bac411e
--- /dev/null
+++ b/src/inputPlugins/oggvorbis_plugin.c
@@ -0,0 +1,426 @@
+/* the Music Player Daemon (MPD)
+ * (c)2003-2004 by Warren Dukes (shank@mercury.chem.pitt.edu)
+ * This project's homepage is: http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* TODO 'ogg' should probably be replaced with 'oggvorbis' in all instances */
+
+#include "../inputPlugin.h"
+
+#ifdef HAVE_OGGVORBIS
+
+#include "_ogg_common.h"
+
+#include "../utils.h"
+#include "../audio.h"
+#include "../log.h"
+#include "../pcm_utils.h"
+#include "../inputStream.h"
+#include "../outputBuffer.h"
+#include "../replayGain.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#ifndef HAVE_TREMOR
+#include <vorbis/vorbisfile.h>
+#else
+#include <tremor/ivorbisfile.h>
+/* Macros to make Tremor's API look like libogg. Tremor always
+   returns host-byte-order 16-bit signed data, and uses integer
+   milliseconds where libogg uses double seconds. 
+*/
+#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
+        ov_read(VF, BUFFER, LENGTH, BITSTREAM)
+#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
+#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
+#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
+#endif /* HAVE_TREMOR */
+
+#include <errno.h>
+
+#ifdef WORDS_BIGENDIAN
+#define OGG_DECODE_USE_BIGENDIAN	1
+#else
+#define OGG_DECODE_USE_BIGENDIAN	0
+#endif
+
+typedef struct _OggCallbackData {
+        InputStream * inStream;
+        DecoderControl * dc;
+} OggCallbackData;
+
+size_t ogg_read_cb(void * ptr, size_t size, size_t nmemb, void * vdata)
+{
+	size_t ret = 0;
+        OggCallbackData * data = (OggCallbackData *)vdata;
+
+        while(1) {
+	        ret = readFromInputStream(data->inStream,ptr,size,nmemb);
+                if(ret == 0 && !inputStreamAtEOF(data->inStream) && 
+                                !data->dc->stop) 
+                {
+                        my_usleep(10000);
+                }
+                else break;
+        }
+        errno = 0;
+	/*if(ret<0) errno = ((InputStream *)inStream)->error;*/
+
+	return ret;
+}
+
+int ogg_seek_cb(void * vdata, ogg_int64_t offset, int whence) {
+        OggCallbackData * data = (OggCallbackData *)vdata;
+
+	return seekInputStream(data->inStream,offset,whence);
+}
+
+int ogg_close_cb(void * vdata) {
+        OggCallbackData * data = (OggCallbackData *)vdata;
+
+	return closeInputStream(data->inStream);
+}
+
+long ogg_tell_cb(void * vdata) {
+        OggCallbackData * data = (OggCallbackData *)vdata;
+
+	return (long)(data->inStream->offset);
+}
+
+static inline char * ogg_parseComment(char * comment, char * needle) {
+        int len = strlen(needle);
+
+        if(strncasecmp(comment, needle, len) == 0 && *(comment+len) == '=') {
+		return comment+len+1;
+	}
+
+        return NULL;
+}
+
+void ogg_getReplayGainInfo(char ** comments, ReplayGainInfo ** infoPtr) {
+        char * temp;
+	int found = 0;
+
+	if(*infoPtr) freeReplayGainInfo(*infoPtr);
+	*infoPtr = newReplayGainInfo();
+
+        while(*comments) {
+                if((temp = ogg_parseComment(*comments,"replaygain_track_gain"))) 
+                {
+                        (*infoPtr)->trackGain = atof(temp);
+			found = 1;
+                }
+                else if((temp = ogg_parseComment(*comments,
+                                        "replaygain_album_gain"))) 
+                {
+                        (*infoPtr)->albumGain = atof(temp);
+			found = 1;
+                }
+                else if((temp = ogg_parseComment(*comments,
+                                        "replaygain_track_peak"))) 
+                {
+                        (*infoPtr)->trackPeak = atof(temp);
+			found = 1;
+                }
+                else if((temp = ogg_parseComment(*comments,
+                                        "replaygain_album_peak"))) 
+                {
+                        (*infoPtr)->albumPeak = atof(temp);
+			found = 1;
+                }
+
+                comments++;
+        }
+
+	if(!found) {
+		freeReplayGainInfo(*infoPtr);
+		*infoPtr = NULL;
+	}
+}
+
+static const char * VORBIS_COMMENT_TRACK_KEY = "tracknumber";
+
+static inline unsigned int ogg_parseCommentAddToTag(char * comment,
+		unsigned int itemType, MpdTag ** tag)
+{
+	const char * needle = (itemType == TAG_ITEM_TRACK) ?
+			VORBIS_COMMENT_TRACK_KEY : mpdTagItemKeys[itemType];
+	unsigned int len = strlen(needle);
+	
+	if(strncasecmp(comment, needle, len) == 0 && *(comment+len) == '=') {
+		if (!*tag)
+			*tag = newMpdTag();
+		
+		addItemToMpdTag(*tag, itemType, comment+len+1);
+		
+		return 1;
+	}
+	
+	return 0;
+}
+
+static MpdTag * oggCommentsParse(char ** comments) {
+	MpdTag * tag = NULL;
+	
+	while(*comments) {
+		unsigned int j;
+		for (j = TAG_NUM_OF_ITEM_TYPES; j--; ) {
+			if (ogg_parseCommentAddToTag(*comments, j, &tag))
+				break;
+		}
+		comments++;
+	}
+	
+	return tag;
+}
+
+void putOggCommentsIntoOutputBuffer(OutputBuffer * cb, char * streamName,
+		char ** comments) 
+{
+	MpdTag * tag;
+
+	tag = oggCommentsParse(comments);
+	if(!tag && streamName) {
+		tag = newMpdTag();
+	}
+	if(!tag) return;
+
+	/*if(tag->artist) printf("Artist: %s\n", tag->artist);
+	if(tag->album) printf("Album: %s\n", tag->album);
+	if(tag->track) printf("Track: %s\n", tag->track);
+	if(tag->title) printf("Title: %s\n", tag->title);*/
+
+	if(streamName) {
+		clearItemsFromMpdTag(tag, TAG_ITEM_NAME);
+		addItemToMpdTag(tag, TAG_ITEM_NAME, streamName);
+	}
+
+	copyMpdTagToOutputBuffer(cb, tag);
+
+	freeMpdTag(tag);
+}
+
+/* public */
+int oggvorbis_decode(OutputBuffer * cb, DecoderControl * dc,
+		InputStream * inStream)
+{
+	OggVorbis_File vf;
+	ov_callbacks callbacks;
+        OggCallbackData data;
+	int current_section;
+	int prev_section = -1;
+	int eof = 0;
+	long ret;
+#define OGG_CHUNK_SIZE 4096
+	char chunk[OGG_CHUNK_SIZE];
+	int chunkpos = 0;
+	long bitRate = 0;
+	long test;
+        ReplayGainInfo * replayGainInfo = NULL;
+	char ** comments;
+
+        data.inStream = inStream;
+        data.dc = dc;
+
+	callbacks.read_func = ogg_read_cb;
+	callbacks.seek_func = ogg_seek_cb;
+	callbacks.close_func = ogg_close_cb;
+	callbacks.tell_func = ogg_tell_cb;
+	
+	if((ret = ov_open_callbacks(&data, &vf, NULL, 0, callbacks)) < 0) {
+		closeInputStream(inStream);
+		if(!dc->stop) {
+		        ERROR("Error decoding Ogg Vorbis stream: ");
+			switch(ret) {
+			case OV_EREAD:
+				ERROR("read error\n");
+				break;
+			case OV_ENOTVORBIS:
+				ERROR("not vorbis stream\n");
+				break;
+			case OV_EVERSION:
+				ERROR("vorbis version mismatch\n");
+				break;
+			case OV_EBADHEADER:
+				ERROR("invalid vorbis header\n");
+				break;
+			case OV_EFAULT:
+				ERROR("internal logic error\n");
+				break;
+			default:
+				ERROR("unknown error\n");
+				break;
+			}
+                        return -1;
+                }
+                else {
+                        dc->state = DECODE_STATE_STOP;
+                        dc->stop = 0;
+                }
+                return 0;
+	}
+	
+	dc->totalTime = ov_time_total(&vf,-1);
+       	if(dc->totalTime < 0) dc->totalTime = 0;
+
+	dc->audioFormat.bits = 16;
+
+	while(!eof) {
+		if(dc->seek) {
+			if(0 == ov_time_seek_page(&vf,dc->seekWhere)) {
+                                clearOutputBuffer(cb);
+			        chunkpos = 0;
+                        }
+                        else dc->seekError = 1;
+			dc->seek = 0;
+		}
+		ret = ov_read(&vf, chunk+chunkpos, 
+				OGG_CHUNK_SIZE-chunkpos,
+				OGG_DECODE_USE_BIGENDIAN,
+				2, 1, &current_section);
+
+		if(current_section!=prev_section) {
+			/*printf("new song!\n");*/
+			vorbis_info *vi=ov_info(&vf,-1);
+			dc->audioFormat.channels = vi->channels;
+			dc->audioFormat.sampleRate = vi->rate;
+			if(dc->state == DECODE_STATE_START) {
+        			getOutputAudioFormat(&(dc->audioFormat),
+					&(cb->audioFormat));
+				dc->state = DECODE_STATE_DECODE;
+			}
+			comments = ov_comment(&vf, -1)->user_comments;
+			putOggCommentsIntoOutputBuffer(cb, inStream->metaName,
+					comments);
+        		ogg_getReplayGainInfo(comments, &replayGainInfo);
+		}
+
+		prev_section = current_section;
+
+		if(ret <= 0 && ret != OV_HOLE) {
+			eof = 1;
+			break;
+		}
+                if(ret == OV_HOLE) ret = 0;
+
+		chunkpos+=ret;
+
+		if(chunkpos >= OGG_CHUNK_SIZE) {
+			if((test = ov_bitrate_instant(&vf))>0) {
+				bitRate = test/1000;
+			}
+			sendDataToOutputBuffer(cb, inStream, dc, 
+						inStream->seekable,  
+                                        	chunk, chunkpos, 
+						ov_pcm_tell(&vf)/
+						dc->audioFormat.sampleRate,
+						bitRate,
+						replayGainInfo);
+			chunkpos = 0;
+			if(dc->stop) break;
+		}
+	}
+
+	if(!dc->stop && chunkpos > 0) {
+		sendDataToOutputBuffer(cb, NULL, dc, inStream->seekable,
+				chunk, chunkpos,
+				ov_time_tell(&vf), bitRate, replayGainInfo);
+	}
+
+	if(replayGainInfo) freeReplayGainInfo(replayGainInfo);
+
+	ov_clear(&vf);
+
+	flushOutputBuffer(cb);
+
+	if(dc->stop) {
+		dc->state = DECODE_STATE_STOP;
+		dc->stop = 0;
+	}
+	else dc->state = DECODE_STATE_STOP;
+
+	return 0;
+}
+
+MpdTag * oggvorbis_TagDup(char * file) {
+	MpdTag * ret = NULL;
+	FILE * fp;
+	OggVorbis_File vf;
+
+	fp = fopen(file,"r"); 
+	if(!fp)
+	{
+		DEBUG("oggTagDup: Failed to open file: '%s', %s\n", file, strerror(errno));
+		 return NULL;
+	}
+	if(ov_open(fp,&vf,NULL,0)<0) {
+		fclose(fp);
+		return NULL;
+	}
+
+	ret = oggCommentsParse(ov_comment(&vf,-1)->user_comments);
+
+	if(!ret) ret = newMpdTag();
+	ret->time = (int)(ov_time_total(&vf,-1)+0.5);
+
+	ov_clear(&vf);
+
+	return ret;	
+}
+
+static unsigned int oggvorbis_try_decode(InputStream * inStream)
+{
+	return (ogg_stream_type_detect(inStream) == VORBIS) ? 1 : 0;
+}
+
+
+static char * oggvorbis_Suffixes[] = {"ogg", NULL};
+static char * oggvorbis_MimeTypes[] = {"application/ogg", NULL};
+
+InputPlugin oggvorbisPlugin =
+{
+	"oggvorbis",
+	NULL,
+	NULL,
+	oggvorbis_try_decode,
+	oggvorbis_decode,
+	NULL,
+	oggvorbis_TagDup,
+	INPUT_PLUGIN_STREAM_URL | INPUT_PLUGIN_STREAM_FILE,
+	oggvorbis_Suffixes,
+	oggvorbis_MimeTypes
+};
+
+#else /* !HAVE_OGGVORBIS */
+
+InputPlugin oggvorbisPlugin =
+{
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	NULL,
+	0,
+	NULL,
+	NULL,
+};
+
+#endif /* HAVE_OGGVORBIS */
-- 
cgit v1.2.3