mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
APE v1/2 tag reader plus typefind function.
Original commit message from CVS: * configure.ac: * gst/apetag/Makefile.am: * gst/apetag/apedemux.c: (gst_ape_demux_get_type), (gst_ape_demux_base_init), (gst_ape_demux_class_init), (gst_ape_demux_init), (gst_ape_demux_get_src_formats), (gst_ape_demux_get_src_query_types), (gst_ape_demux_handle_src_query), (gst_ape_demux_get_event_mask), (gst_ape_demux_handle_src_event), (gst_ape_demux_handle_event), (gst_ape_demux_typefind_peek), (gst_ape_demux_typefind_get_length), (gst_ape_demux_typefind_suggest), (gst_ape_demux_typefind), (gst_ape_demux_parse_tags), (gst_ape_demux_stream_init), (gst_ape_demux_stream_data), (gst_ape_demux_loop), (gst_ape_demux_change_state): * gst/apetag/apedemux.h: * gst/apetag/apetag.c: (plugin_init): * gst/typefind/gsttypefindfunctions.c: (apetag_type_find), (plugin_init): APE v1/2 tag reader plus typefind function.
This commit is contained in:
parent
56c00b370a
commit
a9434b9d75
6 changed files with 911 additions and 1 deletions
21
ChangeLog
21
ChangeLog
|
@ -1,3 +1,24 @@
|
||||||
|
2004-11-25 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
|
||||||
|
* configure.ac:
|
||||||
|
* gst/apetag/Makefile.am:
|
||||||
|
* gst/apetag/apedemux.c: (gst_ape_demux_get_type),
|
||||||
|
(gst_ape_demux_base_init), (gst_ape_demux_class_init),
|
||||||
|
(gst_ape_demux_init), (gst_ape_demux_get_src_formats),
|
||||||
|
(gst_ape_demux_get_src_query_types),
|
||||||
|
(gst_ape_demux_handle_src_query), (gst_ape_demux_get_event_mask),
|
||||||
|
(gst_ape_demux_handle_src_event), (gst_ape_demux_handle_event),
|
||||||
|
(gst_ape_demux_typefind_peek), (gst_ape_demux_typefind_get_length),
|
||||||
|
(gst_ape_demux_typefind_suggest), (gst_ape_demux_typefind),
|
||||||
|
(gst_ape_demux_parse_tags), (gst_ape_demux_stream_init),
|
||||||
|
(gst_ape_demux_stream_data), (gst_ape_demux_loop),
|
||||||
|
(gst_ape_demux_change_state):
|
||||||
|
* gst/apetag/apedemux.h:
|
||||||
|
* gst/apetag/apetag.c: (plugin_init):
|
||||||
|
* gst/typefind/gsttypefindfunctions.c: (apetag_type_find),
|
||||||
|
(plugin_init):
|
||||||
|
APE v1/2 tag reader plus typefind function.
|
||||||
|
|
||||||
2004-11-25 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
2004-11-25 Ronald S. Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
|
||||||
* configure.ac:
|
* configure.ac:
|
||||||
|
|
|
@ -329,6 +329,7 @@ GST_PLUGINS_ALL="\
|
||||||
ac3parse \
|
ac3parse \
|
||||||
adder \
|
adder \
|
||||||
alpha \
|
alpha \
|
||||||
|
apetag \
|
||||||
asfdemux \
|
asfdemux \
|
||||||
audioconvert \
|
audioconvert \
|
||||||
audioscale \
|
audioscale \
|
||||||
|
@ -1846,12 +1847,13 @@ gst/Makefile
|
||||||
gst/ac3parse/Makefile
|
gst/ac3parse/Makefile
|
||||||
gst/adder/Makefile
|
gst/adder/Makefile
|
||||||
gst/alpha/Makefile
|
gst/alpha/Makefile
|
||||||
|
gst/apetag/Makefile
|
||||||
|
gst/asfdemux/Makefile
|
||||||
gst/audioconvert/Makefile
|
gst/audioconvert/Makefile
|
||||||
gst/audioscale/Makefile
|
gst/audioscale/Makefile
|
||||||
gst/audiorate/Makefile
|
gst/audiorate/Makefile
|
||||||
gst/auparse/Makefile
|
gst/auparse/Makefile
|
||||||
gst/avi/Makefile
|
gst/avi/Makefile
|
||||||
gst/asfdemux/Makefile
|
|
||||||
gst/cdxaparse/Makefile
|
gst/cdxaparse/Makefile
|
||||||
gst/chart/Makefile
|
gst/chart/Makefile
|
||||||
gst/colorspace/Makefile
|
gst/colorspace/Makefile
|
||||||
|
|
11
gst/apetag/Makefile.am
Normal file
11
gst/apetag/Makefile.am
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
plugin_LTLIBRARIES = libgstapetag.la
|
||||||
|
|
||||||
|
libgstapetag_la_SOURCES = \
|
||||||
|
apedemux.c \
|
||||||
|
apetag.c
|
||||||
|
libgstapetag_la_CFLAGS = $(GST_CFLAGS)
|
||||||
|
libgstapetag_la_LIBADD =
|
||||||
|
libgstapetag_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
|
||||||
|
|
||||||
|
noinst_HEADERS = \
|
||||||
|
apedemux.h
|
772
gst/apetag/apedemux.c
Normal file
772
gst/apetag/apedemux.c
Normal file
|
@ -0,0 +1,772 @@
|
||||||
|
/* GStreamer APEv1/2 tag reader
|
||||||
|
* Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <gst/gst.h>
|
||||||
|
#include <gst/bytestream/bytestream.h>
|
||||||
|
|
||||||
|
#include "apedemux.h"
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_STATIC (apedemux_debug);
|
||||||
|
#define GST_CAT_DEFAULT apedemux_debug
|
||||||
|
|
||||||
|
static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
|
||||||
|
GST_PAD_SINK,
|
||||||
|
GST_PAD_ALWAYS,
|
||||||
|
GST_STATIC_CAPS ("application/x-apetag")
|
||||||
|
);
|
||||||
|
|
||||||
|
static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
|
||||||
|
GST_PAD_SRC,
|
||||||
|
GST_PAD_SOMETIMES, /* spider/decodebin hack */
|
||||||
|
GST_STATIC_CAPS_ANY);
|
||||||
|
|
||||||
|
static void gst_ape_demux_base_init (GstApeDemuxClass * klass);
|
||||||
|
static void gst_ape_demux_class_init (GstApeDemuxClass * klass);
|
||||||
|
static void gst_ape_demux_init (GstApeDemux * ape);
|
||||||
|
|
||||||
|
static void gst_ape_demux_loop (GstElement * element);
|
||||||
|
|
||||||
|
static const GstEventMask *gst_ape_demux_get_event_mask (GstPad * pad);
|
||||||
|
static gboolean gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event);
|
||||||
|
static const GstFormat *gst_ape_demux_get_src_formats (GstPad * pad);
|
||||||
|
static const GstQueryType *gst_ape_demux_get_src_query_types (GstPad * pad);
|
||||||
|
static gboolean gst_ape_demux_handle_src_query (GstPad * pad,
|
||||||
|
GstQueryType type, GstFormat * format, gint64 * value);
|
||||||
|
|
||||||
|
static GstElementStateReturn gst_ape_demux_change_state (GstElement * element);
|
||||||
|
|
||||||
|
static GstElementClass *parent_class = NULL;
|
||||||
|
|
||||||
|
GType
|
||||||
|
gst_ape_demux_get_type (void)
|
||||||
|
{
|
||||||
|
static GType ape_demux_type = 0;
|
||||||
|
|
||||||
|
if (!ape_demux_type) {
|
||||||
|
static const GTypeInfo ape_demux_info = {
|
||||||
|
sizeof (GstApeDemuxClass),
|
||||||
|
(GBaseInitFunc) gst_ape_demux_base_init,
|
||||||
|
NULL,
|
||||||
|
(GClassInitFunc) gst_ape_demux_class_init,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
sizeof (GstApeDemux),
|
||||||
|
0,
|
||||||
|
(GInstanceInitFunc) gst_ape_demux_init,
|
||||||
|
};
|
||||||
|
|
||||||
|
ape_demux_type =
|
||||||
|
g_type_register_static (GST_TYPE_ELEMENT,
|
||||||
|
"GstApeDemux", &ape_demux_info, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ape_demux_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_base_init (GstApeDemuxClass * klass)
|
||||||
|
{
|
||||||
|
static GstElementDetails gst_ape_demux_details =
|
||||||
|
GST_ELEMENT_DETAILS ("Ape tag reader",
|
||||||
|
"Codec/Demuxer/Audio",
|
||||||
|
"Reads APEv1/2 tags",
|
||||||
|
"Ronald Bultje <rbultje@ronald.bitfreak.net>");
|
||||||
|
GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
|
||||||
|
|
||||||
|
gst_element_class_add_pad_template (element_class,
|
||||||
|
gst_static_pad_template_get (&sink_templ));
|
||||||
|
gst_element_class_add_pad_template (element_class,
|
||||||
|
gst_static_pad_template_get (&src_templ));
|
||||||
|
gst_element_class_set_details (element_class, &gst_ape_demux_details);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_class_init (GstApeDemuxClass * klass)
|
||||||
|
{
|
||||||
|
GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
|
||||||
|
|
||||||
|
GST_DEBUG_CATEGORY_INIT (apedemux_debug, "apedemux",
|
||||||
|
0, "Demuxer for APE tag reader");
|
||||||
|
|
||||||
|
parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
|
||||||
|
|
||||||
|
gstelement_class->change_state = gst_ape_demux_change_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_init (GstApeDemux * ape)
|
||||||
|
{
|
||||||
|
GST_FLAG_SET (ape, GST_ELEMENT_EVENT_AWARE);
|
||||||
|
|
||||||
|
ape->sinkpad =
|
||||||
|
gst_pad_new_from_template (gst_static_pad_template_get (&sink_templ),
|
||||||
|
"sink");
|
||||||
|
gst_element_add_pad (GST_ELEMENT (ape), ape->sinkpad);
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
ape->srcpad =
|
||||||
|
gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
|
||||||
|
"src");
|
||||||
|
gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
|
||||||
|
gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
|
||||||
|
gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
|
||||||
|
gst_pad_set_query_type_function (ape->srcpad,
|
||||||
|
gst_ape_demux_get_src_query_types);
|
||||||
|
gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
|
||||||
|
gst_pad_use_explicit_caps (ape->srcpad);
|
||||||
|
gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);
|
||||||
|
#endif
|
||||||
|
ape->srcpad = NULL;
|
||||||
|
|
||||||
|
gst_element_set_loop_function (GST_ELEMENT (ape), gst_ape_demux_loop);
|
||||||
|
|
||||||
|
ape->state = GST_APE_DEMUX_TAGREAD;
|
||||||
|
ape->start_off = ape->end_off = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const GstFormat *
|
||||||
|
gst_ape_demux_get_src_formats (GstPad * pad)
|
||||||
|
{
|
||||||
|
static const GstFormat formats[] = {
|
||||||
|
GST_FORMAT_BYTES,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
return formats;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const GstQueryType *
|
||||||
|
gst_ape_demux_get_src_query_types (GstPad * pad)
|
||||||
|
{
|
||||||
|
static const GstQueryType types[] = {
|
||||||
|
GST_QUERY_TOTAL,
|
||||||
|
GST_QUERY_POSITION,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_ape_demux_handle_src_query (GstPad * pad,
|
||||||
|
GstQueryType type, GstFormat * format, gint64 * value)
|
||||||
|
{
|
||||||
|
GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));
|
||||||
|
gboolean res;
|
||||||
|
|
||||||
|
res = gst_pad_query (GST_PAD_PEER (ape->sinkpad), type, format, value);
|
||||||
|
if (!res)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case GST_QUERY_TOTAL:
|
||||||
|
switch (*format) {
|
||||||
|
case GST_FORMAT_BYTES:
|
||||||
|
*value -= (ape->start_off + ape->end_off);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GST_QUERY_POSITION:
|
||||||
|
switch (*format) {
|
||||||
|
case GST_FORMAT_BYTES:
|
||||||
|
*value -= ape->start_off;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const GstEventMask *
|
||||||
|
gst_ape_demux_get_event_mask (GstPad * pad)
|
||||||
|
{
|
||||||
|
static const GstEventMask masks[] = {
|
||||||
|
{GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT},
|
||||||
|
{0,}
|
||||||
|
};
|
||||||
|
|
||||||
|
return masks;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event)
|
||||||
|
{
|
||||||
|
GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));
|
||||||
|
|
||||||
|
if (ape->state != GST_APE_DEMUX_IDENTITY)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
|
case GST_EVENT_SEEK:
|
||||||
|
switch (GST_EVENT_SEEK_FORMAT (event)) {
|
||||||
|
case GST_FORMAT_BYTES:{
|
||||||
|
GstEvent *new;
|
||||||
|
gint64 new_off;
|
||||||
|
|
||||||
|
new_off = GST_EVENT_SEEK_OFFSET (event);
|
||||||
|
new_off += ape->start_off;
|
||||||
|
new = gst_event_new_seek (GST_EVENT_SEEK_TYPE (event), new_off);
|
||||||
|
gst_event_unref (event);
|
||||||
|
event = new;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gst_pad_send_event (GST_PAD_PEER (ape->sinkpad), event);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Handle an event during 'open' stage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_ape_demux_handle_event (GstApeDemux * ape, GstByteStream * bs)
|
||||||
|
{
|
||||||
|
GstEvent *event;
|
||||||
|
guint32 remaining;
|
||||||
|
gboolean res = FALSE;
|
||||||
|
|
||||||
|
gst_bytestream_get_status (bs, &remaining, &event);
|
||||||
|
if (!event) {
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL), (NULL));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
|
/* this shouldn't happen. We definately can't deal with it. */
|
||||||
|
case GST_EVENT_EOS:
|
||||||
|
case GST_EVENT_INTERRUPT:
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL),
|
||||||
|
("Cannot deal with EOS/interrupt events during init stage"));
|
||||||
|
break;
|
||||||
|
case GST_EVENT_DISCONTINUOUS:
|
||||||
|
case GST_EVENT_FLUSH:
|
||||||
|
/* we disregard those during init stage */
|
||||||
|
res = TRUE;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
gst_pad_event_default (ape->sinkpad, event);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_event_unref (event);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find media type. Simple for now.
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct _GstApeDemuxTypeFind
|
||||||
|
{
|
||||||
|
GstApeDemux *ape;
|
||||||
|
GstByteStream *bs;
|
||||||
|
gboolean seekable;
|
||||||
|
guint64 len;
|
||||||
|
GstCaps *caps;
|
||||||
|
guint probability;
|
||||||
|
gboolean stop;
|
||||||
|
} GstApeDemuxTypeFind;
|
||||||
|
|
||||||
|
static guint8 *
|
||||||
|
gst_ape_demux_typefind_peek (gpointer ptr, gint64 offset, guint size)
|
||||||
|
{
|
||||||
|
GstApeDemuxTypeFind *apetf = ptr;
|
||||||
|
guint8 *data;
|
||||||
|
|
||||||
|
/* non-seekable first - easy */
|
||||||
|
if (!apetf->seekable || offset == 0) {
|
||||||
|
/* don't seek outside reach */
|
||||||
|
if (offset != 0 || size > apetf->len)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* try to get data, fatal event *is* fatal for typefinding */
|
||||||
|
while (gst_bytestream_peek_bytes (apetf->bs, &data, size) != size) {
|
||||||
|
if (!gst_ape_demux_handle_event (apetf->ape, apetf->bs)) {
|
||||||
|
apetf->stop = TRUE;
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FIXME: theoretically we could embed mp3 and we'd like to seek
|
||||||
|
* beyond just the beginnings then. */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint64
|
||||||
|
gst_ape_demux_typefind_get_length (gpointer ptr)
|
||||||
|
{
|
||||||
|
GstApeDemuxTypeFind *apetf = ptr;
|
||||||
|
|
||||||
|
return apetf->len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_typefind_suggest (gpointer ptr,
|
||||||
|
guint probability, const GstCaps * caps)
|
||||||
|
{
|
||||||
|
GstApeDemuxTypeFind *apetf = ptr;
|
||||||
|
|
||||||
|
GST_LOG ("Found type of mime %s, probability %u",
|
||||||
|
gst_structure_get_name (gst_caps_get_structure (caps, 0)), probability);
|
||||||
|
|
||||||
|
if (probability > apetf->probability) {
|
||||||
|
if (apetf->caps)
|
||||||
|
gst_caps_free (apetf->caps);
|
||||||
|
apetf->caps = gst_caps_copy (caps);
|
||||||
|
apetf->probability = probability;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_ape_demux_typefind (GstApeDemux * ape,
|
||||||
|
GstByteStream * bs, gboolean seekable)
|
||||||
|
{
|
||||||
|
GstApeDemuxTypeFind apetf;
|
||||||
|
GstTypeFind tf;
|
||||||
|
GList *factories;
|
||||||
|
|
||||||
|
GST_LOG ("Doing typefinding now");
|
||||||
|
|
||||||
|
/* prepare */
|
||||||
|
memset (&apetf, 0, sizeof (apetf));
|
||||||
|
memset (&tf, 0, sizeof (tf));
|
||||||
|
tf.peek = gst_ape_demux_typefind_peek;
|
||||||
|
tf.suggest = gst_ape_demux_typefind_suggest;
|
||||||
|
tf.data = &apetf;
|
||||||
|
apetf.bs = bs;
|
||||||
|
apetf.ape = ape;
|
||||||
|
apetf.len = gst_bytestream_length (bs);
|
||||||
|
if (apetf.len != (guint64) - 1) {
|
||||||
|
apetf.len -= ape->start_off + ape->end_off;
|
||||||
|
tf.get_length = gst_ape_demux_typefind_get_length;
|
||||||
|
}
|
||||||
|
apetf.seekable = seekable;
|
||||||
|
|
||||||
|
/* run */
|
||||||
|
for (factories = gst_type_find_factory_get_list ();
|
||||||
|
factories != NULL && !apetf.stop &&
|
||||||
|
apetf.probability < GST_TYPE_FIND_MAXIMUM; factories = factories->next) {
|
||||||
|
gst_type_find_factory_call_function (factories->data, &tf);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* fatal error */
|
||||||
|
if (apetf.stop)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
/* type found? */
|
||||||
|
if (!apetf.caps || apetf.probability < GST_TYPE_FIND_MINIMUM) {
|
||||||
|
GST_ELEMENT_ERROR (ape, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_LOG ("Done typefinding, found mime %s",
|
||||||
|
gst_structure_get_name (gst_caps_get_structure (apetf.caps, 0)));
|
||||||
|
|
||||||
|
ape->srcpad =
|
||||||
|
gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
|
||||||
|
"src");
|
||||||
|
gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
|
||||||
|
gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
|
||||||
|
gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
|
||||||
|
gst_pad_set_query_type_function (ape->srcpad,
|
||||||
|
gst_ape_demux_get_src_query_types);
|
||||||
|
gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
|
||||||
|
gst_pad_use_explicit_caps (ape->srcpad);
|
||||||
|
gst_pad_set_explicit_caps (ape->srcpad, apetf.caps);
|
||||||
|
gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Parse tags from a buffer.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_parse_tags (GstApeDemux * ape, guint8 * data, gint size)
|
||||||
|
{
|
||||||
|
GstTagList *taglist = gst_tag_list_new ();
|
||||||
|
gboolean have_tag = FALSE;
|
||||||
|
|
||||||
|
GST_LOG ("Reading tags from chunk of size %u bytes", size);
|
||||||
|
|
||||||
|
/* get rid of header/footer */
|
||||||
|
if (!memcmp (data, "APETAGEX", 8)) {
|
||||||
|
data += 32;
|
||||||
|
size -= 32;
|
||||||
|
}
|
||||||
|
if (!memcmp (data + size - 32, "APETAGEX", 8)) {
|
||||||
|
size -= 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read actual tags - at least 10 bytes for tag header */
|
||||||
|
while (size >= 10) {
|
||||||
|
guint len, n = 8;
|
||||||
|
gchar *tag, *val;
|
||||||
|
const gchar *type = NULL;
|
||||||
|
gboolean i = FALSE;
|
||||||
|
|
||||||
|
/* find tag type and size */
|
||||||
|
len = GST_READ_UINT32_LE (data);
|
||||||
|
while (n < size && data[n] != 0x0)
|
||||||
|
n++;
|
||||||
|
if (n == size)
|
||||||
|
break;
|
||||||
|
g_assert (data[n] == 0x0);
|
||||||
|
n++;
|
||||||
|
if (size - n < len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* read */
|
||||||
|
tag = g_strndup (&data[8], n - 9);
|
||||||
|
val = g_strndup (&data[n], len);
|
||||||
|
if (!strcasecmp (tag, "title")) {
|
||||||
|
type = GST_TAG_TITLE;
|
||||||
|
} else if (!strcasecmp (tag, "artist")) {
|
||||||
|
type = GST_TAG_ARTIST;
|
||||||
|
} else if (!strcasecmp (tag, "album")) {
|
||||||
|
type = GST_TAG_ALBUM;
|
||||||
|
} else if (!strcasecmp (tag, "comment")) {
|
||||||
|
type = GST_TAG_COMMENT;
|
||||||
|
} else if (!strcasecmp (tag, "copyright")) {
|
||||||
|
type = GST_TAG_COPYRIGHT;
|
||||||
|
} else if (!strcasecmp (tag, "isrc")) {
|
||||||
|
type = GST_TAG_ISRC;
|
||||||
|
} else if (!strcasecmp (tag, "track")) {
|
||||||
|
type = GST_TAG_TRACK_NUMBER;
|
||||||
|
i = TRUE;
|
||||||
|
}
|
||||||
|
if (type) {
|
||||||
|
GValue v = { 0 };
|
||||||
|
|
||||||
|
if (i) {
|
||||||
|
g_value_init (&v, G_TYPE_INT);
|
||||||
|
g_value_set_int (&v, atoi (val));
|
||||||
|
} else {
|
||||||
|
g_value_init (&v, G_TYPE_STRING);
|
||||||
|
g_value_set_string (&v, val);
|
||||||
|
}
|
||||||
|
gst_tag_list_add_values (taglist, GST_TAG_MERGE_APPEND, type, &v, NULL);
|
||||||
|
g_value_unset (&v);
|
||||||
|
have_tag = TRUE;
|
||||||
|
}
|
||||||
|
GST_DEBUG ("Read tag %s: %s", tag, val);
|
||||||
|
g_free (tag);
|
||||||
|
g_free (val);
|
||||||
|
|
||||||
|
/* move data pointer */
|
||||||
|
size -= len + n;
|
||||||
|
data += len + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* let people know */
|
||||||
|
if (have_tag) {
|
||||||
|
gst_element_found_tags (GST_ELEMENT (ape), taglist);
|
||||||
|
/*gst_pad_push (ape->srcpad, GST_DATA (gst_event_new_tag (taglist))); */
|
||||||
|
} else {
|
||||||
|
gst_tag_list_free (taglist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* "Open" a APEv1/2 file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
gst_ape_demux_stream_init (GstApeDemux * ape)
|
||||||
|
{
|
||||||
|
GstByteStream *bs;
|
||||||
|
gboolean seekable = TRUE, res = TRUE;
|
||||||
|
guint8 *data;
|
||||||
|
guint32 size = 0;
|
||||||
|
|
||||||
|
GST_LOG ("Initializing stream, stripping tags");
|
||||||
|
|
||||||
|
/* start off, we'll want byte-reading here */
|
||||||
|
bs = gst_bytestream_new (ape->sinkpad);
|
||||||
|
|
||||||
|
/* can we seek? */
|
||||||
|
if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_END)) {
|
||||||
|
seekable = FALSE;
|
||||||
|
} else {
|
||||||
|
if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_SET)) {
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL),
|
||||||
|
("Couldn't seek back to start - cannot handle that"));
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ape tags at start? */
|
||||||
|
while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
|
||||||
|
if (!gst_ape_demux_handle_event (ape, bs)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!memcmp (data, "APETAGEX", 8)) {
|
||||||
|
GST_LOG ("Found tags at start");
|
||||||
|
|
||||||
|
/* APEv2 at start of file - note that the flags are useless because
|
||||||
|
* I have yet to see the first writer that writes correct HAS_HEADER
|
||||||
|
* and HAS_FOOTER flags... So we detect it ourselves. */
|
||||||
|
size = GST_READ_UINT32_LE (data + 12);
|
||||||
|
|
||||||
|
/* Size is without the header and with the footer. So add 32 because
|
||||||
|
* we're still at position 0 here (peek != read). */
|
||||||
|
size += 32;
|
||||||
|
while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
|
||||||
|
if (!gst_ape_demux_handle_event (ape, bs)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gst_ape_demux_parse_tags (ape, data, size);
|
||||||
|
ape->start_off = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* if we're not seekable, then this is it already. Flush the tags,
|
||||||
|
* and forward the rest of the data to the next element. */
|
||||||
|
if (!seekable) {
|
||||||
|
if (size != 0)
|
||||||
|
gst_bytestream_flush_fast (bs, size);
|
||||||
|
|
||||||
|
if (!gst_ape_demux_typefind (ape, bs, FALSE)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_bytestream_get_status (bs, &size, NULL);
|
||||||
|
if (size) {
|
||||||
|
GstBuffer *buf = NULL;
|
||||||
|
|
||||||
|
gst_bytestream_read (bs, &buf, size);
|
||||||
|
g_assert (buf);
|
||||||
|
gst_pad_push (ape->srcpad, GST_DATA (buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* now look for tags at the end */
|
||||||
|
if (!gst_bytestream_seek (bs, -32, GST_SEEK_METHOD_END)) {
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
|
||||||
|
if (!gst_ape_demux_handle_event (ape, bs)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!memcmp (data, "APETAGEX", 8)) {
|
||||||
|
GST_LOG ("Found tags at end");
|
||||||
|
|
||||||
|
/* APEv1/2 at start of file - note that the flags are useless because
|
||||||
|
* I have yet to see the first writer that writes correct HAS_HEADER
|
||||||
|
* and HAS_FOOTER flags... So we detect it ourselves. */
|
||||||
|
size = GST_READ_UINT32_LE (data + 12);
|
||||||
|
|
||||||
|
/* size is without header, so add 32 to detect that. */
|
||||||
|
size += 32;
|
||||||
|
if (!gst_bytestream_seek (bs, -(gint64) size, GST_SEEK_METHOD_END)) {
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
|
||||||
|
if (!gst_ape_demux_handle_event (ape, bs)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (memcmp (data, "APETAGEX", 8) != 0) {
|
||||||
|
data += 32;
|
||||||
|
size -= 32;
|
||||||
|
}
|
||||||
|
gst_ape_demux_parse_tags (ape, data, size);
|
||||||
|
ape->end_off = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* seek back to beginning */
|
||||||
|
if (!gst_bytestream_seek (bs, ape->start_off, GST_SEEK_METHOD_SET)) {
|
||||||
|
GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* get any events */
|
||||||
|
while (gst_bytestream_peek_bytes (bs, &data, 1) != 1) {
|
||||||
|
if (!gst_ape_demux_handle_event (ape, bs)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* typefind */
|
||||||
|
if (!gst_ape_demux_typefind (ape, bs, TRUE)) {
|
||||||
|
res = FALSE;
|
||||||
|
goto the_city;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* push any leftover data */
|
||||||
|
gst_bytestream_get_status (bs, &size, NULL);
|
||||||
|
if (size) {
|
||||||
|
GstBuffer *buf = NULL;
|
||||||
|
|
||||||
|
gst_bytestream_read (bs, &buf, size);
|
||||||
|
g_assert (buf);
|
||||||
|
gst_pad_push (ape->srcpad, GST_DATA (buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
the_city:
|
||||||
|
/* become rich & famous */
|
||||||
|
gst_bytestream_destroy (bs);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Forward one buffer (we're an identity here).
|
||||||
|
*/
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_stream_data (GstApeDemux * ape)
|
||||||
|
{
|
||||||
|
GstData *data;
|
||||||
|
|
||||||
|
data = gst_pad_pull (ape->sinkpad);
|
||||||
|
if (GST_IS_EVENT (data)) {
|
||||||
|
GstEvent *event = GST_EVENT (data);
|
||||||
|
|
||||||
|
switch (GST_EVENT_TYPE (event)) {
|
||||||
|
case GST_EVENT_DISCONTINUOUS:{
|
||||||
|
GstEvent *new;
|
||||||
|
gint64 new_off = ape->start_off;
|
||||||
|
|
||||||
|
gst_event_discont_get_value (event, GST_FORMAT_BYTES, &new_off);
|
||||||
|
new_off -= ape->start_off;
|
||||||
|
new = gst_event_new_discontinuous (GST_EVENT_DISCONT_NEW_MEDIA (event),
|
||||||
|
GST_FORMAT_BYTES, new_off, GST_FORMAT_UNDEFINED);
|
||||||
|
gst_event_unref (event);
|
||||||
|
data = GST_DATA (new);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
GstBuffer *buf = GST_BUFFER (data), *kid;
|
||||||
|
gint64 pos, len;
|
||||||
|
GstFormat fmt = GST_FORMAT_BYTES;
|
||||||
|
|
||||||
|
kid = gst_buffer_create_sub (buf, 0, GST_BUFFER_SIZE (buf));
|
||||||
|
GST_BUFFER_OFFSET (kid) -= ape->start_off;
|
||||||
|
gst_buffer_unref (buf);
|
||||||
|
data = GST_DATA (kid);
|
||||||
|
|
||||||
|
/* if the plugin allows us to, see if we're close to eos */
|
||||||
|
if (gst_pad_query (GST_PAD_PEER (ape->sinkpad),
|
||||||
|
GST_QUERY_POSITION, &fmt, &pos) &&
|
||||||
|
gst_pad_query (GST_PAD_PEER (ape->sinkpad),
|
||||||
|
GST_QUERY_TOTAL, &fmt, &len)) {
|
||||||
|
if (pos > len - ape->end_off) {
|
||||||
|
if (pos - GST_BUFFER_SIZE (buf) >= len - ape->end_off) {
|
||||||
|
gst_buffer_unref (kid);
|
||||||
|
data = NULL;
|
||||||
|
} else {
|
||||||
|
GST_BUFFER_SIZE (kid) -= ape->end_off - (len - pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data)
|
||||||
|
gst_pad_push (ape->srcpad, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
gst_ape_demux_loop (GstElement * element)
|
||||||
|
{
|
||||||
|
GstApeDemux *ape = GST_APE_DEMUX (element);
|
||||||
|
|
||||||
|
switch (ape->state) {
|
||||||
|
case GST_APE_DEMUX_TAGREAD:
|
||||||
|
if (!gst_ape_demux_stream_init (ape))
|
||||||
|
return;
|
||||||
|
GST_LOG ("From now on, we're in identity mode");
|
||||||
|
ape->state = GST_APE_DEMUX_IDENTITY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GST_APE_DEMUX_IDENTITY:
|
||||||
|
gst_ape_demux_stream_data (ape);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
g_assert (0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static GstElementStateReturn
|
||||||
|
gst_ape_demux_change_state (GstElement * element)
|
||||||
|
{
|
||||||
|
GstApeDemux *ape = GST_APE_DEMUX (element);
|
||||||
|
|
||||||
|
switch (GST_STATE_TRANSITION (element)) {
|
||||||
|
case GST_STATE_PAUSED_TO_READY:
|
||||||
|
if (ape->srcpad) {
|
||||||
|
gst_element_remove_pad (element, ape->srcpad);
|
||||||
|
ape->srcpad = NULL;
|
||||||
|
}
|
||||||
|
ape->state = GST_APE_DEMUX_TAGREAD;
|
||||||
|
ape->start_off = ape->end_off = 0;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GST_ELEMENT_CLASS (parent_class)->change_state)
|
||||||
|
return GST_ELEMENT_CLASS (parent_class)->change_state (element);
|
||||||
|
|
||||||
|
return GST_STATE_SUCCESS;
|
||||||
|
}
|
64
gst/apetag/apedemux.h
Normal file
64
gst/apetag/apedemux.h
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
/* GStreamer APEv1/2 tag reader
|
||||||
|
* Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __GST_APE_DEMUX_H__
|
||||||
|
#define __GST_APE_DEMUX_H__
|
||||||
|
|
||||||
|
#include <gst/gst.h>
|
||||||
|
|
||||||
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
|
#define GST_TYPE_APE_DEMUX \
|
||||||
|
(gst_ape_demux_get_type ())
|
||||||
|
#define GST_APE_DEMUX(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_APE_DEMUX, GstApeDemux))
|
||||||
|
#define GST_APE_DEMUX_CLASS(klass) \
|
||||||
|
(G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_APE_DEMUX, GstApeDemux))
|
||||||
|
#define GST_IS_APE_DEMUX(obj) \
|
||||||
|
(G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_APE_DEMUX))
|
||||||
|
#define GST_IS_APE_DEMUX_CLASS(obj) \
|
||||||
|
(G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_APE_DEMUX))
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
GST_APE_DEMUX_TAGREAD,
|
||||||
|
GST_APE_DEMUX_IDENTITY
|
||||||
|
} GstApeDemuxState;
|
||||||
|
|
||||||
|
typedef struct _GstApeDemux {
|
||||||
|
GstElement parent;
|
||||||
|
|
||||||
|
/* pads */
|
||||||
|
GstPad *srcpad, *sinkpad;
|
||||||
|
|
||||||
|
/* tag read state */
|
||||||
|
GstApeDemuxState state;
|
||||||
|
|
||||||
|
/* length of ape tag at start/end */
|
||||||
|
guint64 start_off, end_off;
|
||||||
|
} GstApeDemux;
|
||||||
|
|
||||||
|
typedef struct _GstApeDemuxClass {
|
||||||
|
GstElementClass parent_class;
|
||||||
|
} GstApeDemuxClass;
|
||||||
|
|
||||||
|
GType gst_ape_demux_get_type (void);
|
||||||
|
|
||||||
|
G_END_DECLS
|
||||||
|
|
||||||
|
#endif /* __GST_APE_DEMUX_H__ */
|
40
gst/apetag/apetag.c
Normal file
40
gst/apetag/apetag.c
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/* GStreamer APEv1/2 tag reader
|
||||||
|
* Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.net>
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
/* Element-Checklist-Version: 5 */
|
||||||
|
|
||||||
|
#ifdef HAVE_CONFIG_H
|
||||||
|
#include "config.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "apedemux.h"
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
plugin_init (GstPlugin * plugin)
|
||||||
|
{
|
||||||
|
if (!gst_library_load ("gstbytestream"))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
return (gst_element_register (plugin, "apedemux",
|
||||||
|
GST_RANK_PRIMARY, GST_TYPE_APE_DEMUX));
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
|
||||||
|
GST_VERSION_MINOR,
|
||||||
|
"apetag",
|
||||||
|
"APEv1/2 tag reader", plugin_init, VERSION, "LGPL", GST_PACKAGE, GST_ORIGIN)
|
Loading…
Reference in a new issue