2 * Copyright (C) 2007 by INdT
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public License
6 * as published by the Free Software Foundation; either version 2
7 * of the License, or (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 * @author Gustavo Sverzut Barbieri <gustavo.barbieri@openbossa.org>
24 * Reads EXIF tags from images.
26 * @todo: get GPS data.
27 * @todo: check if worth using mmap().
34 #define _XOPEN_SOURCE 600
35 #include <lightmediascanner_plugin.h>
36 #include <lightmediascanner_utils.h>
37 #include <lightmediascanner_db.h>
38 #include <sys/types.h>
49 JPEG_MARKER_SOI = 0xd8,
50 JPEG_MARKER_DQT = 0xdb,
51 JPEG_MARKER_JFIF = 0xe0,
52 JPEG_MARKER_EXIF = 0xe1,
53 JPEG_MARKER_COMM = 0xfe,
54 JPEG_MARKER_SOF0 = 0xc0,
55 JPEG_MARKER_SOF1 = 0xc1,
56 JPEG_MARKER_SOF2 = 0xc2,
57 JPEG_MARKER_SOF9 = 0xc9,
58 JPEG_MARKER_SOF10 = 0xca,
59 JPEG_MARKER_SOS = 0xda
63 * Process SOF JPEG, this contains width and height.
66 _jpeg_sof_process(int fd, unsigned short *width, unsigned short *height)
70 if (read(fd, buf, 6) != 6) {
71 perror("could not read() SOF data");
75 *height = (buf[1] << 8) | buf[2];
76 *width = (buf[3] << 8) | buf[4];
82 * Process COM JPEG, this contains user comment.
85 _jpeg_com_process(int fd, int len, struct lms_string_size *comment)
93 comment->str = malloc(len + 1);
98 if (read(fd, comment->str, len) != len) {
105 if (comment->str[len - 1] == '\0')
108 comment->str[len] = '\0';
111 lms_strstrip(comment->str, &comment->len);
112 if (comment->len == 0) {
121 * Walk JPEG markers in order to get useful information.
124 _jpeg_info_get(int fd, int len, struct lms_image_info *info)
126 unsigned char buf[4];
130 found = info->title.str ? 1 : 0;
131 offset = lseek(fd, len - 2, SEEK_CUR);
134 offset = lseek(fd, offset + len, SEEK_SET);
140 if (read(fd, buf, 4) != 4) {
145 len = ((buf[2] << 8) | buf[3]) - 2;
147 if (buf[0] != 0xff) {
148 fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
152 if (buf[1] == JPEG_MARKER_SOF0 ||
153 buf[1] == JPEG_MARKER_SOF1 ||
154 buf[1] == JPEG_MARKER_SOF2 ||
155 buf[1] == JPEG_MARKER_SOF9 ||
156 buf[1] == JPEG_MARKER_SOF10) {
157 if (_jpeg_sof_process(fd, &info->width, &info->height) != 0)
160 } else if (buf[1] == JPEG_MARKER_COMM && !info->title.str) {
161 if (_jpeg_com_process(fd, len, &info->title) != 0)
164 } else if (buf[1] == JPEG_MARKER_SOS)
167 len += 4; /* add read size */
174 * Read JPEG file start (0xffd8 marker) and return the next
175 * marker type and its length.
178 _jpeg_data_get(int fd, int *type, int *len)
180 unsigned char buf[6];
182 if (lseek(fd, 0, SEEK_SET) != 0) {
187 if (read(fd, buf, 6) != 6) {
192 if (buf[0] != 0xff || buf[1] != JPEG_MARKER_SOI || buf[2] != 0xff) {
193 fprintf(stderr, "ERROR: not JPEG file (magic=%#x %#x %#x)\n",
194 buf[0], buf[1], buf[2]);
199 *len = (buf[4] << 8) | buf[5];
204 #define LE_4BYTE(a) ((a)[0] | ((a)[1] << 8) | ((a)[2] << 16) | ((a)[3] << 24))
205 #define BE_4BYTE(a) (((a)[0] << 24) | ((a)[1] << 16) | ((a)[2] << 8) | (a)[3])
207 #define LE_2BYTE(a) ((a)[0] | ((a)[1] << 8))
208 #define BE_2BYTE(a) (((a)[0] << 8) | (a)[1])
210 #define E_2BTYE(little_endian, a) ((little_endian) ? LE_2BYTE(a) : BE_2BYTE(a))
211 #define E_4BTYE(little_endian, a) ((little_endian) ? LE_4BYTE(a) : BE_4BYTE(a))
214 EXIF_TYPE_BYTE = 1, /* 8 bit unsigned */
215 EXIF_TYPE_ASCII = 2, /* 8 bit byte with 7-bit ASCII code, NULL terminated */
216 EXIF_TYPE_SHORT = 3, /* 2-byte unsigned integer */
217 EXIF_TYPE_LONG = 4, /* 4-byte unsigned integer */
218 EXIF_TYPE_RATIONAL = 5, /* 2 4-byte unsigned integer, 1st = numerator */
219 EXIF_TYPE_UNDEFINED = 7, /* 8-bit byte */
220 EXIF_TYPE_SLONG = 9, /* 4-byte signed integer (2'complement) */
221 EXIF_TYPE_SRATIONAL = 10 /* 2 4-byte signed integer, 1st = numerator */
225 EXIF_TAG_ORIENTATION = 0x0112,
226 EXIF_TAG_ARTIST = 0x013b,
227 EXIF_TAG_USER_COMMENT = 0x9286,
228 EXIF_TAG_IMAGE_DESCRIPTION = 0x010e,
229 EXIF_TAG_DATE_TIME = 0x0132,
230 EXIF_TAG_DATE_TIME_ORIGINAL = 0x9003,
231 EXIF_TAG_DATE_TIME_DIGITIZED = 0x9004,
232 EXIF_TAG_EXIF_IFD_POINTER = 0x8769
244 * Read IFD from stream.
247 _exif_ifd_get(int fd, int little_endian, struct exif_ifd *ifd)
249 unsigned char buf[12];
251 if (read(fd, buf, 12) != 12) {
257 ifd->tag = LE_2BYTE(buf);
258 ifd->type = LE_2BYTE(buf + 2);
259 ifd->count = LE_4BYTE(buf + 4);
260 ifd->offset = LE_4BYTE(buf + 8);
262 ifd->tag = BE_2BYTE(buf);
263 ifd->type = BE_2BYTE(buf + 2);
264 ifd->count = BE_4BYTE(buf + 4);
265 ifd->offset = BE_4BYTE(buf + 8);
271 * Get non-exif data based on Exif tag offset.
273 * This will setup the file description position and call _jpeg_info_get().
276 _exif_extra_get(int fd, int abs_offset, int len, struct lms_image_info *info)
278 if (lseek(fd, abs_offset, SEEK_SET) == -1) {
283 if (_jpeg_info_get(fd, len, info) != 0) {
284 fprintf(stderr, "ERROR: could not get image size.\n");
291 _exif_text_encoding_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
296 count -= 8; /* XXX don't just ignore character code, handle it. */
299 if (lseek(fd, offset, SEEK_SET) == -1) {
304 s->str = malloc(count + 1);
306 if (read(fd, s->str, count) != count) {
313 s->str[count] = '\0';
316 lms_strstrip(s->str, &s->len);
326 _exif_text_ascii_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
334 if (lseek(fd, offset, SEEK_SET) == -1) {
339 s->str = malloc(count);
341 if (read(fd, s->str, count) != count) {
348 s->str[count - 1] = '\0';
351 lms_strstrip(s->str, &s->len);
361 _exif_datetime_get(int fd, int offset)
366 if (lseek(fd, offset, SEEK_SET) == -1) {
371 if (read(fd, buf, 20) != 20) {
377 if (strptime(buf, "%Y:%m:%d %H:%M:%S", &tm)) {
383 static int _exif_private_ifd_get(int fd, int base_offset, int offset, int little_endian, struct lms_image_info *info);
386 * Process IFD contents.
389 _exif_ifd_process(int fd, int count, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
391 int i, torig, tdig, tlast;
393 torig = tdig = tlast = 0;
395 for (i = 0; i < count; i++) {
398 lseek(fd, tiff_base + ifd_offset + i * 12, SEEK_SET);
399 if (_exif_ifd_get(fd, little_endian, &ifd) != 0) {
400 fprintf(stderr, "ERROR: could not read Exif IFD.\n");
405 case EXIF_TAG_ORIENTATION:
406 info->orientation = ifd.offset >> 16;
408 case EXIF_TAG_ARTIST:
409 if (!info->artist.str)
410 _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
413 case EXIF_TAG_USER_COMMENT:
414 if (!info->title.str)
415 _exif_text_encoding_get(fd, ifd.count, tiff_base + ifd.offset,
418 case EXIF_TAG_IMAGE_DESCRIPTION:
419 if (!info->title.str)
420 _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
423 case EXIF_TAG_DATE_TIME:
424 if (torig == 0 && info->date == 0)
425 tlast = _exif_datetime_get(fd, tiff_base + ifd.offset);
427 case EXIF_TAG_DATE_TIME_ORIGINAL:
428 if (torig == 0 && info->date == 0)
429 torig = _exif_datetime_get(fd, tiff_base + ifd.offset);
431 case EXIF_TAG_DATE_TIME_DIGITIZED:
432 if (torig == 0 && info->date == 0)
433 tdig = _exif_datetime_get(fd, tiff_base + ifd.offset);
435 case EXIF_TAG_EXIF_IFD_POINTER:
436 if (ifd.count == 1 && ifd.type == EXIF_TYPE_LONG)
437 _exif_private_ifd_get(fd, ifd.offset, tiff_base,
438 little_endian, info);
446 if (info->date == 0) {
459 * Process Exif IFD (Exif Private Tag), with more specific info.
462 _exif_private_ifd_get(int fd, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
467 if (lseek(fd, tiff_base + ifd_offset, SEEK_SET) == -1) {
472 if (read(fd, buf, 2) != 2) {
477 count = E_2BTYE(little_endian, buf);
478 return _exif_ifd_process(fd, count, ifd_offset + 2, tiff_base,
479 little_endian, info);
483 * Process file as it being Exif, will extract Exif as well as other
484 * JPEG markers (comment, size).
487 _exif_data_get(int fd, int len, struct lms_image_info *info)
489 const unsigned char exif_hdr[6] = "Exif\0";
490 unsigned char buf[8];
491 unsigned int little_endian, offset, count;
492 off_t abs_offset, tiff_base;
494 abs_offset = lseek(fd, 0, SEEK_CUR);
495 if (abs_offset == -1) {
500 if (read(fd, buf, 6) != 6) {
505 memset(info, 0, sizeof(*info));
506 info->orientation = 1;
508 if (memcmp(buf, exif_hdr, 6) != 0)
509 return _exif_extra_get(fd, abs_offset, len, info);
511 if (read(fd, buf, 8) != 8) {
516 if (buf[0] == 'I' && buf[1] == 'I') {
518 offset = LE_4BYTE(buf + 4);
519 } else if (buf[0] == 'M' && buf[1] == 'M') {
521 offset = BE_4BYTE(buf + 4);
523 fprintf(stderr, "ERROR: undefined byte sex \"%2.2s\".\n", buf);
528 if (offset > 0 && lseek(fd, offset, SEEK_CUR) == -1) {
533 tiff_base = abs_offset + 6; /* offsets are relative to TIFF base */
535 if (read(fd, buf, 2) != 2) {
539 count = E_2BTYE(little_endian, buf);
541 _exif_ifd_process(fd, count, 8 + 2, tiff_base,
542 little_endian, info);
544 return _exif_extra_get(fd, abs_offset, len, info);
548 * Process file as it being JFIF
551 _jfif_data_get(int fd, int len, struct lms_image_info *info)
553 unsigned char buf[4];
556 memset(info, 0, sizeof(*info));
557 info->orientation = 1;
559 /* JFIF provides no useful information, try to find out Exif */
560 if (lseek(fd, len - 2, SEEK_CUR) == -1) {
565 if (read(fd, buf, 4) != 4) {
570 new_len = ((buf[2] << 8) | buf[3]);
571 if (buf[0] != 0xff) {
572 fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
576 if (buf[1] == JPEG_MARKER_EXIF)
577 return _exif_data_get(fd, new_len, info);
579 /* rollback to avoid losing initial frame */
580 if (lseek(fd, - len - 2, SEEK_CUR) == -1) {
584 return _jpeg_info_get(fd, len, info);
588 static const char _name[] = "jpeg";
589 static const struct lms_string_size _exts[] = {
590 LMS_STATIC_STRING_SIZE(".jpg"),
591 LMS_STATIC_STRING_SIZE(".jpeg"),
592 LMS_STATIC_STRING_SIZE(".jpe")
596 struct lms_plugin plugin;
597 lms_db_image_t *img_db;
601 _match(struct plugin *p, const char *path, int len, int base)
605 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
609 return (void*)(i + 1);
613 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
615 struct lms_image_info info = {0};
616 int fd, type, len, r;
618 fd = open(finfo->path, O_RDONLY);
624 if (_jpeg_data_get(fd, &type, &len) != 0) {
629 if (type == JPEG_MARKER_EXIF) {
630 if (_exif_data_get(fd, len, &info) != 0) {
631 fprintf(stderr, "ERROR: could not get EXIF info (%s).\n",
636 } else if (type == JPEG_MARKER_JFIF || type == JPEG_MARKER_DQT) {
637 if (_jfif_data_get(fd, len, &info) != 0) {
638 fprintf(stderr, "ERROR: could not get JPEG size (%s).\n",
644 fprintf(stderr, "ERROR: unsupported JPEG marker %#x (%s)\n", type,
651 info.date = finfo->mtime;
653 if (!info.title.str) {
656 ext_idx = ((int)match) - 1;
657 info.title.len = finfo->path_len - finfo->base - _exts[ext_idx].len;
658 info.title.str = malloc((info.title.len + 1) * sizeof(char));
659 memcpy(info.title.str, finfo->path + finfo->base, info.title.len);
660 info.title.str[info.title.len] = '\0';
664 lms_charset_conv(ctxt->cs_conv, &info.title.str, &info.title.len);
666 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
669 r = lms_db_image_add(plugin->img_db, &info);
673 free(info.title.str);
675 free(info.artist.str);
677 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
684 _setup(struct plugin *plugin, struct lms_context *ctxt)
686 plugin->img_db = lms_db_image_new(ctxt->db);
694 _start(struct plugin *plugin, struct lms_context *ctxt)
696 return lms_db_image_start(plugin->img_db);
700 _finish(struct plugin *plugin, struct lms_context *ctxt)
703 return lms_db_image_free(plugin->img_db);
710 _close(struct plugin *plugin)
716 API struct lms_plugin *
717 lms_plugin_open(void)
719 struct plugin *plugin;
721 plugin = malloc(sizeof(*plugin));
722 plugin->plugin.name = _name;
723 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
724 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
725 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
726 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
727 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
728 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
730 return (struct lms_plugin *)plugin;