summaryrefslogtreecommitdiffstats
path: root/ext/metadata/metadatamuxjpeg.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/metadata/metadatamuxjpeg.c')
-rw-r--r--ext/metadata/metadatamuxjpeg.c371
1 files changed, 281 insertions, 90 deletions
diff --git a/ext/metadata/metadatamuxjpeg.c b/ext/metadata/metadatamuxjpeg.c
index 5384f30a..d8aa2cdd 100644
--- a/ext/metadata/metadatamuxjpeg.c
+++ b/ext/metadata/metadatamuxjpeg.c
@@ -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/";
-
- 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,
@@ -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 */