1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
|
/*
* GStreamer
* Copyright 2007 Edgard Lima <edgard.lima@indt.org.br>
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Alternatively, the contents of this file may be used under the
* GNU Lesser General Public License Version 2.1 (the "LGPL"), in
* which case the following provisions apply instead of the ones
* mentioned above:
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* SECTION: 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
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "metadatamuxjpeg.h"
#include "metadataxmp.h"
#include <string.h>
#ifdef HAVE_IPTC
#include <libiptcdata/iptc-jpeg.h>
#endif
GST_DEBUG_CATEGORY_EXTERN (gst_metadata_mux_debug);
#define GST_CAT_DEFAULT gst_metadata_mux_debug
/*
* 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);
static void
metadatamux_wrap_chunk (MetadataChunk * chunk, const guint8 * buf,
guint32 buf_size, guint8 a, guint8 b);
#ifdef HAVE_IPTC
static gboolean
metadatamux_wrap_iptc_with_ps3 (unsigned char **buf, unsigned int *buf_size);
#endif /* #ifdef HAVE_IPTC */
/*
* 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,
MetadataChunkArray * strip_chunks, MetadataChunkArray * inject_chunks)
{
jpeg_data->state = JPEG_MUX_NULL;
jpeg_data->strip_chunks = strip_chunks;
jpeg_data->inject_chunks = inject_chunks;
}
/*
* 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->inject_chunks = NULL;
jpeg_data->strip_chunks = NULL;
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,
guint32 * next_size)
{
int ret = META_PARSING_DONE;
const guint8 *step_buf = buf;
*next_start = buf;
if (jpeg_data->state == JPEG_MUX_NULL) {
guint8 mark[2];
if (*bufsize < 2) {
GST_INFO ("need more data");
*next_size = (buf - *next_start) + 2;
ret = META_PARSING_NEED_MORE_DATA;
goto done;
}
mark[0] = READ (buf, *bufsize);
mark[1] = READ (buf, *bufsize);
if (mark[0] != 0xFF || mark[1] != 0xD8) {
GST_INFO ("missing marker");
ret = META_PARSING_ERROR;
goto done;
}
jpeg_data->state = JPEG_MUX_READING;
}
while (ret == META_PARSING_DONE) {
switch (jpeg_data->state) {
case JPEG_MUX_READING:
GST_DEBUG ("start reading");
ret =
metadatamux_jpeg_reading (jpeg_data, &buf, bufsize,
offset, step_buf, next_start, next_size);
break;
case JPEG_MUX_DONE:
goto done;
break;
default:
GST_INFO ("invalid parser state");
ret = META_PARSING_ERROR;
break;
}
}
done:
GST_INFO ("finishing: %d", ret);
return ret;
}
/*
* 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;
MetadataChunkArray *chunks = jpeg_data->inject_chunks;
GST_INFO ("checking %" G_GSIZE_FORMAT " chunks", chunks->len);
for (i = 0; i < chunks->len; ++i) {
GST_INFO ("checking chunk[%" G_GSIZE_FORMAT "], type=%d, len=%u",
i, chunks->chunk[i].type, chunks->chunk[i].size);
if (chunks->chunk[i].size > 0 && chunks->chunk[i].data) {
switch (chunks->chunk[i].type) {
case MD_CHUNK_EXIF:
metadatamux_wrap_chunk (&chunks->chunk[i], NULL, 0, 0xFF, 0xE1);
has_exif = TRUE;
break;
case MD_CHUNK_IPTC:
#ifdef HAVE_IPTC
if (metadatamux_wrap_iptc_with_ps3 (&chunks->chunk[i].data,
&chunks->chunk[i].size)) {
metadatamux_wrap_chunk (&chunks->chunk[i], NULL, 0, 0xFF, 0xED);
} else {
GST_ERROR ("Invalid IPTC chunk\n");
metadata_chunk_array_remove_by_index (chunks, i);
continue;
}
#endif /* #ifdef HAVE_IPTC */
break;
case MD_CHUNK_XMP:
metadatamux_wrap_chunk (&chunks->chunk[i],
(guint8 *) XMP_HEADER, sizeof (XMP_HEADER), 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>
*/
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)
{
int ret = META_PARSING_ERROR;
guint8 mark[2] = { 0x00, 0x00 };
guint16 chunk_size = 0;
gint64 new_chunk_offset = 0;
MetadataChunk chunk;
gboolean jfif_found = FALSE;
static const char JfifHeader[] = "JFIF";
*next_start = *buf;
if (*bufsize < 2) {
GST_INFO ("need more data");
*next_size = (*buf - *next_start) + 2;
ret = META_PARSING_NEED_MORE_DATA;
goto done;
}
mark[0] = READ (*buf, *bufsize);
mark[1] = READ (*buf, *bufsize);
GST_DEBUG ("parsing JPEG marker : 0x%02x%02x", mark[0], mark[1]);
if (mark[0] == 0xFF) {
chunk_size = READ (*buf, *bufsize) << 8;
chunk_size += READ (*buf, *bufsize);
if (mark[1] == 0xE0) { /* APP0 - may be JFIF */
/* FIXME: whats the 14 ? according to
* http://en.wikipedia.org/wiki/JFIF#JFIF_segment_format
* its the jfif segment without thumbnails
*/
if (chunk_size >= 14 + 2) {
if (*bufsize < sizeof (JfifHeader)) {
GST_INFO ("need more data");
*next_size = (*buf - *next_start) + sizeof (JfifHeader);
ret = META_PARSING_NEED_MORE_DATA;
goto done;
}
if (0 == memcmp (JfifHeader, *buf, sizeof (JfifHeader))) {
jfif_found = TRUE;
}
} else {
/* FIXME: should we check if the first chunk is EXIF? */
GST_INFO ("chunk size too small %u", chunk_size);
}
}
if (!jfif_found) {
GST_INFO ("no jfif found, will insert it as needed");
}
new_chunk_offset = 2;
/* EXIF will always be in the begining */
memset (&chunk, 0x00, sizeof (MetadataChunk));
chunk.offset_orig = 2;
chunk.type = MD_CHUNK_EXIF;
metadata_chunk_array_append_sorted (jpeg_data->inject_chunks, &chunk);
if (jfif_found) {
/* remove JFIF chunk */
/* this acation can be canceled with lazy update if no Exif is add */
memset (&chunk, 0x00, sizeof (MetadataChunk));
chunk.offset_orig = 2;
chunk.size = chunk_size + 2; /* chunk size plus app marker */
chunk.type = MD_CHUNK_UNKNOWN;
metadata_chunk_array_append_sorted (jpeg_data->strip_chunks, &chunk);
new_chunk_offset = chunk.offset_orig + chunk.size;
}
/* IPTC */
memset (&chunk, 0x00, sizeof (MetadataChunk));
chunk.offset_orig = new_chunk_offset;
chunk.type = MD_CHUNK_IPTC;
metadata_chunk_array_append_sorted (jpeg_data->inject_chunks, &chunk);
/* XMP */
memset (&chunk, 0x00, sizeof (MetadataChunk));
chunk.offset_orig = new_chunk_offset;
chunk.type = MD_CHUNK_XMP;
metadata_chunk_array_append_sorted (jpeg_data->inject_chunks, &chunk);
jpeg_data->state = JPEG_MUX_DONE;
ret = META_PARSING_DONE;
} else {
GST_INFO ("invalid JPEG chunk");
ret = META_PARSING_ERROR;
}
done:
return ret;
}
/*
* 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 */
|