/*
 * gst-id3v23-plugin - Gstreamer ID3 Tags v2.3 plugin
 * Copyright 2007 - Emmauel Rodriguez <emmanuel.rodriguez@gmail.com>
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Alternatively, the contents of this file may be used under the
 * GNU Lesser General Public License Version 2.1 (the "LGPL"), in
 * which case the following provisions apply instead of the ones
 * mentioned above:
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library 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.
 */

/**
 * SECTION:id3v23
 *
 * <refsect2>
 * <title>ID3 tags in the version 2.3</title>
 * <para>
 * This plugin encodes ID3 tags in the version 2.3. The ID3 tags consist
 * of a special region in an MP3 used to store meta information about the
 * MP3. This information contains elements such a tile, artist and so on.
 *
 * The ID3 tags can be written in multiple versions: 1.0, 2.3 and 2.4. 
 * Each format has it advantages and inconveniences. The advantage of the
 * format 2.3 is that it supports UNICODE (almost all written characters 
 * used by the human kind) and that it's more widespread among players
 * than the format 2.4, for instance the reason why this plugin got written
 * is because the MP3 player Sansa e250 can't handle 2.4 tags.
 *
 * The tagging is performed through the C++ library id3lib.
 *
 * This plugin is a simple tagger. Here's a sample example on how to retag
 * an existing MP3:
 * <programlisting>
 * gst-launch filesrc location=old.mp3 ! id3demux ! id3v23 ! filesink location=new.mp3
 * </programlisting>
 * </para>
 * </refsect2>
 */

// Includes
#include <gst/gst.h>

#include <iostream>

#include <id3/tag.h>
#include <string.h>

#include "gst-id3v23-plugin.h"


// Defines
#define VERSION "0.0.2"
#define PACKAGE "id3v23"

#define BOOLEAN(value) ( (value) ? "TRUE" : "FALSE" )

#define TAG_ADD_FRAME(tag, frame) if (frame != NULL) {tag.AddFrame(frame);}

// Debug
GST_DEBUG_CATEGORY_STATIC (id3v23_debug);
#define GST_CAT_DEFAULT id3v23_debug


// Function used to write a gstreamer tag into IDv2.3
static typedef ID3_Frame* (*TagWriterFunction) (
	const GstId3v23   *tagger, 
	const gchar       *tag,
	const ID3_FrameID id
);


static typedef struct _TagWriterMapping TagWriterMapping;
struct _TagWriterMapping {
	ID3_FrameID       id;
	TagWriterFunction function;
};


// Gstreamer initialization
static gboolean plugin_init (
	GstPlugin *plugin
);

// Class stuff (constructors, destructors, etc)
static void gst_id3v23_base_init (
	gpointer gclass
);

static void gst_id3v23_class_init (
	GstId3v23Class *klass
);

static void gst_id3v23_iface_init (
	GType taglib_type
);

static void gst_id3v23_init (
	GstId3v23      *tagger, 
	GstId3v23Class *gclas
);

static void gst_id3v23_finalize (
	GObject *object
);


static GstFlowReturn gst_id3v23_chain (
	GstPad    *pad, 
	GstBuffer *buffer
);

static gboolean gst_id3v23_sink_event (
	GstPad   *pad, 
	GstEvent *event
);


// Custom methods and functions
static void tags_print_loop (
	const GstTagList *tags, 
	const gchar      *tag, 
	const gpointer   user_data
);

static GstBuffer* tags_to_gst_buffer (
	const GstTagList *tags
);

static ID3_Frame* tags_tag_to_frame (
	const GstTagList  *tags,
	const gchar       *tag,
	const ID3_FrameID id
);

static ID3_Frame* tags_text_to_frame (
	const gchar       *value,
	const ID3_FrameID id
);

static ID3_Frame* tags_composed_tags_to_frame (
	const GstTagList  *tags,
	const gchar       *left,
	const gchar       *right,
	const ID3_FrameID id
);

static gchar* tags_tag_to_string (
	const GstTagList *tags, 
	const gchar      *tag
);

static GDate* tags_tag_to_date(
	const GstTagList *tags, 
	const gchar      *tag
);

static size_t utils_number_length (
	const guint i
);

static unicode_t* utils_utf8_to_utf16 (
	const gchar *text
);


//
// Entry point to initialize the plug-in.
//
// Initialize the plug-in itself.
// Register the element factories and pad templates.
// Register the features.
//
static gboolean plugin_init (
	GstPlugin *plugin
) {

	GST_DEBUG_CATEGORY_INIT(
		id3v23_debug, 
		PACKAGE,
		0, 
		"ID3 Tags in version 2.3"
	);

	GST_INFO("Plugin " PACKAGE " is initialized");

	return gst_element_register(plugin, PACKAGE, GST_RANK_NONE, GST_TYPE_ID3V23);
}


//
// Class definition
//
GST_BOILERPLATE_FULL(
	GstId3v23, 
	gst_id3v23, 
	GstElement, 
	GST_TYPE_ELEMENT,
	gst_id3v23_iface_init
);


//
// Implement GST_TYPE_TAG_SETTER
//
static void gst_id3v23_iface_init (
	GType taglib_type
) {

  static const GInterfaceInfo tag_setter_info = {
    NULL,
    NULL,
    NULL
  };

  g_type_add_interface_static(taglib_type, GST_TYPE_TAG_SETTER, &tag_setter_info);
}

//
// Gstreamer type registration
//
static void gst_id3v23_base_init (
	gpointer gclass
) {

	static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE(
		"sink",
		GST_PAD_SINK,
		GST_PAD_ALWAYS,
		GST_STATIC_CAPS("ANY")
	);

	static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE(
		"src",
		GST_PAD_SRC,
		GST_PAD_ALWAYS,
		//GST_STATIC_CAPS("application/x-id3")
		GST_STATIC_CAPS("ANY")
	);

	static GstElementDetails element_details = {
		"Tags in ID3v2.3",
		"Formatter/Metadata",
		"Writes ID3 version 2.3 tags",
		"Emmanuel Rodriguez <emmanuel.rodriguez@gmail.com>"
	};
	
	GstElementClass *element_class = GST_ELEMENT_CLASS(gclass);

	gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&src_factory));
	gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sink_factory));
	gst_element_class_set_details(element_class, &element_details);
}


//
// Initialize the plugin's class
//
static void gst_id3v23_class_init (
	GstId3v23Class *klass
) {
	GObjectClass *gobject_class = (GObjectClass *) klass;
	gobject_class->finalize = gst_id3v23_finalize;
}


//
// Initialize the new element, instantiate pads and add them to element,
// set functions and initialize structure.
//
static void gst_id3v23_init (
	GstId3v23      *tagger, 
	GstId3v23Class *gclass
) {
	GstElementClass *klass = GST_ELEMENT_GET_CLASS(tagger);

	// Incomming data
	GstPadTemplate *pad_template = gst_element_class_get_pad_template(klass, "sink");
	tagger->sinkpad = gst_pad_new_from_template(pad_template, "sink");
	gst_pad_set_chain_function(tagger->sinkpad, GST_DEBUG_FUNCPTR(gst_id3v23_chain));
	gst_pad_set_event_function(tagger->sinkpad, GST_DEBUG_FUNCPTR(gst_id3v23_sink_event));
	gst_element_add_pad(GST_ELEMENT(tagger), tagger->sinkpad);


	// Outgoind data
	pad_template = gst_element_class_get_pad_template(klass, "src");
	tagger->srcpad = gst_pad_new_from_template(pad_template, "src");
	gst_pad_use_fixed_caps(tagger->srcpad);
	gst_pad_set_caps(tagger->srcpad, gst_pad_template_get_caps(pad_template));
	gst_element_add_pad(GST_ELEMENT(tagger), tagger->srcpad);


	// Initialize the data members
	tagger->tags = gst_tag_list_new();
	tagger->render_tags = FALSE;
}


//
// Cleanup
//
static void gst_id3v23_finalize (
	GObject *object
) {
	
	GstId3v23 *tagger = GST_ID3V23(object);
	
	gst_tag_list_free(tagger->tags);
	
  G_OBJECT_CLASS(parent_class)->finalize(object);
}



// ------------------
//  Instance methods
// ------------------


//
// Chain function, this function does the actual processing.
// Required by the Gstreamer framework.
//
static GstFlowReturn gst_id3v23_chain (
	GstPad    *pad, 
	GstBuffer *buffer
) {

	GstId3v23 *tagger = GST_ID3V23(GST_OBJECT_PARENT(pad));


	// Cool we got tags
	if (tagger->render_tags) {

		// Merge the tags
		GstTagSetter *tagsetter = GST_TAG_SETTER(tagger);
		const GstTagList *tagsetter_tags = gst_tag_setter_get_tag_list(tagsetter);
		GstTagMergeMode merge_mode = gst_tag_setter_get_tag_merge_mode(tagsetter);
		GstTagList *taglist = gst_tag_list_merge(tagsetter_tags, tagger->tags, merge_mode);


		// Write the tags into a buffer
		GstBuffer *tags_buffer = tags_to_gst_buffer(taglist);
		GST_LOG("Tags where written into a buffer of %u bytes", GST_BUFFER_SIZE(tags_buffer));

		// Send newsegment event from byte position 0, so the tag really gets
		// written to the start of the file, independent of the upstream segment
		// COPIED from gsttaglibmux.c
		GstEvent* event = gst_event_new_new_segment(FALSE, 1.0, GST_FORMAT_BYTES, 0, -1, 0);
		gst_pad_push_event(tagger->srcpad, event);

		// Send an event about the new tags to downstream elements
		// gst_event_new_tag takes ownership of the list, so no need to unref it
		// COPIED from gsttaglibmux.c
		event = gst_event_new_tag(taglist);
		gst_pad_push_event(tagger->srcpad, event);
		GST_BUFFER_OFFSET(buffer) = 0;
		
		// Push the tags into the pipeline
		GstFlowReturn return_value = gst_pad_push(tagger->srcpad, tags_buffer);
		
		// Check if everything went fine
		if (return_value != GST_FLOW_OK) {
			gst_buffer_unref(buffer);
			return return_value;
		}
		
		tagger->render_tags = FALSE;
	}


	// Just push out the incoming buffer without touching it
	return gst_pad_push(tagger->srcpad, buffer);
}


//
// Called when an event is raised. In our case we are only interested when 
// tags are parsed and detected.
//
static gboolean gst_id3v23_sink_event (
	GstPad   *pad, 
	GstEvent *event
) {

	GstId3v23 *tagger = GST_ID3V23(GST_OBJECT_PARENT(pad));
	
	gboolean result = FALSE;
	switch (GST_EVENT_TYPE(event)) {
		case GST_EVENT_TAG: 
		{
			GST_INFO("Received tags through a gstreamer event.");
			
			// Parse the tags
			GstTagList *tags;
			gst_event_parse_tag(event, &tags);
			
			// Cache the tags
			gst_tag_list_insert(tagger->tags, tags, GST_TAG_MERGE_REPLACE);

      // Drop the event
			gst_event_unref(event);

			// Return code
			result = TRUE;
			
			// Remeber that we found some tags
			tagger->render_tags = TRUE;
		}
		break;
		
		default:
		{
			// Ask gstreamer to consume the event
			result = gst_pad_event_default(pad, event);
		}
		break;
	}
	
	return result;
}


//
// Prints a tag from the given tag list.
//
// This function is meant to be used by gst_tag_list_foreach().
//
static void tags_print_loop (
	const GstTagList *tags, 
	const gchar      *tag, 
	gpointer         user_data
) {
	
	gchar *value = tags_tag_to_string(tags, tag);
	g_free(value);
	
	return;
}


//
// This function writes the gstreamer tags that have been collected so far.
//
static GstBuffer* tags_to_gst_buffer (
	const GstTagList *tags
) {
	

	// Print the tags (DEBUG)
	gst_tag_list_foreach(tags, tags_print_loop, NULL);
	
	// Trivial frames (tag -> frame)
	ID3_Frame *title = tags_tag_to_frame(tags, GST_TAG_TITLE, ID3FID_TITLE);	
	ID3_Frame *album = tags_tag_to_frame(tags, GST_TAG_ALBUM, ID3FID_ALBUM);	
	ID3_Frame *artist = tags_tag_to_frame(tags, GST_TAG_ARTIST, ID3FID_LEADARTIST);	
	ID3_Frame *genre = tags_tag_to_frame(tags, GST_TAG_GENRE, ID3FID_CONTENTTYPE);
	
	// Composed frames (two gst tags -> 1 frame)
	ID3_Frame *track_number = tags_composed_tags_to_frame(tags, GST_TAG_TRACK_NUMBER, GST_TAG_TRACK_COUNT, ID3FID_TRACKNUM);
	ID3_Frame *part_in_set = tags_composed_tags_to_frame(tags, GST_TAG_ALBUM_VOLUME_NUMBER, GST_TAG_ALBUM_VOLUME_COUNT, ID3FID_PARTINSET);


	GDate *track_date = tags_tag_to_date(tags, GST_TAG_DATE);
	ID3_Frame *frame_year = NULL;
	ID3_Frame *frame_date = NULL;
	if (track_date != NULL) {
		
		// The year frame format YYYY
		GDateYear year = g_date_get_year(track_date);
		if (year != G_DATE_BAD_YEAR) {
			gchar *value = g_strdup_printf("%04u", year);
			frame_year = tags_text_to_frame(value, ID3FID_YEAR);
			g_free(value);
		}
	
		// The date frame format DDMM
		GDateMonth month = g_date_get_month(track_date);
		GDateDay day = g_date_get_day(track_date);
		if (month != G_DATE_BAD_MONTH && day != G_DATE_BAD_DAY) {
			gchar *value = g_strdup_printf("%02u%02u", day, month);
			frame_date = tags_text_to_frame(value, ID3FID_DATE);
			g_free(value);
		}
	}
	g_free(track_date);
	

	// Add the frames to the tag
	ID3_Tag tag;
	TAG_ADD_FRAME(tag, title);
	TAG_ADD_FRAME(tag, artist);
	TAG_ADD_FRAME(tag, album);
	TAG_ADD_FRAME(tag, part_in_set);
	TAG_ADD_FRAME(tag, track_number);
	TAG_ADD_FRAME(tag, genre);
	TAG_ADD_FRAME(tag, frame_year);
	TAG_ADD_FRAME(tag, frame_date);
	
	// Write the tag's binary data into a gstreamer buffer
	int length = tag.Size() * (sizeof(unicode_t)/sizeof(uchar));
	uchar *data = new uchar[length];
	tag.Render(data, ID3TT_ID3V2);
	GstBuffer *buffer = gst_buffer_new_and_alloc(length);
	memcpy(GST_BUFFER_DATA(buffer), data, length);
	
	return buffer;
}


//
// Returns a frame who's value is composed of two numeric tags.
// Ideally this function is used to return frames in the fashion:
//   "01/10"  (Ideal for track numbers and parts in sets).
//
// If the right tag is missing then the left tag will be returned
// alone in a string.
//
// The frame will have the numbers padded with 0 at the beginning.
// The number of zeros to use depends on the number of digits used
// by the longest number.
//
// Parameters:
//   tagger: the plugin used to collect the tags.
//   left:   the left tag to lookup.
//   right:  the right tag to lookup.
//
// Returns:
//   The value of the tag as a string or NULL if the tag couldn't be found.
//
//
static ID3_Frame* tags_composed_tags_to_frame(
	const GstTagList  *tags, 
	const gchar       *left,
	const gchar       *right,
	const ID3_FrameID id
) {
	
	// The values to render
	gboolean found;
	guint left_value;
	found = gst_tag_list_get_uint(tags, left, &left_value);
	if (! found) {
		// Assume that his is the first item of composed group (set or track) 
		left_value = 1;
	}
	
	// Check if the right value is there
	guint right_value = left_value;
	found = gst_tag_list_get_uint(tags, right, &right_value);
	if (! found) {
		// Return a single value
		gchar *as_string = g_strdup_printf("%u", left_value);
		ID3_Frame *frame = tags_text_to_frame(as_string, id);
		g_free(as_string);
		
		return frame;
	}
	
	
	// Compute the format, it depends on the longest number (number of digits)
	size_t left_length = utils_number_length(left_value);
	size_t right_length = utils_number_length(right_value);
	size_t length = left_length < right_length ? right_length : left_length;
	gchar *format = g_strdup_printf("%%0%uu/%%0%uu", length, length);

	// The text to add to the content of the tag
	gchar *composed = g_strdup_printf(format, left_value, right_value);
	g_free(format);


	ID3_Frame *frame = tags_text_to_frame(composed, id);
	g_free(composed);

	return frame;
}


//
// Returns the length (number of digits) of the given number.
//
static size_t utils_number_length(const guint i) {
	// Compute the length by counting the number
	// of characters in the string representation
	gchar *string = g_strdup_printf("%u", i);
	size_t length = strlen(string);
	g_free(string);
	return length;
}


//
// Returns the value of the given tag as an UTF-8 string.
// If the tag holds a value that's not a string, the value
// will be converted to a string.
//
//
// Parameters:
//   tags: the tags collected so far.
//   tag:  the tag to lookup.
//
// Returns:
//   The value of the tag as a string or NULL if the tag couldn't be found.
//   The value has to be freed with g_free.
//
static gchar* tags_tag_to_string(
	const GstTagList *tags, 
	const gchar      *tag
) {

	gchar *value = NULL;
	gboolean copied = FALSE;

	GType type = gst_tag_get_type(tag);
	switch (type) {
	
		case G_TYPE_STRING:
		{
			copied = gst_tag_list_get_string(tags, tag, &value);
			if (copied) {
				GST_LOG("Tag %s has a STRING of value %s", tag, value);
			}
		}
		break;
		
		case G_TYPE_UINT:
		{
			guint tmp;
			copied = gst_tag_list_get_uint(tags, tag, &tmp);
			if (copied) {
				value = g_strdup_printf("%u", tmp);
				GST_LOG("Tag %s has an UINT of value %s", tag, value);
			}
		}
		break;
		
		case G_TYPE_UINT64:
		{
			guint64 tmp;
			gst_tag_list_get_uint64(tags, tag, &tmp);
			if (copied) {
				value = g_strdup_printf("%" G_GUINT64_FORMAT, tmp);
				GST_LOG("Tag %s has an UINT64 of value %s", tag, value);
			}
		}
		break;
		
		default:
		{
		
			// Some types are not defined through constants but through functions!
			// In such a case it's impossible to use a switch/case
			if (type == GST_TYPE_DATE || type == G_TYPE_DATE) {
				GDate *tmp = NULL;
				copied = gst_tag_list_get_date(tags, tag, &tmp);
				if (copied) {
					GDateYear year = g_date_get_year(tmp);
					if (copied) {
						value = g_strdup_printf("%u", year);
						GST_LOG("Tag %s has a DATE with year %s", tag, value);
					}
				}
				g_free(tmp);
				break;
			}

			GST_WARNING("Tag %s is a non supported type (%s)", tag, g_type_name(type));
			value = g_strdup("");
			copied = TRUE;
		}
	}


	if (! copied) {
		value = NULL;
	}
	
	return value;
}


//
// Returns the value of the given tag as a date.
//
//
// Parameters:
//   tags: the tags collected so far.
//   tag:  the tag to lookup.
//
// Returns:
//   The value of the tag as a date or NULL if the tag can't be found.
//   The return value has to be freed with g_free.
//
static GDate* tags_tag_to_date(
	const GstTagList *tags,
	const gchar      *tag
) {
	GDate *tmp = NULL;
	gboolean copied = gst_tag_list_get_date(tags, tag, &tmp);
	if (! copied) {return NULL;}
	return tmp;
}


//
// Converts a tag into an ID3 tag.
// The tag will be written as a string, no matter the type of the tag.
//
//
// Parameters:
//   tags: the tags collected so far.
//   tag:  the tag to lookup.
//
// Returns:
//   The value of the tag as a sting or NULL if the tag can't be found.
// 
//
static ID3_Frame* tags_tag_to_frame (
	const GstTagList  *tags, 
	const gchar       *tag,
	const ID3_FrameID id
) {
	
	// Set the convert the text value to UTF-16
	guint size = gst_tag_list_get_tag_size(tags, tag);
	if (size > 1) {
		GST_WARNING("Tag %s has more than one value (%d), but only one tag will be written", tag, size);
	}
	
	gchar *value = tags_tag_to_string(tags, tag);
	if (value == NULL) {return NULL;}
	
	ID3_Frame *frame = tags_text_to_frame(value, id);
	g_free(value);
	
	return frame;
}

//
// Converts a glib string (UTF-8) into an ID3 frame.
//
//
// Parameters:
//   value: the value of the tag.
//   id:    the ID3 frame ID.
//
// Returns:
//   The corresponding ID3_Frame.
//
static ID3_Frame* tags_text_to_frame (
	const gchar       *value,
	const ID3_FrameID id
) {
	
	// Set the convert the text value to UTF-16
	unicode_t *utf16 = utils_utf8_to_utf16(value);
	
	// Create a frame for the value and set the encoding to UTF-16
	ID3_Frame *frame = new ID3_Frame(id);
	ID3_Field *field = frame->GetField(ID3FN_TEXT);
	field->SetEncoding(ID3TE_UTF16);
	field->Set(utf16);
		
	ID3_Field *encoding = frame->GetField(ID3FN_TEXTENC);
	encoding->Set(ID3TE_UTF16);
	delete [] utf16;

	return frame;
}


//
// Converts an UTF-8 string to UTF-16.
//
// The conversion is made with g_convert.
//
// The UTF-16 string returned is specially made for libid3 which seems to be 
// very picky about UNICODE strings.
//
// Parameters:
//   text:     the original string in UTF-8.
//
// Returns:
//   The string in UTF-16, this string must be freed with g_free.
//
static unicode_t* utils_utf8_to_utf16 (
	const gchar *text 
) {
	
	// Perform the conversion
	GError *error = NULL;
	gsize bytes_written = 0;
	gchar *converted = g_convert(
		text, -1, 
		"UTF-16BE", "UTF-8",
		NULL, &bytes_written, 
		&error
	);
	
	// Check that all was perfect
	if (error != NULL) {
		GST_WARNING("Converstion from %s to %s failed: %s", "UTF-8", "UTF-16BE", error->message);
		g_error_free(error);
	}
	
	// Just in case that the conversion returns NULL without an error
	if (converted == NULL) {
		return NULL;
	}
	
	// g_convert seems to end the strings with a single null character but an 
	// UTF-16 string needs two of them. In order to correct this, the string has
	// to be resized of one character more and then it needs to be added the 
	// extra null character.
	converted = (gchar *) g_realloc(converted, bytes_written + 2);
	converted[bytes_written] = 0;
	converted[bytes_written + 1] = 0;

	return (unicode_t *) converted;
}


//
// This is the structure that gstreamer looks for to register plugins
//
GST_PLUGIN_DEFINE (
	GST_VERSION_MAJOR,
	GST_VERSION_MINOR,
	PACKAGE,
	"ID3 Tags in version 2.3",
	plugin_init, 
	VERSION, 
	"LGPL", 
	PACKAGE, 
	"http://www.potyl.com/"
)
