Add documentation. Fix test app compilation. Fix pull mode.

Original commit message from CVS:
Add documentation. Fix test app compilation. Fix pull mode.
This commit is contained in:
Edgard Lima 2008-01-30 12:56:51 +00:00
parent 7460bb6d91
commit 1159638102
12 changed files with 796 additions and 290 deletions

View file

@ -1,3 +1,18 @@
2008-01-30 Edgard Lima <edgard.lima@indt.org.br>
* ext/Makefile.am:
* ext/metadata/TODO:
* ext/metadata/gstbasemetadata.c:
* ext/metadata/gstbasemetadata.h:
* ext/metadata/metadatamuxjpeg.c:
* ext/metadata/metadatamuxjpeg.h:
* ext/metadata/metadatamuxpng.c:
* ext/metadata/metadatamuxpng.h:
* ext/metadata/metadataparsejpeg.c:
* ext/metadata/metadataparsepng.c:
* tests/icles/Makefile.am:
Add documentation. Fix test app compilation. Fix pull mode.
2008-01-29 Wim Taymans <wim.taymans@collabora.co.uk>
Patch by: Thijs Vermeir <thijsvermeir at gmail dot com>

View file

@ -76,12 +76,6 @@ else
DTS_DIR=
endif
if USE_METADATA
METADATA_DIR=metadata
else
METADATA_DIR=
endif
if USE_FAAC
FAAC_DIR=faac
else
@ -160,6 +154,12 @@ else
MPEG2ENC_DIR=
endif
if USE_METADATA
METADATA_DIR=metadata
else
METADATA_DIR=
endif
# if USE_MPLEX
# MPLEX_DIR=mplex
# else
@ -313,6 +313,7 @@ SUBDIRS=\
$(LIBFAME_DIR) \
$(LIBMMS_DIR) \
$(MPEG2ENC_DIR) \
$(METADATA_DIR) \
$(MPLEX_DIR) \
$(MUSEPACK_DIR) \
$(MUSICBRAINZ_DIR) \
@ -342,7 +343,6 @@ DIST_SUBDIRS = \
cdaudio \
dc1394 \
directfb \
metadata \
faac \
faad \
gio \
@ -353,6 +353,7 @@ DIST_SUBDIRS = \
libmms \
dts \
divx \
metadata \
mpeg2enc \
musepack \
musicbrainz \

View file

@ -26,12 +26,4 @@ OPEN ISSUES:
KNOWN BUGS
1- gst-launch-0.10 filesrc location=BlueSquare.png ! metadatademux ! metadatamux ! pngdec ! ffmpegcolorspace ! freeze ! xvimagesink
the following pipelines work fine:
gst-launch-0.10 filesrc location=BlueSquare.png ! metadatamux ! metadatademux ! pngdec ! ffmpegcolorspace ! freeze ! xvimagesink
gst-launch-0.10 filesrc location=BlueSquare.png ! metadatademux ! metadatamux ! queue ! pngdec ! ffmpegcolorspace ! freeze ! xvimagesink
gst-launch-0.10 filesrc location=BlueSquare.png ! ! metadatamux ! pngdec ! ffmpegcolorspace ! freeze ! xvimagesink
gst-launch-0.10 filesrc location=BlueSquare.png ! metadatademux ! ! pngdec ! ffmpegcolorspace ! freeze ! xvimagesink

View file

@ -159,7 +159,8 @@ gst_base_metadata_parse (GstBaseMetadata * filter, const guint8 * buf,
static gboolean
gst_base_metadata_strip_push_buffer (GstBaseMetadata * base,
gint64 offset_orig, GstBuffer ** prepend, GstBuffer ** buf);
const gint64 offset_orig, GstBuffer ** prepend, GstBuffer ** buf,
gboolean inject_begin);
static int
gst_base_metadata_buf_get_intersection_seg (const gint64 offset, guint32 size,
@ -168,7 +169,7 @@ gst_base_metadata_buf_get_intersection_seg (const gint64 offset, guint32 size,
static gboolean
gst_base_metadata_translate_pos_to_orig (GstBaseMetadata * base,
gint64 pos, gint64 * orig_pos, GstBuffer ** buf);
gint64 pos, gint64 * orig_pos, GstBuffer ** buf, guint32 max_size);
static gboolean gst_base_metadata_calculate_offsets (GstBaseMetadata * base);
@ -639,6 +640,7 @@ done:
* beginning og @buf
* @buf: a pointer to a buffer that will be modified (data striped/injected or
* prepended)
* @inject_begin: is TRUE can inject a chunk start exactly in @offset_orig
*
* Strip bytes from @buf that are part of some chunk that will be striped. Add
* a whole injected chunk if some inject chunk starts into the buffer. Prepend
@ -658,7 +660,8 @@ done:
static gboolean
gst_base_metadata_strip_push_buffer (GstBaseMetadata * base,
gint64 offset_orig, GstBuffer ** prepend, GstBuffer ** buf)
const gint64 offset_orig, GstBuffer ** prepend, GstBuffer ** buf,
gboolean inject_begin)
{
MetadataChunk *strip = META_DATA_STRIP_CHUNKS (base->metadata).chunk;
MetadataChunk *inject = META_DATA_INJECT_CHUNKS (base->metadata).chunk;
@ -692,11 +695,13 @@ gst_base_metadata_strip_push_buffer (GstBaseMetadata * base,
int res;
if (inject[i].offset_orig >= offset_orig) {
if (inject[i].offset_orig < offset_orig + size_buf_in) {
injected_bytes += inject[i].size;
} else {
/* segment is after size (segments are sorted) */
break;
if (G_LIKELY (inject_begin || inject[i].offset_orig > offset_orig)) {
if (inject[i].offset_orig < offset_orig + size_buf_in) {
injected_bytes += inject[i].size;
} else {
/* segment is after size (segments are sorted) */
break;
}
}
}
}
@ -846,20 +851,22 @@ inject:
original buffer */
if (inject[i].offset_orig >= offset_orig) {
if (inject[i].offset_orig <
offset_orig + size_buf_in + striped_bytes - injected_bytes) {
/* insert */
guint32 buf_off =
inject[i].offset_orig - offset_orig - striped_so_far +
injected_bytes;
memmove (data + buf_off + inject[i].size, data + buf_off,
size_buf_in - buf_off);
memcpy (data + buf_off, inject[i].data, inject[i].size);
injected_bytes += inject[i].size;
size_buf_in += inject[i].size;
} else {
/* segment is after size (segments are sorted) */
break;
if (G_LIKELY (inject_begin || inject[i].offset_orig > offset_orig)) {
if (inject[i].offset_orig <
offset_orig + size_buf_in + striped_bytes - injected_bytes) {
/* insert */
guint32 buf_off =
inject[i].offset_orig - offset_orig - striped_so_far +
injected_bytes;
memmove (data + buf_off + inject[i].size, data + buf_off,
size_buf_in - buf_off);
memcpy (data + buf_off, inject[i].data, inject[i].size);
injected_bytes += inject[i].size;
size_buf_in += inject[i].size;
} else {
/* segment is after size (segments are sorted) */
break;
}
}
}
}
@ -988,6 +995,7 @@ done:
* @orig_pos: position in original stream
* @buf: if not NULL, will have data that starts at some point into a injected
* chunk
* @max_size: the maximum size to allocate to @buf. pass 0 if don't care
*
* Given a position in output stream (@pos), returns the position in original
* stream (@orig_pos) that contains the same data. If @pos is into a injected
@ -1006,7 +1014,7 @@ done:
static gboolean
gst_base_metadata_translate_pos_to_orig (GstBaseMetadata * base,
gint64 pos, gint64 * orig_pos, GstBuffer ** buf)
gint64 pos, gint64 * orig_pos, GstBuffer ** buf, guint32 max_size)
{
MetadataChunk *strip = META_DATA_STRIP_CHUNKS (base->metadata).chunk;
MetadataChunk *inject = META_DATA_INJECT_CHUNKS (base->metadata).chunk;
@ -1014,9 +1022,11 @@ gst_base_metadata_translate_pos_to_orig (GstBaseMetadata * base,
const gsize inject_len = META_DATA_INJECT_CHUNKS (base->metadata).len;
const gint64 duration_orig = base->duration_orig;
const gint64 duration = base->duration;
gboolean ret = TRUE;
const gint64 saved_pos = pos;
int i;
gboolean ret = TRUE;
guint64 new_buf_size = 0;
guint64 injected_before = 0;
@ -1032,56 +1042,67 @@ gst_base_metadata_translate_pos_to_orig (GstBaseMetadata * base,
/* calculate for injected */
/* just calculate size */
*orig_pos = pos; /* save pos */
for (i = 0; i < inject_len; ++i) {
/* check if pos in inside chunk */
if (inject[i].offset <= pos) {
if (pos >= inject[i].offset) {
if (pos < inject[i].offset + inject[i].size) {
/* orig pos points after insert chunk */
new_buf_size += inject[i].size;
/* put pos after current chunk */
pos = inject[i].offset + inject[i].size;
/* pos is inside the chunk */
const guint32 offset_in_chunk = pos - inject[i].offset;
ret = FALSE;
pos = inject[i].offset + inject[i].size; /* put pos just after chunk */
new_buf_size += inject[i].size - offset_in_chunk;
/* we still continue, 'cause the next chunk could be just after this */
} else {
/* in case pos is not inside a injected chunk */
injected_before += inject[i].size;
}
} else {
/* pos is before the chunk */
break;
}
}
/* alloc buffer and calcute original pos */
if (buf && ret == FALSE) {
guint8 *data;
if (ret == FALSE) {
if (*buf)
gst_buffer_unref (*buf);
*buf = gst_buffer_new_and_alloc (new_buf_size);
data = GST_BUFFER_DATA (*buf);
pos = *orig_pos; /* recover saved pos */
for (i = 0; i < inject_len; ++i) {
if (inject[i].offset > pos) {
break;
}
if (inject[i].offset <= pos && pos < inject[i].offset + inject[i].size) {
memcpy (data, inject[i].data, inject[i].size);
data += inject[i].size;
pos = inject[i].offset + inject[i].size;
/* out position after insert chunk orig */
*orig_pos = inject[i].offset_orig + inject[i].size;
*orig_pos = pos;
if (buf) {
guint8 *data;
if (max_size > 0)
if (new_buf_size > max_size)
new_buf_size = max_size;
if (*buf)
gst_buffer_unref (*buf);
*buf = gst_buffer_new_and_alloc (new_buf_size);
data = GST_BUFFER_DATA (*buf);
pos = saved_pos;
for (i = 0; i < inject_len && new_buf_size > 0; ++i) {
if (inject[i].offset > pos) {
break;
}
if (pos < inject[i].offset + inject[i].size) {
const guint32 offset = pos - inject[i].offset;
guint32 size = inject[i].size - offset;
if (size > new_buf_size)
size = new_buf_size;
memcpy (data, inject[i].data + offset, size);
data += size;
pos = inject[i].offset + inject[i].size;
new_buf_size -= size;
}
}
}
}
if (ret == FALSE) {
/* if it inside a injected is already done */
goto done;
}
/* calculate for striped */
*orig_pos = pos - injected_before;
*orig_pos = saved_pos - injected_before;
for (i = 0; i < strip_len; ++i) {
if (strip[i].offset_orig > pos) {
break;
@ -1479,7 +1500,7 @@ gst_base_metadata_src_event (GstPad * pad, GstEvent * event)
striped/injected buffer in next 'chain' calling */
filter->offset = start;
gst_base_metadata_translate_pos_to_orig (filter, start, &start,
&filter->prepend_buffer);
&filter->prepend_buffer, 0);
filter->offset_orig = start;
if (stop_type == GST_SEEK_TYPE_CUR)
@ -1491,7 +1512,7 @@ gst_base_metadata_src_event (GstPad * pad, GstEvent * event)
}
stop_type == GST_SEEK_TYPE_SET;
gst_base_metadata_translate_pos_to_orig (filter, stop, &stop, NULL);
gst_base_metadata_translate_pos_to_orig (filter, stop, &stop, NULL, 0);
gst_event_unref (event);
event = gst_event_new_seek (rate, format, flags,
@ -1569,6 +1590,7 @@ gst_base_metadata_get_range (GstPad * pad,
guint size_orig;
GstBuffer *prepend = NULL;
gboolean need_append = FALSE;
gboolean into_inject;
filter = GST_BASE_METADATA (GST_PAD_PARENT (pad));
@ -1583,34 +1605,45 @@ gst_base_metadata_get_range (GstPad * pad,
size_orig = size;
gst_base_metadata_translate_pos_to_orig (filter, offset,
&offset_orig, &prepend);
into_inject = !gst_base_metadata_translate_pos_to_orig (filter, offset,
&offset_orig, &prepend, size);
if (size > 1) {
if (into_inject) {
size_orig = GST_BUFFER_SIZE (prepend) < size_orig ?
size_orig - GST_BUFFER_SIZE (prepend) : 0;
}
if (size_orig == 0) {
/* enough data in prepend */
*buf = prepend;
goto done;
}
if (size_orig > 1) {
gint64 pos;
pos = offset + size - 1;
gst_base_metadata_translate_pos_to_orig (filter, pos, &pos, NULL);
into_inject = gst_base_metadata_translate_pos_to_orig (filter, pos, &pos,
NULL, 0);
size_orig = pos + 1 - offset_orig;
}
if (size_orig) {
ret = gst_pad_pull_range (filter->sinkpad, offset_orig, size_orig, buf);
ret = gst_pad_pull_range (filter->sinkpad, offset_orig, size_orig, buf);
if (ret == GST_FLOW_OK && *buf) {
gst_base_metadata_strip_push_buffer (filter, offset_orig, &prepend, buf);
if (GST_BUFFER_SIZE (*buf) < size) {
/* need append */
need_append = TRUE;
}
if (ret == GST_FLOW_OK && *buf) {
gst_base_metadata_strip_push_buffer (filter, offset_orig, &prepend, buf,
FALSE);
if (GST_BUFFER_SIZE (*buf) < size) {
/* need append */
need_append = TRUE;
} else {
/* hide extra bytes */
GST_BUFFER_SIZE (*buf) = size;
}
} else {
*buf = prepend;
}
done:
if (need_append) {
@ -1715,7 +1748,7 @@ gst_base_metadata_chain (GstPad * pad, GstBuffer * buf)
buf_size = GST_BUFFER_SIZE (buf);
gst_base_metadata_strip_push_buffer (filter, filter->offset_orig,
&filter->prepend_buffer, &buf);
&filter->prepend_buffer, &buf, TRUE);
if (buf) { /* may be all buffer has been striped */
gst_buffer_set_caps (buf, GST_PAD_CAPS (filter->srcpad));
@ -1848,6 +1881,7 @@ gst_base_metadata_src_query (GstPad * pad, GstQuery * query)
gst_query_set_position (query, GST_FORMAT_BYTES, filter->offset);
ret = TRUE;
}
break;
case GST_QUERY_DURATION:
@ -1864,6 +1898,7 @@ gst_base_metadata_src_query (GstPad * pad, GstQuery * query)
ret = TRUE;
}
}
break;
case GST_QUERY_FORMATS:
gst_query_set_formats (query, 1, GST_FORMAT_BYTES);

View file

@ -73,27 +73,57 @@ typedef enum _tag_BaseMetadataType {
} BaseMetadataType;
/**
/*
* GST_BASE_METADATA_SRC_PAD:
* @obj: base metadata instance
*
* Gives the pointer to the #GstPad object of the element.
*/
#define GST_BASE_METADATA_SRC_PAD(obj) (GST_BASE_METADATA_CAST (obj)->srcpad)
#define GST_BASE_METADATA_SRC_PAD(obj) (GST_BASE_METADATA_CAST (obj)->srcpad)
/**
/*
* GST_BASE_METADATA_SINK_PAD:
* @obj: base metadata instance
*
* Gives the pointer to the #GstPad object of the element.
*/
#define GST_BASE_METADATA_SINK_PAD(obj) (GST_BASE_METADATA_CAST (obj)->sinkpad)
#define GST_BASE_METADATA_SINK_PAD(obj) (GST_BASE_METADATA_CAST (obj)->sinkpad)
#define GST_BASE_METADATA_EXIF_ADAPTER(obj) (GST_BASE_METADATA_CAST (obj)->metadata->exif_adapter)
#define GST_BASE_METADATA_IPTC_ADAPTER(obj) (GST_BASE_METADATA_CAST (obj)->metadata->iptc_adapter)
#define GST_BASE_METADATA_XMP_ADAPTER(obj) (GST_BASE_METADATA_CAST (obj)->metadata->xmp_adapter)
/*
* GST_BASE_METADATA_EXIF_ADAPTER
* @obj: base metadata instance
*
* Gives the pointer to the EXIF #GstAdapter of the element.
*/
#define GST_BASE_METADATA_EXIF_ADAPTER(obj) \
(GST_BASE_METADATA_CAST (obj)->metadata->exif_adapter)
#define GST_BASE_METADATA_IMG_TYPE(obj) (GST_BASE_METADATA_CAST (obj)->img_type)
/*
* GST_BASE_METADATA_IPTC_ADAPTER
* @obj: base metadata instance
*
* Gives the pointer to the IPTC #GstAdapter of the element.
*/
#define GST_BASE_METADATA_IPTC_ADAPTER(obj) \
(GST_BASE_METADATA_CAST (obj)->metadata->iptc_adapter)
/*
* GST_BASE_METADATA_XMP_ADAPTER
* @obj: base metadata instance
*
* Gives the pointer to the XMP #GstAdapter of the element.
*/
#define GST_BASE_METADATA_XMP_ADAPTER(obj) \
(GST_BASE_METADATA_CAST (obj)->metadata->xmp_adapter)
/*
* GST_BASE_METADATA_IMG_TYPE
* @obj: base metadata instance
*
* Gives the type indentified by the parser of the element.
*/
#define GST_BASE_METADATA_IMG_TYPE(obj) \
(GST_BASE_METADATA_CAST (obj)->img_type)
typedef enum _tag_MetadataState
@ -127,7 +157,7 @@ struct _GstBaseMetadata
MetaOptions options;
gboolean need_processing; /* still need some action before send first buffer */
gboolean need_processing; /* still need a action before send first buffer */
GstAdapter *adapter_parsing;
GstAdapter *adapter_holding;
@ -161,10 +191,12 @@ extern GType
gst_base_metadata_get_type (void);
extern void
gst_base_metadata_set_option_flag(GstBaseMetadata *base, const MetaOptions options);
gst_base_metadata_set_option_flag(GstBaseMetadata *base,
const MetaOptions options);
extern void
gst_base_metadata_unset_option_flag(GstBaseMetadata *base, const MetaOptions options);
gst_base_metadata_unset_option_flag(GstBaseMetadata *base,
const MetaOptions options);
extern MetaOptions
gst_base_metadata_get_option_flag(const GstBaseMetadata *base);

View file

@ -41,6 +41,41 @@
* Boston, MA 02111-1307, USA.
*/
/*
* SECTION: metadatamuxjpeg
* @short_description: This module provides functions to parse JPEG files in
* order to write metadata to it.
*
* This module parses a JPEG stream to find the places in which metadata (EXIF,
* IPTC, XMP) chunks would be written. It also wraps metadata chunks with JPEG
* marks according to the specification.
*
* <refsect2>
* <para>
* #metadatamux_jpeg_init must be called before any other function in this
* module and must be paired with a call to #metadatamux_jpeg_dispose.
* #metadatamux_jpeg_parse is used to parse the stream (find the place
* metadata chunks should be written to).
* #metadatamux_jpeg_lazy_update do nothing.
* </para>
* <para>
* EXIF chunks will always be the first chunk (replaces JFIF). IPTC and XMP
* chunks will be placed or second chunk (after JFIF or EXIF) or third chunk
* if both (IPTC and XMP) are written to the file.
* </para>
* <para>
* When a EXIF chunk is written to the JPEG stream, if there is a JFIF chunk
* as the first chunk, it will be stripped out.
* </para>
* </refsect2>
*
* Last reviewed on 2008-01-24 (0.10.15)
*/
/*
* includes
*/
#include "metadatamuxjpeg.h"
#include <string.h>
@ -49,109 +84,51 @@
#include <libiptcdata/iptc-jpeg.h>
#endif
/*
* defines and macros
*/
#define READ(buf, size) ( (size)--, *((buf)++) )
/*
* static helper functions declaration
*/
static MetadataParsingReturn
metadatamux_jpeg_reading (JpegMuxData * jpeg_data, guint8 ** buf,
guint32 * bufsize, const guint32 offset, const guint8 * step_buf,
guint8 ** next_start, guint32 * next_size);
#define READ(buf, size) ( (size)--, *((buf)++) )
static void
metadatamux_wrap_chunk (MetadataChunk * chunk, const guint8 * buf,
guint32 buf_size, guint8 a, guint8 b)
{
guint8 *data = g_new (guint8, 4 + buf_size + chunk->size);
memcpy (data + 4 + buf_size, chunk->data, chunk->size);
g_free (chunk->data);
chunk->data = data;
chunk->size += 4 + buf_size;
data[0] = a;
data[1] = b;
data[2] = ((chunk->size - 2) >> 8) & 0xFF;
data[3] = (chunk->size - 2) & 0xFF;
if (buf && buf_size) {
memcpy (data + 4, buf, buf_size);
}
}
guint32 buf_size, guint8 a, guint8 b);
#ifdef HAVE_IPTC
static gboolean
metadatamux_wrap_iptc_with_ps3 (unsigned char **buf, unsigned int *buf_size)
{
unsigned int out_size = *buf_size + 4096;
unsigned char *outbuf = g_new (unsigned char, out_size);
int size_written;
gboolean ret = TRUE;
size_written =
iptc_jpeg_ps3_save_iptc (NULL, 0, *buf, *buf_size, outbuf, out_size);
g_free (*buf);
*buf = NULL;
*buf_size = 0;
if (size_written < 0) {
g_free (outbuf);
ret = FALSE;
} else {
*buf_size = size_written;
*buf = outbuf;
}
return ret;
}
metadatamux_wrap_iptc_with_ps3 (unsigned char **buf, unsigned int *buf_size);
#endif /* #ifdef HAVE_IPTC */
void
metadatamux_jpeg_lazy_update (JpegMuxData * jpeg_data)
{
gsize i;
gboolean has_exif = FALSE;
for (i = 0; i < jpeg_data->inject_chunks->len; ++i) {
if (jpeg_data->inject_chunks->chunk[i].size > 0 &&
jpeg_data->inject_chunks->chunk[i].data) {
switch (jpeg_data->inject_chunks->chunk[i].type) {
case MD_CHUNK_EXIF:
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i], NULL, 0,
0xFF, 0xE1);
has_exif = TRUE;
break;
case MD_CHUNK_IPTC:
#ifdef HAVE_IPTC
{
if (metadatamux_wrap_iptc_with_ps3 (&jpeg_data->inject_chunks->
chunk[i].data, &jpeg_data->inject_chunks->chunk[i].size)) {
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i], NULL,
0, 0xFF, 0xED);
} else {
GST_ERROR ("Invalid IPTC chunk\n");
/* FIXME: remove entry from list */
}
}
#endif /* #ifdef HAVE_IPTC */
break;
case MD_CHUNK_XMP:
{
static const char XmpHeader[] = "http://ns.adobe.com/xap/1.0/";
/*
* extern functions implementations
*/
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i],
XmpHeader, sizeof (XmpHeader), 0xFF, 0xE1);
}
break;
default:
break;
}
}
}
if (!has_exif) {
/* EXIF not injected so not strip JFIF anymore */
metadata_chunk_array_clear (jpeg_data->strip_chunks);
}
}
/*
* metadatamux_jpeg_init:
* @jpeg_data: [in] jpeg data handler to be inited
* @strip_chunks: Array of chunks (offset and size) marked for removal
* @inject_chunks: Array of chunks (offset, data, size) marked for injection
* adapter (@exif_adpt, @iptc_adpt, @xmp_adpt). Or FALSE if should also put
* them on @strip_chunks.
*
* Init jpeg data handle.
* This function must be called before any other function from this module.
* This function must not be called twice without call to
* #metadatamux_jpeg_dispose beteween them.
* @see_also: #metadatamux_jpeg_dispose #metadatamux_jpeg_parse
*
* Returns: nothing
*/
void
metadatamux_jpeg_init (JpegMuxData * jpeg_data,
@ -164,6 +141,16 @@ metadatamux_jpeg_init (JpegMuxData * jpeg_data,
}
/*
* metadatamux_jpeg_dispose:
* @jpeg_data: [in] jpeg data handler to be freed
*
* Call this function to free any resource allocated by #metadatamux_jpeg_init
* @see_also: #metadatamux_jpeg_init
*
* Returns: nothing
*/
void
metadatamux_jpeg_dispose (JpegMuxData * jpeg_data)
{
@ -173,6 +160,42 @@ metadatamux_jpeg_dispose (JpegMuxData * jpeg_data)
jpeg_data->state = JPEG_MUX_NULL;
}
/*
* metadatamux_jpeg_parse:
* @jpeg_data: [in] jpeg data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
* @offset: is the offset where @buf starts from the beginnig of the whole
* stream
* @next_start: is a pointer after @buf which indicates where @buf should start
* on the next call to this function. It means, that after returning, this
* function has consumed *@next_start - @buf bytes. Which also means
* that @offset should also be incremanted by (*@next_start - @buf) for the
* next time.
* @next_size: [out] number of minimal bytes in @buf for the next call to this
* function
*
* This function is used to parse a JPEG stream step-by-step incrementally.
* Basically this function works like a state machine, that will run in a loop
* while there is still bytes in @buf to be read or it has finished parsing.
* If the it hasn't parsed yet and there is no more data in @buf, then the
* current state is saved and a indication will be make about the buffer to
* be passed by the caller function.
* @see_also: #metadatamux_jpeg_init
*
* Returns:
* <itemizedlist>
* <listitem><para>%META_PARSING_ERROR
* </para></listitem>
* <listitem><para>%META_PARSING_DONE if parse has finished. Now strip and
* inject chunks has been found
* </para></listitem>
* <listitem><para>%META_PARSING_NEED_MORE_DATA if this function should be
* called again (look @next_start and @next_size)
* </para></listitem>
* </itemizedlist>
*/
MetadataParsingReturn
metadatamux_jpeg_parse (JpegMuxData * jpeg_data, guint8 * buf,
guint32 * bufsize, const guint32 offset, guint8 ** next_start,
@ -227,8 +250,114 @@ done:
}
/*
* metadatamux_jpeg_lazy_update:
* @jpeg_data: [in] jpeg data handle
*
* This function wrap metadata chunk with proper JPEG marks. In case of IPTC
* it will be wrapped by PhotoShop and then by JPEG mark.
* @see_also: #metadata_lazy_update
*
* Returns: nothing
*/
void
metadatamux_jpeg_lazy_update (JpegMuxData * jpeg_data)
{
gsize i;
gboolean has_exif = FALSE;
for (i = 0; i < jpeg_data->inject_chunks->len; ++i) {
if (jpeg_data->inject_chunks->chunk[i].size > 0 &&
jpeg_data->inject_chunks->chunk[i].data) {
switch (jpeg_data->inject_chunks->chunk[i].type) {
case MD_CHUNK_EXIF:
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i], NULL, 0,
0xFF, 0xE1);
has_exif = TRUE;
break;
case MD_CHUNK_IPTC:
#ifdef HAVE_IPTC
{
if (metadatamux_wrap_iptc_with_ps3 (&jpeg_data->inject_chunks->
chunk[i].data, &jpeg_data->inject_chunks->chunk[i].size)) {
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i], NULL,
0, 0xFF, 0xED);
} else {
GST_ERROR ("Invalid IPTC chunk\n");
/* FIXME: remove entry from list */
}
}
#endif /* #ifdef HAVE_IPTC */
break;
case MD_CHUNK_XMP:
{
static const char XmpHeader[] = "http://ns.adobe.com/xap/1.0/";
metadatamux_wrap_chunk (&jpeg_data->inject_chunks->chunk[i],
XmpHeader, sizeof (XmpHeader), 0xFF, 0xE1);
}
break;
default:
break;
}
}
}
if (!has_exif) {
/* EXIF not injected so not strip JFIF anymore */
metadata_chunk_array_clear (jpeg_data->strip_chunks);
}
}
/*
* static helper functions implementation
*/
/*
* metadatamux_jpeg_reading:
* @jpeg_data: [in] jpeg data handle
* @buf: [in] data to be parsed. @buf will increment during the parsing step.
* So it will hold the next byte to be read inside a parsing function or on
* the next nested parsing function. And so, @bufsize will decrement.
* @bufsize: [in] size of @buf in bytes. This value will decrement during the
* parsing for the same reason that @buf will advance.
* @offset: is the offset where @step_buf starts from the beginnig of the
* stream
* @step_buf: holds the pointer to the buffer passed to
* #metadatamux_jpeg_parse. It means that any point inside this function
* the offset (related to the beginning of the whole stream) after the last
* byte read so far is "(*buf - step_buf) + offset"
* @next_start: is a pointer after @step_buf which indicates where the next
* call to #metadatamux_jpeg_parse should start on the next call to this
* function. It means, that after return, this function has
* consumed *@next_start - @buf bytes. Which also means that @offset should
* also be incremanted by (*@next_start - @buf) for the next time.
* @next_size: [out] number of minimal bytes in @buf for the next call to this
* function
*
* This function is used to parse a JPEG stream step-by-step incrementally.
* If this function quickly finds the place (offset) in which EXIF, IPTC and
* XMP chunk should be written to.
* The found places are written to @jpeg_data->inject_chunks
* @see_also: #metadatamux_jpeg_init
*
* Returns:
* <itemizedlist>
* <listitem><para>%META_PARSING_ERROR
* </para></listitem>
* <listitem><para>%META_PARSING_DONE if parse has finished. Now strip and
* inject chunks has been found. Or some chunk has been found and should be
* held or jumped.
* </para></listitem>
* <listitem><para>%META_PARSING_NEED_MORE_DATA if this function should be
* called again (look @next_start and @next_size)
* </para></listitem>
* </itemizedlist>
*/
/* look for markers */
static MetadataParsingReturn
metadatamux_jpeg_reading (JpegMuxData * jpeg_data, guint8 ** buf,
guint32 * bufsize, const guint32 offset, const guint8 * step_buf,
@ -338,3 +467,65 @@ done:
}
/*
* metadatamux_wrap_chunk:
* @chunk: chunk to be wrapped
* @buf: data to inject in the beginning of @chunk->data and after @a and @b
* @buf_size: size in bytes of @buf
* @a: together with @b forms the JPEG mark to be injected in the beginning
* @b: look at @a
*
* Wraps a chunk if a JPEG mark (@a@b) and, if @buf_size > 0, with some data
* (@buf)
*
* Returns: nothing
*/
static void
metadatamux_wrap_chunk (MetadataChunk * chunk, const guint8 * buf,
guint32 buf_size, guint8 a, guint8 b)
{
guint8 *data = g_new (guint8, 4 + buf_size + chunk->size);
memcpy (data + 4 + buf_size, chunk->data, chunk->size);
g_free (chunk->data);
chunk->data = data;
chunk->size += 4 + buf_size;
data[0] = a;
data[1] = b;
data[2] = ((chunk->size - 2) >> 8) & 0xFF;
data[3] = (chunk->size - 2) & 0xFF;
if (buf && buf_size) {
memcpy (data + 4, buf, buf_size);
}
}
#ifdef HAVE_IPTC
static gboolean
metadatamux_wrap_iptc_with_ps3 (unsigned char **buf, unsigned int *buf_size)
{
unsigned int out_size = *buf_size + 4096;
unsigned char *outbuf = g_new (unsigned char, out_size);
int size_written;
gboolean ret = TRUE;
size_written =
iptc_jpeg_ps3_save_iptc (NULL, 0, *buf, *buf_size, outbuf, out_size);
g_free (*buf);
*buf = NULL;
*buf_size = 0;
if (size_written < 0) {
g_free (outbuf);
ret = FALSE;
} else {
*buf_size = size_written;
*buf = outbuf;
}
return ret;
}
#endif /* #ifdef HAVE_IPTC */

View file

@ -44,10 +44,18 @@
#ifndef __METADATAMUX_JPEG_H__
#define __METADATAMUX_JPEG_H__
/*
* includes
*/
#include <gst/base/gstadapter.h>
#include "metadatatypes.h"
/*
* enum and types
*/
G_BEGIN_DECLS
typedef enum _tag_JpegMuxState
@ -67,6 +75,9 @@ typedef struct _tag_JpegMuxData
} JpegMuxData;
/*
* external function prototypes
*/
extern void
metadatamux_jpeg_init (JpegMuxData * jpeg_data,
@ -74,11 +85,12 @@ metadatamux_jpeg_init (JpegMuxData * jpeg_data,
extern void metadatamux_jpeg_dispose (JpegMuxData * jpeg_data);
extern void metadatamux_jpeg_lazy_update (JpegMuxData * jpeg_data);
extern MetadataParsingReturn
metadatamux_jpeg_parse (JpegMuxData * jpeg_data, guint8 * buf,
guint32 * bufsize, const guint32 offset, guint8 ** next_start, guint32 * next_size);
guint32 * bufsize, const guint32 offset, guint8 ** next_start,
guint32 * next_size);
extern void metadatamux_jpeg_lazy_update (JpegMuxData * jpeg_data);
G_END_DECLS
#endif /* __METADATAMUX_JPEG_H__ */

View file

@ -41,113 +41,88 @@
* Boston, MA 02111-1307, USA.
*/
/*
* SECTION: metadatamuxpng
* @short_description: This module provides functions to parse PNG files in
* order to write metadata to it.
*
* This module parses a PNG stream to find the places in which XMP metadata
* chunks would be written. It also wraps metadata chunks with PNG marks
* according to the specification.
*
* <refsect2>
* <para>
* #metadatamux_png_init must be called before any other function in this
* module and must be paired with a call to #metadatamux_png_dispose.
* #metadatamux_png_parse is used to parse the stream (find the place
* metadata chunks should be written to).
* #metadatamux_png_lazy_update do nothing.
* </para>
* <para>
* EXIF chunks will always be the first chunk (replaces JFIF). IPTC and XMP
* chunks will be placed or second chunk (after JFIF or EXIF) or third chunk
* if both (IPTC and XMP) are written to the file.
* </para>
* <para>
* When a EXIF chunk is written to the PNG stream, if there is a JFIF chunk
* as the first chunk, it will be stripped out.
* </para>
* </refsect2>
*
* Last reviewed on 2008-01-24 (0.10.15)
*/
/*
* includes
*/
#include "metadatamuxpng.h"
#include <string.h>
/*
* defines and macros
*/
#define READ(buf, size) ( (size)--, *((buf)++) )
/*
* static helper functions declaration
*/
static MetadataParsingReturn
metadatamux_png_reading (PngMuxData * png_data, guint8 ** buf,
guint32 * bufsize, const guint32 offset, const guint8 * step_buf,
guint8 ** next_start, guint32 * next_size);
#define READ(buf, size) ( (size)--, *((buf)++) )
static void metadatamux_make_crc_table (guint32 crc_table[]);
static void
make_crc_table (guint32 crc_table[])
{
guint32 c;
guint16 n, k;
static guint32 metadatamux_update_crc (guint32 crc, guint8 * buf, guint32 len);
for (n = 0; n < 256; n++) {
c = (guint32) n;
for (k = 0; k < 8; k++) {
if (c & 1)
c = 0xedb88320L ^ (c >> 1);
else
c = c >> 1;
}
crc_table[n] = c;
}
}
static guint32 metadatamux_calc_crc (guint8 * buf, guint32 len);
static guint32
update_crc (guint32 crc, guint8 * buf, guint32 len)
{
guint32 c = crc;
guint32 n;
guint32 crc_table[256];
static void metadatamux_wrap_xmp_chunk (MetadataChunk * chunk);
/* FIXME: make_crc_table should be done once in life
for speed up */
make_crc_table (crc_table);
/*
* extern functions implementations
*/
for (n = 0; n < len; n++) {
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}
/* Return the CRC of the bytes buf[0..len-1]. */
static guint32
calc_crc (guint8 * buf, guint32 len)
{
return update_crc (0xffffffffL, buf, len) ^ 0xffffffffL;
}
static void
metadatamux_wrap_xmp_chunk (MetadataChunk * chunk)
{
static const char XmpHeader[] = "XML:com.adobe.xmp";
guint8 *data = NULL;
guint32 crc;
data = g_new (guint8, 12 + 18 + 4 + chunk->size);
memcpy (data + 8, XmpHeader, 18);
memset (data + 8 + 18, 0x00, 4);
memcpy (data + 8 + 18 + 4, chunk->data, chunk->size);
g_free (chunk->data);
chunk->data = data;
chunk->size += 18 + 4;
data[0] = (chunk->size >> 24) & 0xFF;
data[1] = (chunk->size >> 16) & 0xFF;
data[2] = (chunk->size >> 8) & 0xFF;
data[3] = chunk->size & 0xFF;
data[4] = 'i';
data[5] = 'T';
data[6] = 'X';
data[7] = 't';
crc = calc_crc (data + 4, chunk->size + 4 + 18);
data[chunk->size + 8] = (crc >> 24) & 0xFF;
data[chunk->size + 9] = (crc >> 16) & 0xFF;
data[chunk->size + 10] = (crc >> 8) & 0xFF;
data[chunk->size + 11] = crc & 0xFF;
chunk->size += 12;
}
void
metadatamux_png_lazy_update (PngMuxData * png_data)
{
gsize i;
for (i = 0; i < png_data->inject_chunks->len; ++i) {
if (png_data->inject_chunks->chunk[i].size > 0 &&
png_data->inject_chunks->chunk[i].data) {
switch (png_data->inject_chunks->chunk[i].type) {
case MD_CHUNK_XMP:
{
metadatamux_wrap_xmp_chunk (&png_data->inject_chunks->chunk[i]);
}
break;
default:
GST_ERROR ("Unexpected chunk for PNG muxer.");
break;
}
}
}
}
/*
* metadatamux_png_init:
* @png_data: [in] png data handler to be inited
* @strip_chunks: Array of chunks (offset and size) marked for removal
* @inject_chunks: Array of chunks (offset, data, size) marked for injection
* adapter (@exif_adpt, @iptc_adpt, @xmp_adpt). Or FALSE if should also put
* them on @strip_chunks.
*
* Init png data handle.
* This function must be called before any other function from this module.
* This function must not be called twice without call to
* #metadatamux_png_dispose beteween them.
* @see_also: #metadatamux_png_dispose #metadatamux_png_parse
*
* Returns: nothing
*/
void
metadatamux_png_init (PngMuxData * png_data,
@ -159,6 +134,16 @@ metadatamux_png_init (PngMuxData * png_data,
png_data->inject_chunks = inject_chunks;
}
/*
* metadatamux_png_dispose:
* png_data: [in] png data handler to be freed
*
* Call this function to free any resource allocated by #metadatamux_png_init
* @see_also: #metadatamux_png_init
*
* Returns: nothing
*/
void
metadatamux_png_dispose (PngMuxData * png_data)
{
@ -168,6 +153,42 @@ metadatamux_png_dispose (PngMuxData * png_data)
png_data->state = PNG_MUX_NULL;
}
/*
* metadatamux_png_parse:
* @png_data: [in] png data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
* @offset: is the offset where @buf starts from the beginnig of the whole
* stream
* @next_start: is a pointer after @buf which indicates where @buf should start
* on the next call to this function. It means, that after returning, this
* function has consumed *@next_start - @buf bytes. Which also means
* that @offset should also be incremanted by (*@next_start - @buf) for the
* next time.
* @next_size: [out] number of minimal bytes in @buf for the next call to this
* function
*
* This function is used to parse a PNG stream step-by-step incrementally.
* Basically this function works like a state machine, that will run in a loop
* while there is still bytes in @buf to be read or it has finished parsing.
* If the it hasn't parsed yet and there is no more data in @buf, then the
* current state is saved and a indication will be make about the buffer to
* be passed by the caller function.
* @see_also: #metadatamux_png_init
*
* Returns:
* <itemizedlist>
* <listitem><para>%META_PARSING_ERROR
* </para></listitem>
* <listitem><para>%META_PARSING_DONE if parse has finished. Now strip and
* inject chunks has been found
* </para></listitem>
* <listitem><para>%META_PARSING_NEED_MORE_DATA if this function should be
* called again (look @next_start and @next_size)
* </para></listitem>
* </itemizedlist>
*/
MetadataParsingReturn
metadatamux_png_parse (PngMuxData * png_data, guint8 * buf,
guint32 * bufsize, const guint32 offset, guint8 ** next_start,
@ -230,8 +251,86 @@ done:
}
/*
* metadatamux_png_lazy_update:
* @png_data: [in] png data handle
*
* This function wrap metadata chunk with proper PNG bytes.
* @see_also: #metadata_lazy_update
*
* Returns: nothing
*/
void
metadatamux_png_lazy_update (PngMuxData * png_data)
{
gsize i;
for (i = 0; i < png_data->inject_chunks->len; ++i) {
if (png_data->inject_chunks->chunk[i].size > 0 &&
png_data->inject_chunks->chunk[i].data) {
switch (png_data->inject_chunks->chunk[i].type) {
case MD_CHUNK_XMP:
{
metadatamux_wrap_xmp_chunk (&png_data->inject_chunks->chunk[i]);
}
break;
default:
GST_ERROR ("Unexpected chunk for PNG muxer.");
break;
}
}
}
}
/*
* static helper functions implementation
*/
/*
* metadatamux_png_reading:
* @png_data: [in] png data handle
* @buf: [in] data to be parsed. @buf will increment during the parsing step.
* So it will hold the next byte to be read inside a parsing function or on
* the next nested parsing function. And so, @bufsize will decrement.
* @bufsize: [in] size of @buf in bytes. This value will decrement during the
* parsing for the same reason that @buf will advance.
* @offset: is the offset where @step_buf starts from the beginnig of the
* stream
* @step_buf: holds the pointer to the buffer passed to
* #metadatamux_png_parse. It means that any point inside this function
* the offset (related to the beginning of the whole stream) after the last
* byte read so far is "(*buf - step_buf) + offset"
* @next_start: is a pointer after @step_buf which indicates where the next
* call to #metadatamux_png_parse should start on the next call to this
* function. It means, that after return, this function has
* consumed *@next_start - @buf bytes. Which also means that @offset should
* also be incremanted by (*@next_start - @buf) for the next time.
* @next_size: [out] number of minimal bytes in @buf for the next call to this
* function
*
* This function is used to parse a PNG stream step-by-step incrementally.
* If this function quickly finds the place (offset) in which EXIF, IPTC and
* XMP chunk should be written to.
* The found places are written to @png_data->inject_chunks
* @see_also: #metadatamux_png_init
*
* Returns:
* <itemizedlist>
* <listitem><para>%META_PARSING_ERROR
* </para></listitem>
* <listitem><para>%META_PARSING_DONE if parse has finished. Now strip and
* inject chunks has been found. Or some chunk has been found and should be
* held or jumped.
* </para></listitem>
* <listitem><para>%META_PARSING_NEED_MORE_DATA if this function should be
* called again (look @next_start and @next_size)
* </para></listitem>
* </itemizedlist>
*/
/* look for markers */
static MetadataParsingReturn
metadatamux_png_reading (PngMuxData * png_data, guint8 ** buf,
guint32 * bufsize, const guint32 offset, const guint8 * step_buf,
@ -287,3 +386,119 @@ done:
}
/*
* metadatamux_make_crc_table:
* @crc_table: table to be written to.
*
* Creates a startup CRC table. For optimization it should be done only once.
* @see_also: #metadatamux_update_crc
*
* Returns: nothing.
*/
static void
metadatamux_make_crc_table (guint32 crc_table[])
{
guint32 c;
guint16 n, k;
for (n = 0; n < 256; n++) {
c = (guint32) n;
for (k = 0; k < 8; k++) {
if (c & 1)
c = 0xedb88320L ^ (c >> 1);
else
c = c >> 1;
}
crc_table[n] = c;
}
}
/*
* metadatamux_update_crc:
* @crc: seed to calculate the CRC
* @buf: data to calculate the CRC for
* @len: size in bytes of @buf
*
* Calculates the CRC of a data buffer for a seed @crc.
* @see_also: #metadatamux_make_crc_table #metadatamux_calc_crc
*
* Returns: the CRC of the bytes buf[0..len-1].
*/
static guint32
metadatamux_update_crc (guint32 crc, guint8 * buf, guint32 len)
{
guint32 c = crc;
guint32 n;
guint32 crc_table[256];
/* FIXME: make_crc_table should be done once in life
for speed up. It could be written hard coded to a file */
metadatamux_make_crc_table (crc_table);
for (n = 0; n < len; n++) {
c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
}
return c;
}
/*
* metadatamux_calc_crc:
* @buf: data to calculate the CRC for
* @len: size in bytes of @buf
*
* Calculates the CRC of a data buffer.
*
* Returns: the CRC of the bytes buf[0..len-1].
*/
static guint32
metadatamux_calc_crc (guint8 * buf, guint32 len)
{
return metadatamux_update_crc (0xffffffffL, buf, len) ^ 0xffffffffL;
}
/*
* metadatamux_wrap_xmp_chunk:
* @chunk: chunk to be wrapped
*
* Wraps a XMP chunk with proper PNG bytes (mark, size and crc in the end)
*
* Returns: nothing
*/
static void
metadatamux_wrap_xmp_chunk (MetadataChunk * chunk)
{
static const char XmpHeader[] = "XML:com.adobe.xmp";
guint8 *data = NULL;
guint32 crc;
data = g_new (guint8, 12 + 18 + 4 + chunk->size);
memcpy (data + 8, XmpHeader, 18);
memset (data + 8 + 18, 0x00, 4);
memcpy (data + 8 + 18 + 4, chunk->data, chunk->size);
g_free (chunk->data);
chunk->data = data;
chunk->size += 18 + 4;
data[0] = (chunk->size >> 24) & 0xFF;
data[1] = (chunk->size >> 16) & 0xFF;
data[2] = (chunk->size >> 8) & 0xFF;
data[3] = chunk->size & 0xFF;
data[4] = 'i';
data[5] = 'T';
data[6] = 'X';
data[7] = 't';
crc = metadatamux_calc_crc (data + 4, chunk->size + 4);
data[chunk->size + 8] = (crc >> 24) & 0xFF;
data[chunk->size + 9] = (crc >> 16) & 0xFF;
data[chunk->size + 10] = (crc >> 8) & 0xFF;
data[chunk->size + 11] = crc & 0xFF;
chunk->size += 12;
}

View file

@ -44,12 +44,20 @@
#ifndef __METADATAMUX_PNG_H__
#define __METADATAMUX_PNG_H__
/*
* includes
*/
#include <gst/base/gstadapter.h>
#include "metadatatypes.h"
G_BEGIN_DECLS
/*
* enum and types
*/
typedef enum _tag_PngMuxState
{
PNG_MUX_NULL,
@ -69,6 +77,9 @@ typedef struct _tag_PngMuxData
} PngMuxData;
/*
* external function prototypes
*/
extern void
metadatamux_png_init (PngMuxData * png_data,
@ -80,7 +91,8 @@ extern void metadatamux_png_lazy_update (PngMuxData * png_data);
extern MetadataParsingReturn
metadatamux_png_parse (PngMuxData * png_data, guint8 * buf,
guint32 * bufsize, const guint32 offset, guint8 ** next_start, guint32 * next_size);
guint32 * bufsize, const guint32 offset, guint8 ** next_start,
guint32 * next_size);
G_END_DECLS
#endif /* __METADATAMUX_PNG_H__ */

View file

@ -185,7 +185,7 @@ metadataparse_jpeg_dispose (JpegParseData * jpeg_data)
}
/*
* metadata_parse:
* metadataparse_jpeg_parse:
* @jpeg_data: [in] jpeg data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
@ -310,7 +310,7 @@ done:
* @jpeg_data: [in] jpeg data handle
*
* This function do nothing
* @see_also: metadata_lazy_update
* @see_also: #metadata_lazy_update
*
* Returns: nothing
*/

View file

@ -152,7 +152,7 @@ metadataparse_png_dispose (PngParseData * png_data)
}
/*
* metadata_parse:
* metadataparse_png_parse:
* @png_data: [in] png data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
@ -273,7 +273,7 @@ done:
* @png_data: [in] png data handle
*
* This function do nothing
* @see_also: metadata_lazy_update
* @see_also: #metadata_lazy_update
*
* Returns: nothing
*/

View file

@ -22,7 +22,8 @@ metadata_editor_SOURCES = metadata_editor.c
metadata_editor_CFLAGS = \
$(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) $(GLADE_CFLAGS)
metadata_editor_LDADD = \
$(GST_PLUGINS_BASE_LIBS) -lgstinterfaces-0.10 $(GST_LIBS) $(GLADE_LIBS)
$(GST_PLUGINS_BASE_LIBS) -lgstinterfaces-0.10 $(GST_LIBS) \
$(GLADE_LIBS) -Wl -export-dynamic
metadata_editor_LDFLAGS = $(GST_PLUGIN_LDFLAGS)
else
GST_METADATA_TESTS =