diff --git a/Makefile.am b/Makefile.am
index 982e76f..65327d5 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,10 +1,10 @@
 plugin_LTLIBRARIES = libgstflac.la
 
-libgstflac_la_SOURCES = gstflac.c gstflacdec.c gstflacenc.c
+libgstflac_la_SOURCES = gstflac.c gstflacdec.c gstflacenc.c gstflactag.c
 libgstflac_la_CFLAGS = $(GST_PLUGINS_BASE_CFLAGS) $(GST_BASE_CFLAGS) $(GST_CFLAGS)
 libgstflac_la_LIBADD = \
 	$(GST_PLUGINS_BASE_LIBS) -lgsttag-$(GST_MAJORMINOR) \
 	$(GST_BASE_LIBS) $(GST_LIBS) $(FLAC_LIBS)
 libgstflac_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
 
-noinst_HEADERS = gstflacenc.h gstflacdec.h
+noinst_HEADERS = gstflacenc.h gstflacdec.h gstflactag.h
diff --git a/gstflac.c b/gstflac.c
index d07ec47..7d76a41 100644
--- a/gstflac.c
+++ b/gstflac.c
@@ -23,7 +23,7 @@
 
 #include "gstflacenc.h"
 #include "gstflacdec.h"
-/* #include "gstflactag.h" */
+#include "gstflactag.h"
 
 #include <gst/tag/tag.h>
 #include <gst/gst-i18n-plugin.h>
@@ -43,11 +43,9 @@ plugin_init (GstPlugin * plugin)
   if (!gst_element_register (plugin, "flacdec", GST_RANK_PRIMARY,
           GST_TYPE_FLAC_DEC))
     return FALSE;
-#if 0
   if (!gst_element_register (plugin, "flactag", GST_RANK_PRIMARY,
           gst_flac_tag_get_type ()))
     return FALSE;
-#endif
 
   gst_tag_register_musicbrainz_tags ();
 
diff --git a/gstflactag.c b/gstflactag.c
index 247e1de..ed2658f 100644
--- a/gstflactag.c
+++ b/gstflactag.c
@@ -1,6 +1,6 @@
-
 /* GStreamer
  * Copyright (C) 2003 Christophe Fergeau <teuf@gnome.org>
+ * Copyright (C) 2008 Jonathan Matthew <jonathan@d14n.org>
  *
  * gstflactag.c: plug-in for reading/modifying vorbis comments in flac files
  *
@@ -24,6 +24,7 @@
 #include "config.h"
 #endif
 #include <gst/gsttagsetter.h>
+#include <gst/base/gstadapter.h>
 #include <gst/tag/tag.h>
 #include <string.h>
 
@@ -70,11 +71,11 @@ struct _GstFlacTag
 
   GstFlacTagState state;
 
-  GstBuffer *buffer;
+  GstAdapter *adapter;
   GstBuffer *vorbiscomment;
   GstTagList *tags;
 
-  guint metadata_bytes_remaining;
+  guint metadata_block_size;
   gboolean metadata_last_block;
 
   gboolean only_output_tags;
@@ -85,12 +86,15 @@ struct _GstFlacTagClass
   GstElementClass parent_class;
 };
 
+GST_DEBUG_CATEGORY_STATIC (flactag_debug);
+#define GST_CAT_DEFAULT flactag_debug
+
 /* elementfactory information */
 static const GstElementDetails gst_flac_tag_details =
 GST_ELEMENT_DETAILS ("FLAC tagger",
     "Tag",
     "Rewrite tags in a FLAC file",
-    "Christope Fergeau <teuf@gnome.org>");
+    "Christophe Fergeau <teuf@gnome.org>");
 
 
 /* signals and args */
@@ -103,7 +107,7 @@ enum
 enum
 {
   ARG_0
-      /* FILL ME */
+  /* FILL ME */
 };
 
 static GstStaticPadTemplate flac_tag_src_template =
@@ -124,8 +128,9 @@ GST_STATIC_PAD_TEMPLATE ("sink",
 static void gst_flac_tag_base_init (gpointer g_class);
 static void gst_flac_tag_class_init (GstFlacTagClass * klass);
 static void gst_flac_tag_init (GstFlacTag * tag);
+static void gst_flac_tag_dispose (GObject *object);
 
-static void gst_flac_tag_chain (GstPad * pad, GstData * data);
+static GstFlowReturn gst_flac_tag_chain (GstPad * pad, GstBuffer * buffer);
 
 static GstStateChangeReturn gst_flac_tag_change_state (GstElement * element,
     GstStateChange transition);
@@ -181,19 +186,21 @@ gst_flac_tag_base_init (gpointer g_class)
       gst_static_pad_template_get (&flac_tag_sink_template));
   gst_element_class_add_pad_template (element_class,
       gst_static_pad_template_get (&flac_tag_src_template));
+  
+  GST_DEBUG_CATEGORY_INIT (flactag_debug, "flactag", 0, "flac tag rewriter");
 }
 
 
 static void
 send_eos (GstFlacTag * tag)
 {
-  gst_element_set_eos (GST_ELEMENT (tag));
-  gst_pad_push (tag->srcpad, GST_DATA (gst_event_new (GST_EVENT_EOS)));
-  /* Seek to end of sink stream */
-  if (gst_pad_send_event (GST_PAD_PEER (tag->sinkpad),
-          gst_event_new_seek (GST_FORMAT_BYTES | GST_SEEK_METHOD_END |
-              GST_SEEK_FLAG_FLUSH, 0))) {
-  } else {
+  GstEvent *seek;
+  gst_pad_push_event (tag->srcpad, gst_event_new_eos ());
+  seek = gst_event_new_seek (1.0, GST_FORMAT_BYTES, GST_SEEK_FLAG_NONE,
+      			     GST_SEEK_TYPE_END, 0,
+			     GST_SEEK_TYPE_NONE, 0);
+  /* Seek to end of sink stream (XXX is this at all sane?) */
+  if (gst_pad_send_event (GST_PAD_PEER (tag->sinkpad), seek) == FALSE) {
     g_warning ("Couldn't seek to eos on sinkpad\n");
   }
 }
@@ -206,21 +213,19 @@ caps_nego (GstFlacTag * tag)
   GstCaps *caps;
 
   caps = gst_caps_new_simple ("audio/x-flac", NULL);
-  if (gst_pad_try_set_caps (tag->srcpad, caps) != GST_PAD_LINK_REFUSED) {
-    tag->only_output_tags = FALSE;
+  if (gst_pad_set_caps (tag->srcpad, caps) == TRUE) {
     GST_LOG_OBJECT (tag, "normal operation, using audio/x-flac output");
-  } else {
-    if (gst_pad_try_set_caps (tag->srcpad,
-            gst_caps_new_simple ("application/x-gst-tags", NULL))
-        != GST_PAD_LINK_REFUSED) {
-      tag->only_output_tags = TRUE;
-      GST_LOG_OBJECT (tag, "fast operation, just outputting tags");
-      printf ("output tags only\n");
-    } else {
-      return FALSE;
-    }
+    return TRUE;
   }
-  return TRUE;
+
+  gst_caps_unref (caps);
+  caps = gst_caps_new_simple ("application/x-gst-tags", NULL);
+  if (gst_pad_set_caps (tag->srcpad, caps) == TRUE) {
+    GST_LOG_OBJECT (tag, "fast operation, just outputting tags");
+    return TRUE;
+  }
+  GST_LOG_OBJECT (tag, "unable to set srcpad caps");
+  return FALSE;
 }
 
 static void
@@ -234,9 +239,31 @@ gst_flac_tag_class_init (GstFlacTagClass * klass)
 
   parent_class = g_type_class_peek_parent (klass);
 
+  gobject_class->dispose = gst_flac_tag_dispose;
   gstelement_class->change_state = gst_flac_tag_change_state;
 }
 
+static void
+gst_flac_tag_dispose (GObject *object)
+{
+  GstFlacTag *tag = GST_FLAC_TAG (object);
+
+  if (tag->adapter) {
+    g_object_unref (tag->adapter);
+    tag->adapter = NULL;
+  }
+  if (tag->vorbiscomment) {
+    gst_buffer_unref (tag->vorbiscomment);
+    tag->vorbiscomment = NULL;
+  }
+  if (tag->tags) {
+    gst_tag_list_free (tag->tags);
+    tag->tags = NULL;
+  }
+
+  G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
 
 static void
 gst_flac_tag_init (GstFlacTag * tag)
@@ -252,68 +279,51 @@ gst_flac_tag_init (GstFlacTag * tag)
       gst_pad_new_from_static_template (&flac_tag_src_template, "src");
   gst_element_add_pad (GST_ELEMENT (tag), tag->srcpad);
 
-  tag->buffer = NULL;
+  tag->adapter = gst_adapter_new ();
 }
 
 #define FLAC_MAGIC "fLaC"
 #define FLAC_MAGIC_SIZE (sizeof (FLAC_MAGIC) - 1)
 
-static void
-gst_flac_tag_chain (GstPad * pad, GstData * data)
+static GstFlowReturn
+gst_flac_tag_chain (GstPad * pad, GstBuffer * buffer)
 {
-  GstBuffer *buffer;
   GstFlacTag *tag;
+  GstFlowReturn ret;
 
-  if (GST_IS_EVENT (data)) {
-    g_print ("Unhandled event\n");
-    return;
-  }
-
-  buffer = GST_BUFFER (data);
+  ret = GST_FLOW_OK;
   tag = GST_FLAC_TAG (gst_pad_get_parent (pad));
 
-  if (tag->buffer) {
-    GstBuffer *merge;
-
-    merge = gst_buffer_merge (tag->buffer, buffer);
-    gst_buffer_unref (buffer);
-    gst_buffer_unref (tag->buffer);
-    tag->buffer = merge;
-  } else {
-    tag->buffer = buffer;
-  }
-
+  gst_adapter_push (tag->adapter, buffer);
 
   /* Initial state, we don't even know if we are dealing with a flac file */
   if (tag->state == GST_FLAC_TAG_STATE_INIT) {
+    GstBuffer *id_buffer;
+
     if (!caps_nego (tag)) {
       goto cleanup;
     }
 
-    if (GST_BUFFER_SIZE (tag->buffer) < sizeof (FLAC_MAGIC)) {
+    if (gst_adapter_available (tag->adapter) < sizeof (FLAC_MAGIC)) {
       goto cleanup;
     }
 
-    if (strncmp (GST_BUFFER_DATA (tag->buffer), FLAC_MAGIC,
+    id_buffer = gst_adapter_take_buffer (tag->adapter, FLAC_MAGIC_SIZE);
+    GST_DEBUG_OBJECT (tag, "looking for " FLAC_MAGIC " identifier");
+    if (memcmp (GST_BUFFER_DATA (id_buffer), FLAC_MAGIC,
             FLAC_MAGIC_SIZE) == 0) {
-      GstBuffer *sub;
 
+      GST_DEBUG_OBJECT (tag, "pushing " FLAC_MAGIC " identifier buffer");
+      ret = gst_pad_push (tag->srcpad, id_buffer);
+      if (ret != GST_FLOW_OK) {
+	goto cleanup;
+      }
       tag->state = GST_FLAC_TAG_STATE_METADATA_BLOCKS;
-      sub = gst_buffer_create_sub (tag->buffer, 0, FLAC_MAGIC_SIZE);
-
-      gst_pad_push (tag->srcpad, GST_DATA (sub));
-      sub =
-          gst_buffer_create_sub (tag->buffer, FLAC_MAGIC_SIZE,
-          GST_BUFFER_SIZE (tag->buffer) - FLAC_MAGIC_SIZE);
-      gst_buffer_unref (tag->buffer);
-      /* We do a copy because we need a writable buffer, and _create_sub
-       * sets the buffer it uses to read-only
-       */
-      tag->buffer = gst_buffer_copy (sub);
-      gst_buffer_unref (sub);
     } else {
       /* FIXME: does that work well with FLAC files containing ID3v2 tags ? */
+      gst_buffer_unref (id_buffer);
       GST_ELEMENT_ERROR (tag, STREAM, WRONG_TYPE, (NULL), (NULL));
+      ret = GST_FLOW_ERROR;
     }
   }
 
@@ -325,8 +335,9 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
     guint size;
     guint type;
     gboolean is_last;
+    const guint8 *block_header;
 
-    g_assert (tag->metadata_bytes_remaining == 0);
+    g_assert (tag->metadata_block_size == 0);
     g_assert (tag->metadata_last_block == FALSE);
 
     /* The header of a flac metadata block is 4 bytes long:
@@ -334,26 +345,25 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
      * 7 next bits: 4 if vorbis comment block
      * 24 next bits: size of the metadata to follow (big endian)
      */
-    if (GST_BUFFER_SIZE (tag->buffer) < 4) {
+    if (gst_adapter_available (tag->adapter) < 4) {
       goto cleanup;
     }
-    is_last = (((GST_BUFFER_DATA (tag->buffer)[0]) & 0x80) == 0x80);
-    /* If we have metadata set on the element, the last metadata block 
-     * will be the vorbis comment block which we will build ourselves
-     */
-    if (is_last) {
-      (GST_BUFFER_DATA (tag->buffer)[0]) &= (~0x80);
-    }
 
-    type = (GST_BUFFER_DATA (tag->buffer)[0]) & 0x7F;
-    size = ((GST_BUFFER_DATA (tag->buffer)[1]) << 16)
-        | ((GST_BUFFER_DATA (tag->buffer)[2]) << 8)
-        | (GST_BUFFER_DATA (tag->buffer)[3]);
+    block_header = gst_adapter_peek (tag->adapter, 4);
+
+    is_last = ((block_header[0] & 0x80) == 0x80);
+    type = block_header[0] & 0x7F;
+    size = (block_header[1] << 16)
+        | (block_header[2] << 8)
+        | block_header[3];
 
     /* The 4 bytes long header isn't included in the metadata size */
-    tag->metadata_bytes_remaining = size + 4;
+    tag->metadata_block_size = size + 4;
     tag->metadata_last_block = is_last;
 
+    GST_DEBUG_OBJECT (tag, "got metadata block: %d bytes, type %d, is vorbiscomment: %d, is last: %d",
+	              size, type, (type == 0x04), is_last);
+
     /* Metadata blocks of type 4 are vorbis comment blocks */
     if (type == 0x04) {
       tag->state = GST_FLAC_TAG_STATE_VC_METADATA_BLOCK;
@@ -366,56 +376,30 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
   /* Reads a metadata block */
   if ((tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) ||
       (tag->state == GST_FLAC_TAG_STATE_VC_METADATA_BLOCK)) {
-    GstBuffer *sub;
-    guint bytes_to_push;
-
-    g_assert (tag->metadata_bytes_remaining != 0);
+    GstBuffer *metadata_buffer;
 
-    bytes_to_push = min (tag->metadata_bytes_remaining,
-        GST_BUFFER_SIZE (tag->buffer));
+    if (gst_adapter_available (tag->adapter) < tag->metadata_block_size) {
+      goto cleanup;
+    }
 
-    sub = gst_buffer_create_sub (tag->buffer, 0, bytes_to_push);
+    metadata_buffer = gst_adapter_take_buffer (tag->adapter,
+	tag->metadata_block_size);
+    /* clear the is-last flag, as the last metadata block will
+     * be the vorbis comment block which we will build ourselves.
+     */
+    GST_BUFFER_DATA (metadata_buffer)[0] &= (~0x80);
 
     if (tag->state == GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK) {
-      gst_pad_push (tag->srcpad, GST_DATA (sub));
-    } else {
-      if (tag->vorbiscomment == NULL) {
-        tag->vorbiscomment = sub;
-      } else {
-        GstBuffer *merge;
-
-        merge = gst_buffer_merge (tag->vorbiscomment, sub);
-        gst_buffer_unref (tag->vorbiscomment);
-        gst_buffer_unref (sub);
-        tag->vorbiscomment = merge;
+      GST_DEBUG_OBJECT (tag, "pushing metadata block buffer");
+      ret = gst_pad_push (tag->srcpad, metadata_buffer);
+      if (ret != GST_FLOW_OK) {
+	goto cleanup;
       }
-    }
-
-    tag->metadata_bytes_remaining -= (bytes_to_push);
-
-    if (GST_BUFFER_SIZE (tag->buffer) > bytes_to_push) {
-      GstBuffer *sub;
-
-      sub = gst_buffer_create_sub (tag->buffer, bytes_to_push,
-          GST_BUFFER_SIZE (tag->buffer) - bytes_to_push);
-      gst_buffer_unref (tag->buffer);
-
-      /* We make a copy because we need a writable buffer, and _create_sub
-       * sets the buffer it uses to read-only
-       */
-      tag->buffer = gst_buffer_copy (sub);
-      gst_buffer_unref (sub);
-
-      tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
-    } else if (tag->metadata_bytes_remaining == 0) {
-      gst_buffer_unref (tag->buffer);
-      tag->buffer = NULL;
-      tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
-      tag->buffer = NULL;
     } else {
-      tag->state = GST_FLAC_TAG_STATE_WRITING_METADATA_BLOCK;
-      tag->buffer = NULL;
+      tag->vorbiscomment = metadata_buffer;
     }
+    tag->metadata_block_size = 0;
+    tag->state = GST_FLAC_TAG_STATE_METADATA_NEXT_BLOCK;
   }
 
   /* This state is mainly used to be able to stop as soon as we read
@@ -428,18 +412,20 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
      */
     if (tag->vorbiscomment != NULL) {
       /* We found some tags, try to parse them and notify the other elements
-       * that we encoutered some tags
+       * that we encountered some tags
        */
+      GST_DEBUG_OBJECT (tag, "emitting vorbiscomment tags");
       tag->tags = gst_tag_list_from_vorbiscomment_buffer (tag->vorbiscomment,
           GST_BUFFER_DATA (tag->vorbiscomment), 4, NULL);
       if (tag->tags != NULL) {
-        gst_element_found_tags (GST_ELEMENT (tag), tag->tags);
+        gst_element_found_tags (GST_ELEMENT (tag), gst_tag_list_copy (tag->tags));
       }
 
       gst_buffer_unref (tag->vorbiscomment);
       tag->vorbiscomment = NULL;
 
       if (tag->only_output_tags) {
+	GST_DEBUG_OBJECT (tag, "in fast mode, so sending EOS");
         send_eos (tag);
         goto cleanup;
       }
@@ -453,6 +439,7 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
         /* If we finished parsing the metadata blocks, we will never find any
          * metadata, so just stop now
          */
+	GST_DEBUG_OBJECT (tag, "didn't find any tags in fast mode, so sending EOS");
         send_eos (tag);
         goto cleanup;
       } else {
@@ -473,9 +460,14 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
 
     g_assert (tag->only_output_tags == FALSE);
 
+    /* merge the tag lists */
     user_tags = gst_tag_setter_get_tag_list (GST_TAG_SETTER (tag));
-    merged_tags = gst_tag_list_merge (tag->tags, user_tags,
-        gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (tag)));
+    if (user_tags != NULL) { 
+      merged_tags = gst_tag_list_merge (user_tags, tag->tags,
+	  gst_tag_setter_get_tag_merge_mode (GST_TAG_SETTER (tag)));
+    } else {
+      merged_tags = gst_tag_list_copy (tag->tags);
+    }
 
     if (merged_tags == NULL) {
       /* If we get a NULL list of tags, we must generate a padding block
@@ -539,18 +531,25 @@ gst_flac_tag_chain (GstPad * pad, GstData * data)
     GST_BUFFER_DATA (buffer)[1] = ((size & 0xFF0000) >> 16);
     GST_BUFFER_DATA (buffer)[2] = ((size & 0x00FF00) >> 8);
     GST_BUFFER_DATA (buffer)[3] = (size & 0x0000FF);
-    gst_pad_push (tag->srcpad, GST_DATA (buffer));
+    GST_DEBUG_OBJECT (tag, "pushing %d byte vorbiscomment buffer", GST_BUFFER_SIZE (buffer));
+    ret = gst_pad_push (tag->srcpad, buffer);
+    if (ret != GST_FLOW_OK) {
+      goto cleanup;
+    }
     tag->state = GST_FLAC_TAG_STATE_AUDIO_DATA;
   }
 
   /* The metadata blocks have been read, now we are reading audio data */
   if (tag->state == GST_FLAC_TAG_STATE_AUDIO_DATA) {
-    gst_pad_push (tag->srcpad, GST_DATA (tag->buffer));
-    tag->buffer = NULL;
+    GstBuffer *buffer;
+    buffer = gst_adapter_take_buffer (tag->adapter, gst_adapter_available (tag->adapter));
+    ret = gst_pad_push (tag->srcpad, buffer);
   }
 
 cleanup:
   gst_object_unref (tag);
+
+  return ret;
 }
 
 
@@ -572,16 +571,14 @@ gst_flac_tag_change_state (GstElement * element, GstStateChange transition)
     case GST_STATE_CHANGE_PLAYING_TO_PAUSED:
       break;
     case GST_STATE_CHANGE_PAUSED_TO_READY:
-      if (tag->buffer) {
-        gst_buffer_unref (tag->buffer);
-        tag->buffer = NULL;
-      }
+      gst_adapter_clear (tag->adapter);
       if (tag->vorbiscomment) {
         gst_buffer_unref (tag->vorbiscomment);
         tag->vorbiscomment = NULL;
       }
       if (tag->tags) {
         gst_tag_list_free (tag->tags);
+	tag->tags = NULL;
       }
       tag->state = GST_FLAC_TAG_STATE_INIT;
       break;
