+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>
DTS_DIR=
endif
-if USE_METADATA
-METADATA_DIR=metadata
-else
-METADATA_DIR=
-endif
-
if USE_FAAC
FAAC_DIR=faac
else
MPEG2ENC_DIR=
endif
+if USE_METADATA
+METADATA_DIR=metadata
+else
+METADATA_DIR=
+endif
+
# if USE_MPLEX
# MPLEX_DIR=mplex
# else
$(LIBFAME_DIR) \
$(LIBMMS_DIR) \
$(MPEG2ENC_DIR) \
+ $(METADATA_DIR) \
$(MPLEX_DIR) \
$(MUSEPACK_DIR) \
$(MUSICBRAINZ_DIR) \
cdaudio \
dc1394 \
directfb \
- metadata \
faac \
faad \
gio \
libmms \
dts \
divx \
+ metadata \
mpeg2enc \
musepack \
musicbrainz \
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
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,
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);
* 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
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;
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;
+ }
}
}
}
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;
+ }
}
}
}
* @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
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;
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;
/* 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;
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)
}
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,
guint size_orig;
GstBuffer *prepend = NULL;
gboolean need_append = FALSE;
+ gboolean into_inject;
filter = GST_BASE_METADATA (GST_PAD_PARENT (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 (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 > 1) {
+ 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) {
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));
gst_query_set_position (query, GST_FORMAT_BYTES, filter->offset);
ret = TRUE;
}
+
break;
case GST_QUERY_DURATION:
ret = TRUE;
}
}
+
break;
case GST_QUERY_FORMATS:
gst_query_set_formats (query, 1, GST_FORMAT_BYTES);
} 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)
+
+/*
+ * 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)
+
+/*
+ * 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)
-#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_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)
-#define GST_BASE_METADATA_IMG_TYPE(obj) (GST_BASE_METADATA_CAST (obj)->img_type)
+/*
+ * 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
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;
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);
* 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>
#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/";
-
- 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);
- }
+/*
+ * extern functions implementations
+ */
-}
+/*
+ * 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,
}
+/*
+ * 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)
{
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,
}
+/*
+ * 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,
}
+
+/*
+ * 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 */
#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
} JpegMuxData;
+/*
+ * external function prototypes
+ */
extern void
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__ */
* 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>
-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);
+/*
+ * defines and macros
+ */
#define READ(buf, size) ( (size)--, *((buf)++) )
-static void
-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;
- }
-}
-
-static guint32
-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 */
- make_crc_table (crc_table);
+/*
+ * static helper functions declaration
+ */
- for (n = 0; n < len; n++) {
- c = crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8);
- }
- return c;
-}
+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);
-/* 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_make_crc_table (guint32 crc_table[]);
+static guint32 metadatamux_update_crc (guint32 crc, guint8 * buf, guint32 len);
-static void
-metadatamux_wrap_xmp_chunk (MetadataChunk * chunk)
-{
- static const char XmpHeader[] = "XML:com.adobe.xmp";
- guint8 *data = NULL;
- guint32 crc;
+static guint32 metadatamux_calc_crc (guint8 * buf, guint32 len);
- data = g_new (guint8, 12 + 18 + 4 + chunk->size);
+static void metadatamux_wrap_xmp_chunk (MetadataChunk * chunk);
- 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;
+/*
+ * extern functions implementations
+ */
- 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,
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)
{
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,
}
+/*
+ * 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,
}
+
+/*
+ * 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;
+
+}
#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,
} PngMuxData;
+/*
+ * external function prototypes
+ */
extern void
metadatamux_png_init (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__ */
}
/*
- * metadata_parse:
+ * metadataparse_jpeg_parse:
* @jpeg_data: [in] jpeg data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
* @jpeg_data: [in] jpeg data handle
*
* This function do nothing
- * @see_also: metadata_lazy_update
+ * @see_also: #metadata_lazy_update
*
* Returns: nothing
*/
}
/*
- * metadata_parse:
+ * metadataparse_png_parse:
* @png_data: [in] png data handle
* @buf: [in] data to be parsed
* @bufsize: [in] size of @buf in bytes
* @png_data: [in] png data handle
*
* This function do nothing
- * @see_also: metadata_lazy_update
+ * @see_also: #metadata_lazy_update
*
* Returns: nothing
*/
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 =