2 * Copyright (C) 2008-2011 by ProFUSION embedded systems
3 * Copyright (C) 2007 by INdT
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1 of
8 * the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful, but
11 * WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * @author Gustavo Sverzut Barbieri <barbieri@profusion.mobi>
26 * Reads EXIF tags from images.
28 * @todo: get GPS data.
29 * @todo: check if worth using mmap().
32 #include <lightmediascanner_plugin.h>
33 #include <lightmediascanner_utils.h>
34 #include <lightmediascanner_db.h>
35 #include <shared/util.h>
37 #include <sys/types.h>
47 static const struct lms_string_size dlna_mime =
48 LMS_STATIC_STRING_SIZE("image/jpeg");
49 static const struct lms_string_size dlna_sm_ico =
50 LMS_STATIC_STRING_SIZE("JPEG_SM_ICO");
51 static const struct lms_string_size dlna_lrg_ico =
52 LMS_STATIC_STRING_SIZE("JPEG_LRG_ICO");
53 static const struct lms_string_size dlna_tn =
54 LMS_STATIC_STRING_SIZE("JPEG_TN");
55 static const struct lms_string_size dlna_sm =
56 LMS_STATIC_STRING_SIZE("JPEG_SM");
57 static const struct lms_string_size dlna_med =
58 LMS_STATIC_STRING_SIZE("JPEG_MED");
59 static const struct lms_string_size dlna_lrg =
60 LMS_STATIC_STRING_SIZE("JPEG_LRG");
63 _fill_dlna_profile(struct lms_image_info *info)
65 const unsigned short w = info->width;
66 const unsigned short h = info->height;
68 info->dlna_mime = dlna_mime;
73 if (w == 48 && h == 48)
74 info->dlna_profile = dlna_sm_ico;
75 else if (w == 120 && h == 120)
76 info->dlna_profile = dlna_lrg_ico;
77 else if (w <= 160 && h <= 160)
78 info->dlna_profile = dlna_tn;
79 else if (w <= 640 && h <= 480)
80 info->dlna_profile = dlna_sm;
81 else if (w <= 1024 && h <= 768)
82 info->dlna_profile = dlna_med;
83 else if (w <= 4096 && h <= 4096)
84 info->dlna_profile = dlna_lrg;
88 JPEG_MARKER_SOI = 0xd8,
89 JPEG_MARKER_DQT = 0xdb,
90 JPEG_MARKER_JFIF = 0xe0,
91 JPEG_MARKER_EXIF = 0xe1,
92 JPEG_MARKER_COMM = 0xfe,
93 JPEG_MARKER_SOF0 = 0xc0,
94 JPEG_MARKER_SOF1 = 0xc1,
95 JPEG_MARKER_SOF2 = 0xc2,
96 JPEG_MARKER_SOF9 = 0xc9,
97 JPEG_MARKER_SOF10 = 0xca,
98 JPEG_MARKER_SOS = 0xda
102 * Process SOF JPEG, this contains width and height.
105 _jpeg_sof_process(int fd, unsigned short *width, unsigned short *height)
107 unsigned char buf[6];
109 if (read(fd, buf, 6) != 6) {
110 perror("could not read() SOF data");
114 *height = (buf[1] << 8) | buf[2];
115 *width = (buf[3] << 8) | buf[4];
121 * Process COM JPEG, this contains user comment.
124 _jpeg_com_process(int fd, int len, struct lms_string_size *comment)
132 comment->str = malloc(len + 1);
137 if (read(fd, comment->str, len) != len) {
144 if (comment->str[len - 1] == '\0')
147 comment->str[len] = '\0';
150 lms_string_size_strip_and_free(comment);
156 * Walk JPEG markers in order to get useful information.
159 _jpeg_info_get(int fd, int len, struct lms_image_info *info)
161 unsigned char buf[4];
165 found = info->title.str ? 1 : 0;
166 offset = lseek(fd, len - 2, SEEK_CUR);
169 offset = lseek(fd, offset + len, SEEK_SET);
175 if (read(fd, buf, 4) != 4) {
180 len = ((buf[2] << 8) | buf[3]) - 2;
182 if (buf[0] != 0xff) {
183 fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
187 if (buf[1] == JPEG_MARKER_SOF0 ||
188 buf[1] == JPEG_MARKER_SOF1 ||
189 buf[1] == JPEG_MARKER_SOF2 ||
190 buf[1] == JPEG_MARKER_SOF9 ||
191 buf[1] == JPEG_MARKER_SOF10) {
192 if (_jpeg_sof_process(fd, &info->width, &info->height) != 0)
195 } else if (buf[1] == JPEG_MARKER_COMM && !info->title.str) {
196 if (_jpeg_com_process(fd, len, &info->title) != 0)
199 } else if (buf[1] == JPEG_MARKER_SOS)
202 len += 4; /* add read size */
209 * Read JPEG file start (0xffd8 marker) and return the next
210 * marker type and its length.
213 _jpeg_data_get(int fd, int *type, int *len)
215 unsigned char buf[6];
217 if (lseek(fd, 0, SEEK_SET) != 0) {
222 if (read(fd, buf, 6) != 6) {
227 if (buf[0] != 0xff || buf[1] != JPEG_MARKER_SOI || buf[2] != 0xff) {
228 fprintf(stderr, "ERROR: not JPEG file (magic=%#x %#x %#x)\n",
229 buf[0], buf[1], buf[2]);
234 *len = (buf[4] << 8) | buf[5];
239 #define E_2BTYE(little_endian, a) ((little_endian) ? get_le16(a) : get_be16(a))
240 #define E_4BTYE(little_endian, a) ((little_endian) ? get_le32(a) : get_be32(a))
243 EXIF_TYPE_BYTE = 1, /* 8 bit unsigned */
244 EXIF_TYPE_ASCII = 2, /* 8 bit byte with 7-bit ASCII code, NULL terminated */
245 EXIF_TYPE_SHORT = 3, /* 2-byte unsigned integer */
246 EXIF_TYPE_LONG = 4, /* 4-byte unsigned integer */
247 EXIF_TYPE_RATIONAL = 5, /* 2 4-byte unsigned integer, 1st = numerator */
248 EXIF_TYPE_UNDEFINED = 7, /* 8-bit byte */
249 EXIF_TYPE_SLONG = 9, /* 4-byte signed integer (2'complement) */
250 EXIF_TYPE_SRATIONAL = 10 /* 2 4-byte signed integer, 1st = numerator */
254 EXIF_TAG_ORIENTATION = 0x0112,
255 EXIF_TAG_ARTIST = 0x013b,
256 EXIF_TAG_USER_COMMENT = 0x9286,
257 EXIF_TAG_IMAGE_DESCRIPTION = 0x010e,
258 EXIF_TAG_DATE_TIME = 0x0132,
259 EXIF_TAG_DATE_TIME_ORIGINAL = 0x9003,
260 EXIF_TAG_DATE_TIME_DIGITIZED = 0x9004,
261 EXIF_TAG_EXIF_IFD_POINTER = 0x8769
273 * Read IFD from stream.
276 _exif_ifd_get(int fd, int little_endian, struct exif_ifd *ifd)
278 unsigned char buf[12];
280 if (read(fd, buf, 12) != 12) {
286 ifd->tag = get_le16(buf);
287 ifd->type = get_le16(buf + 2);
288 ifd->count = get_le32(buf + 4);
289 ifd->offset = get_le32(buf + 8);
291 ifd->tag = get_be16(buf);
292 ifd->type = get_be16(buf + 2);
293 ifd->count = get_be32(buf + 4);
294 ifd->offset = get_be32(buf + 8);
300 * Get non-exif data based on Exif tag offset.
302 * This will setup the file description position and call _jpeg_info_get().
305 _exif_extra_get(int fd, int abs_offset, int len, struct lms_image_info *info)
307 if (lseek(fd, abs_offset, SEEK_SET) == -1) {
312 if (_jpeg_info_get(fd, len, info) != 0) {
313 fprintf(stderr, "ERROR: could not get image size.\n");
320 _exif_text_encoding_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
325 count -= 8; /* XXX don't just ignore character code, handle it. */
328 if (lseek(fd, offset, SEEK_SET) == -1) {
333 s->str = malloc(count + 1);
335 if (read(fd, s->str, count) != count) {
342 s->str[count] = '\0';
345 lms_string_size_strip_and_free(s);
351 _exif_text_ascii_get(int fd, unsigned int count, int offset, struct lms_string_size *s)
359 if (lseek(fd, offset, SEEK_SET) == -1) {
364 s->str = malloc(count);
366 if (read(fd, s->str, count) != count) {
373 s->str[count - 1] = '\0';
376 lms_string_size_strip_and_free(s);
382 _exif_datetime_get(int fd, int offset)
387 if (lseek(fd, offset, SEEK_SET) == -1) {
392 if (read(fd, buf, 20) != 20) {
398 if (strptime(buf, "%Y:%m:%d %H:%M:%S", &tm)) {
404 static int _exif_private_ifd_get(int fd, int base_offset, int offset, int little_endian, struct lms_image_info *info);
407 * Process IFD contents.
410 _exif_ifd_process(int fd, int count, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
412 int i, torig, tdig, tlast;
414 torig = tdig = tlast = 0;
416 for (i = 0; i < count; i++) {
419 lseek(fd, tiff_base + ifd_offset + i * 12, SEEK_SET);
420 if (_exif_ifd_get(fd, little_endian, &ifd) != 0) {
421 fprintf(stderr, "ERROR: could not read Exif IFD.\n");
426 case EXIF_TAG_ORIENTATION:
427 info->orientation = ifd.offset >> 16;
429 case EXIF_TAG_ARTIST:
430 if (!info->artist.str)
431 _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
434 case EXIF_TAG_USER_COMMENT:
435 if (!info->title.str)
436 _exif_text_encoding_get(fd, ifd.count, tiff_base + ifd.offset,
439 case EXIF_TAG_IMAGE_DESCRIPTION:
440 if (!info->title.str)
441 _exif_text_ascii_get(fd, ifd.count, tiff_base + ifd.offset,
444 case EXIF_TAG_DATE_TIME:
445 if (torig == 0 && info->date == 0)
446 tlast = _exif_datetime_get(fd, tiff_base + ifd.offset);
448 case EXIF_TAG_DATE_TIME_ORIGINAL:
449 if (torig == 0 && info->date == 0)
450 torig = _exif_datetime_get(fd, tiff_base + ifd.offset);
452 case EXIF_TAG_DATE_TIME_DIGITIZED:
453 if (torig == 0 && info->date == 0)
454 tdig = _exif_datetime_get(fd, tiff_base + ifd.offset);
456 case EXIF_TAG_EXIF_IFD_POINTER:
457 if (ifd.count == 1 && ifd.type == EXIF_TYPE_LONG)
458 _exif_private_ifd_get(fd, ifd.offset, tiff_base,
459 little_endian, info);
467 if (info->date == 0) {
480 * Process Exif IFD (Exif Private Tag), with more specific info.
483 _exif_private_ifd_get(int fd, int ifd_offset, int tiff_base, int little_endian, struct lms_image_info *info)
488 if (lseek(fd, tiff_base + ifd_offset, SEEK_SET) == -1) {
493 if (read(fd, buf, 2) != 2) {
498 count = E_2BTYE(little_endian, buf);
499 return _exif_ifd_process(fd, count, ifd_offset + 2, tiff_base,
500 little_endian, info);
504 * Process file as it being Exif, will extract Exif as well as other
505 * JPEG markers (comment, size).
508 _exif_data_get(int fd, int len, struct lms_image_info *info)
510 const unsigned char exif_hdr[6] = "Exif\0";
511 unsigned char buf[8];
512 unsigned int little_endian, offset, count;
513 off_t abs_offset, tiff_base;
515 abs_offset = lseek(fd, 0, SEEK_CUR);
516 if (abs_offset == -1) {
521 if (read(fd, buf, 6) != 6) {
526 memset(info, 0, sizeof(*info));
527 info->orientation = 1;
529 if (memcmp(buf, exif_hdr, 6) != 0)
530 return _exif_extra_get(fd, abs_offset, len, info);
532 if (read(fd, buf, 8) != 8) {
537 if (buf[0] == 'I' && buf[1] == 'I') {
539 offset = get_le32(buf + 4);
540 } else if (buf[0] == 'M' && buf[1] == 'M') {
542 offset = get_be32(buf + 4);
544 fprintf(stderr, "ERROR: undefined byte sex \"%2.2s\".\n", buf);
549 if (offset > 0 && lseek(fd, offset, SEEK_CUR) == -1) {
554 tiff_base = abs_offset + 6; /* offsets are relative to TIFF base */
556 if (read(fd, buf, 2) != 2) {
560 count = E_2BTYE(little_endian, buf);
562 _exif_ifd_process(fd, count, 8 + 2, tiff_base,
563 little_endian, info);
565 return _exif_extra_get(fd, abs_offset, len, info);
569 * Process file as it being JFIF
572 _jfif_data_get(int fd, int len, struct lms_image_info *info)
574 unsigned char buf[4];
577 memset(info, 0, sizeof(*info));
578 info->orientation = 1;
580 /* JFIF provides no useful information, try to find out Exif */
581 if (lseek(fd, len - 2, SEEK_CUR) == -1) {
586 if (read(fd, buf, 4) != 4) {
591 new_len = ((buf[2] << 8) | buf[3]);
592 if (buf[0] != 0xff) {
593 fprintf(stderr, "ERROR: expected 0xff marker, got %#x\n", buf[0]);
597 if (buf[1] == JPEG_MARKER_EXIF)
598 return _exif_data_get(fd, new_len, info);
600 /* rollback to avoid losing initial frame */
601 if (lseek(fd, - len - 2, SEEK_CUR) == -1) {
605 return _jpeg_info_get(fd, len, info);
609 static const char _name[] = "jpeg";
610 static const struct lms_string_size _exts[] = {
611 LMS_STATIC_STRING_SIZE(".jpg"),
612 LMS_STATIC_STRING_SIZE(".jpeg"),
613 LMS_STATIC_STRING_SIZE(".jpe")
615 static const char *_cats[] = {
620 static const char *_authors[] = {
621 "Gustavo Sverzut Barbieri",
626 struct lms_plugin plugin;
627 lms_db_image_t *img_db;
631 _match(struct plugin *p, const char *path, int len, int base)
635 i = lms_which_extension(path, len, _exts, LMS_ARRAY_SIZE(_exts));
639 return (void*)(i + 1);
643 _parse(struct plugin *plugin, struct lms_context *ctxt, const struct lms_file_info *finfo, void *match)
645 struct lms_image_info info = { };
646 int fd, type, len, r;
648 fd = open(finfo->path, O_RDONLY);
654 if (_jpeg_data_get(fd, &type, &len) != 0) {
659 if (type == JPEG_MARKER_EXIF) {
660 if (_exif_data_get(fd, len, &info) != 0) {
661 fprintf(stderr, "ERROR: could not get EXIF info (%s).\n",
666 } else if (type == JPEG_MARKER_JFIF || type == JPEG_MARKER_DQT) {
667 if (_jfif_data_get(fd, len, &info) != 0) {
668 fprintf(stderr, "ERROR: could not get JPEG size (%s).\n",
674 fprintf(stderr, "ERROR: unsupported JPEG marker %#x (%s)\n", type,
681 info.date = finfo->mtime;
684 lms_name_from_path(&info.title, finfo->path, finfo->path_len,
685 finfo->base, _exts[((long) match) - 1].len,
688 lms_charset_conv(ctxt->cs_conv, &info.artist.str, &info.artist.len);
690 _fill_dlna_profile(&info);
693 r = lms_db_image_add(plugin->img_db, &info);
696 free(info.title.str);
697 free(info.artist.str);
699 posix_fadvise(fd, 0, 0, POSIX_FADV_DONTNEED);
706 _setup(struct plugin *plugin, struct lms_context *ctxt)
708 plugin->img_db = lms_db_image_new(ctxt->db);
716 _start(struct plugin *plugin, struct lms_context *ctxt)
718 return lms_db_image_start(plugin->img_db);
722 _finish(struct plugin *plugin, struct lms_context *ctxt)
725 return lms_db_image_free(plugin->img_db);
732 _close(struct plugin *plugin)
738 API struct lms_plugin *
739 lms_plugin_open(void)
741 struct plugin *plugin;
743 plugin = malloc(sizeof(*plugin));
744 plugin->plugin.name = _name;
745 plugin->plugin.match = (lms_plugin_match_fn_t)_match;
746 plugin->plugin.parse = (lms_plugin_parse_fn_t)_parse;
747 plugin->plugin.close = (lms_plugin_close_fn_t)_close;
748 plugin->plugin.setup = (lms_plugin_setup_fn_t)_setup;
749 plugin->plugin.start = (lms_plugin_start_fn_t)_start;
750 plugin->plugin.finish = (lms_plugin_finish_fn_t)_finish;
752 return (struct lms_plugin *)plugin;
755 API const struct lms_plugin_info *
756 lms_plugin_info(void)
758 static struct lms_plugin_info info = {
764 "http://lms.garage.maemo.org"