Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Added Exif feature

  • Loading branch information...
commit e002c0b1be44793ccdd84a1798e7e509351c065f 1 parent a6b6116
Angel Carpintero authored
1  CHANGELOG
View
@@ -38,6 +38,7 @@ Features
* Implemented new logging system
http://www.lavrsen.dk/foswiki/bin/view/Motion/MotionLog (Angel Carpintero)
* Added a macro MOTION_LOG , no need to add __FUNCTION__ anymore. (Angel Carpintero)
+ * Added EXTIF feature for jpeg images , http://www.lavrsen.dk/foswiki/bin/view/Motion/ExifTaggingPatch (Wim Lewis)
Bugfixes
* Avoid segfault detecting strerror_r() version GNU or SUSv3. (Angel Carpintero)
11 conf.c
View
@@ -154,6 +154,7 @@ struct config conf_template = {
despeckle_filter: NULL,
area_detect: NULL,
minimum_motion_frames: 1,
+ exif_text: NULL,
pid_file: NULL,
log_file: NULL,
log_level: LEVEL_DEFAULT+10,
@@ -894,6 +895,16 @@ config_param config_params[] = {
print_bool
},
{
+ "exif_text",
+ "# Text to include in a JPEG EXIF comment\n"
+ "# May be any text, including conversion specifiers.\n"
+ "# The EXIF timestamp is included independent of this text.",
+ 0,
+ CONF_OFFSET(exif_text),
+ copy_string,
+ print_string
+ },
+ {
"target_dir",
"\n############################################################\n"
"# Target Directories and filenames For Images And Films\n"
1  conf.h
View
@@ -131,6 +131,7 @@ struct config {
const char *despeckle_filter;
const char *area_detect;
int minimum_motion_frames;
+ const char *exif_text;
char *pid_file;
int argc;
char **argv;
5 motion-dist.conf.in
View
@@ -392,6 +392,11 @@ text_event %Y%m%d%H%M%S
text_double off
+# Text to include in a JPEG EXIF comment
+# May be any text, including conversion specifiers.
+# The EXIF timestamp is included independent of this text.
+;exif_text %i%J/%K%L
+
############################################################
# Target Directories and filenames For Images And Films
# For the options snapshot_, picture_, movie_ and timelapse_filename
5 motion.1
View
@@ -111,6 +111,11 @@ Values: 0 - 2147483647 / Default: 60
.br
Event Gap is the seconds of no motion detection that triggers the end of an event. An event is defined as a series of motion images taken within a short timeframe.
.TP
+.B exif_text string
+Values: Max 4095 characters / Default: Not defined
+.br
+Text to include in a JPEG EXIF comment , may be any text, including conversion specifiers. The EXIF timestamp is included independent of this text.
+.TP
.B extpipe string
Values: Max 4095 characters / Default: Not defined
.br
2  motion.c
View
@@ -3110,7 +3110,7 @@ int myfclose(FILE* fh)
*
* Returns: number of bytes written to the string s
*/
-size_t mystrftime(struct context *cnt, char *s, size_t max, const char *userformat,
+size_t mystrftime(const struct context *cnt, char *s, size_t max, const char *userformat,
const struct tm *tm, const char *filename, int sqltype)
{
char formatstring[PATH_MAX] = "";
2  motion.h
View
@@ -454,6 +454,6 @@ void * mymalloc(size_t);
void * myrealloc(void *, size_t, const char *);
FILE * myfopen(const char *, const char *, size_t);
int myfclose(FILE *);
-size_t mystrftime(struct context *, char *, size_t, const char *, const struct tm *, const char *, int);
+size_t mystrftime(const struct context *, char *, size_t, const char *, const struct tm *, const char *, int);
int create_path(const char *);
#endif /* _INCLUDE_MOTION_H */
338 picture.c
View
@@ -3,6 +3,7 @@
* Various funtions for saving/loading pictures.
* Copyright 2002 by Jeroen Vreeken (pe1rxq@amsat.org)
* Portions of this file are Copyright by Lionnel Maugis
+ * Portions of this file are Copyright 2010 by Wim Lewis (wiml@hhhh.org)
* This software is distributed under the GNU public license version 2
* See also the file 'COPYING'.
*
@@ -11,6 +12,8 @@
#include "picture.h"
#include "event.h"
+#include <assert.h>
+
#undef HAVE_STDLIB_H
#include <jpeglib.h>
#include <jerror.h>
@@ -80,6 +83,320 @@ static GLOBAL(int) _jpeg_mem_size(j_compress_ptr cinfo)
return dest->jpegsize;
}
+/* EXIF image data is always in TIFF format, even if embedded in another
+ * file type. This consists of a constant header (TIFF file header,
+ * IFD header) followed by the tags in the IFD and then the data
+ * from any tags which do not fit inline in the IFD.
+ *
+ * The tags we write in the main IFD are:
+ * 0x010E Image description
+ * 0x8769 Exif sub-IFD
+ * 0x882A Time zone of time stamps
+ * and in the Exif sub-IFD:
+ * 0x9000 Exif version
+ * 0x9003 File date and time
+ * 0x9291 File date and time subsecond info
+ * But we omit any empty IFDs.
+ */
+
+#define TIFF_TAG_IMAGE_DESCRIPTION 0x010E
+#define TIFF_TAG_DATETIME 0x0132
+#define TIFF_TAG_EXIF_IFD 0x8769
+#define TIFF_TAG_TZ_OFFSET 0x882A
+
+#define EXIF_TAG_EXIF_VERSION 0x9000
+#define EXIF_TAG_ORIGINAL_DATETIME 0x9003
+#define EXIF_TAG_SUBJECT_AREA 0x9214
+#define EXIF_TAG_TIFF_DATETIME_SS 0x9290
+#define EXIF_TAG_ORIGINAL_DATETIME_SS 0x9291
+
+#define TIFF_TYPE_ASCII 2 /* ASCII text */
+#define TIFF_TYPE_USHORT 3 /* Unsigned 16-bit int */
+#define TIFF_TYPE_LONG 4 /* Unsigned 32-bit int */
+#define TIFF_TYPE_UNDEF 7 /* Byte blob */
+#define TIFF_TYPE_SSHORT 8 /* Signed 16-bit int */
+
+static const char exif_marker_start[14] = {
+ 'E', 'x', 'i', 'f', 0, 0, /* EXIF marker signature */
+ 'M', 'M', 0, 42, /* TIFF file header (big-endian) */
+ 0, 0, 0, 8, /* Offset to first toplevel IFD */
+};
+
+static const char exif_version_tag[12] = {
+ 0x90, 0x00, /* EXIF version tag, 0x9000 */
+ 0x00, 0x07, /* Data type 7 = "unknown" (raw byte blob) */
+ 0x00, 0x00, 0x00, 0x04, /* Data length */
+ 0x30, 0x32, 0x32, 0x30 /* Inline data, EXIF version 2.2 */
+};
+
+static const char exif_subifd_tag[8] = {
+ 0x87, 0x69, /* EXIF Sub-IFD tag */
+ 0x00, 0x04, /* Data type 4 = uint32 */
+ 0x00, 0x00, 0x00, 0x01, /* Number of values */
+};
+
+static const char exif_tzoffset_tag[12] = {
+ 0x88, 0x2A, /* TIFF/EP time zone offset tag */
+ 0x00, 0x08, /* Data type 8 = sint16 */
+ 0x00, 0x00, 0x00, 0x01, /* Number of values */
+ 0, 0, 0, 0 /* Dummy data */
+};
+
+static void put_uint16(JOCTET *buf, unsigned value)
+{
+ buf[0] = ( value & 0xFF00 ) >> 8;
+ buf[1] = ( value & 0x00FF );
+}
+
+static void put_sint16(JOCTET *buf, int value)
+{
+ buf[0] = ( value & 0xFF00 ) >> 8;
+ buf[1] = ( value & 0x00FF );
+}
+
+static void put_uint32(JOCTET *buf, unsigned value)
+{
+ buf[0] = ( value & 0xFF000000 ) >> 24;
+ buf[1] = ( value & 0x00FF0000 ) >> 16;
+ buf[2] = ( value & 0x0000FF00 ) >> 8;
+ buf[3] = ( value & 0x000000FF );
+}
+
+struct tiff_writing {
+ JOCTET * const base;
+ JOCTET *buf;
+ unsigned data_offset;
+};
+
+static void put_direntry(struct tiff_writing *into, const char *data, unsigned length)
+{
+ if (length <= 4) {
+ /* Entries that fit in the directory entry are stored there */
+ memset(into->buf, 0, 4);
+ memcpy(into->buf, data, length);
+ } else {
+ /* Longer entries are stored out-of-line */
+ unsigned offset = into->data_offset;
+
+ while ((offset & 0x03) != 0) { /* Alignment */
+ into->base[offset] = 0;
+ offset ++;
+ }
+
+ put_uint32(into->buf, offset);
+ memcpy(into->base + offset, data, length);
+ into->data_offset = offset + length;
+ }
+}
+
+static void put_stringentry(struct tiff_writing *into, unsigned tag, const char *str, int with_nul)
+{
+ unsigned stringlength = strlen(str) + (with_nul?1:0);
+
+ put_uint16(into->buf, tag);
+ put_uint16(into->buf + 2, TIFF_TYPE_ASCII);
+ put_uint32(into->buf + 4, stringlength);
+ into->buf += 8;
+ put_direntry(into, str, stringlength);
+ into->buf += 4;
+}
+
+static void put_subjectarea(struct tiff_writing *into, const struct coord *box)
+{
+ put_uint16(into->buf , EXIF_TAG_SUBJECT_AREA);
+ put_uint16(into->buf + 2, TIFF_TYPE_USHORT);
+ put_uint32(into->buf + 4, 4 /* Four USHORTs */);
+ put_uint32(into->buf + 8, into->data_offset);
+ into->buf += 12;
+ JOCTET *ool = into->base + into->data_offset;
+ put_uint16(ool , box->x); /* Center.x */
+ put_uint16(ool+2, box->y); /* Center.y */
+ put_uint16(ool+4, box->width);
+ put_uint16(ool+6, box->height);
+ into->data_offset += 8;
+}
+
+/*
+ * put_jpeg_exif writes the EXIF APP1 chunk to the jpeg file.
+ * It must be called after jpeg_start_compress() but before
+ * any image data is written by jpeg_write_scanlines().
+ */
+static void put_jpeg_exif(j_compress_ptr cinfo,
+ const struct context *cnt,
+ const struct tm *timestamp,
+ const struct coord *box)
+{
+ /* description, datetime, and subtime are the values that are actually
+ * put into the EXIF data
+ */
+ char *description, *datetime, *subtime;
+ char datetime_buf[22];
+
+ if (timestamp) {
+ /* Exif requires this exact format */
+ snprintf(datetime_buf, 21, "%04d:%02d:%02d %02d:%02d:%02d",
+ timestamp->tm_year + 1900,
+ timestamp->tm_mon + 1,
+ timestamp->tm_mday,
+ timestamp->tm_hour,
+ timestamp->tm_min,
+ timestamp->tm_sec);
+ datetime = datetime_buf;
+ } else {
+ datetime = NULL;
+ }
+
+ // TODO: Extract subsecond timestamp from somewhere, but only
+ // use as much of it as is indicated by conf->frame_limit
+ subtime = NULL;
+
+ if (cnt->conf.exif_text) {
+ description = malloc(PATH_MAX);
+ mystrftime(cnt, description, PATH_MAX-1,
+ cnt->conf.exif_text,
+ timestamp, NULL, 0);
+ } else {
+ description = NULL;
+ }
+
+ /* Calculate an upper bound on the size of the APP1 marker so
+ * we can allocate a buffer for it.
+ */
+
+ /* Count up the number of tags and max amount of OOL data */
+ int ifd0_tagcount = 0;
+ int ifd1_tagcount = 0;
+ unsigned datasize = 0;
+
+ if (description) {
+ ifd0_tagcount ++;
+ datasize += 5 + strlen(description); /* Add 5 for NUL and alignment */
+ }
+
+ if (datetime) {
+ /* We write this to both the TIFF datetime tag (which most programs
+ * treat as "last-modified-date") and the EXIF "time of creation of
+ * original image" tag (which many programs ignore). This is
+ * redundant but seems to be the thing to do.
+ */
+ ifd0_tagcount++;
+ ifd1_tagcount++;
+ /* We also write the timezone-offset tag in IFD0 */
+ ifd0_tagcount++;
+ /* It would be nice to use the same offset for both tags' values,
+ * but I don't want to write the bookkeeping for that right now */
+ datasize += 2 * (5 + strlen(datetime));
+ }
+
+ if (subtime) {
+ ifd1_tagcount++;
+ datasize += 5 + strlen(subtime);
+ }
+
+ if (box) {
+ ifd1_tagcount++;
+ datasize += 2 * 4; /* Four 16-bit ints */
+ }
+
+ if (ifd1_tagcount > 0) {
+ /* If we're writing the Exif sub-IFD, account for the
+ * two tags that requires */
+ ifd0_tagcount ++; /* The tag in IFD0 that points to IFD1 */
+ ifd1_tagcount ++; /* The EXIF version tag */
+ }
+
+ /* Each IFD takes 12 bytes per tag, plus six more (the tag count and the
+ * pointer to the next IFD, always zero in our case)
+ */
+ unsigned int ifds_size =
+ ( ifd1_tagcount > 0 ? ( 12 * ifd1_tagcount + 6 ) : 0 ) +
+ ( ifd0_tagcount > 0 ? ( 12 * ifd0_tagcount + 6 ) : 0 );
+
+ if (ifds_size == 0) {
+ /* We're not actually going to write any information. */
+ return;
+ }
+
+ unsigned int buffer_size = 6 /* EXIF marker signature */ +
+ 8 /* TIFF file header */ +
+ ifds_size /* the tag directories */ +
+ datasize;
+
+ JOCTET *marker = malloc(buffer_size);
+ memcpy(marker, exif_marker_start, 14); /* EXIF and TIFF headers */
+
+ struct tiff_writing writing = (struct tiff_writing) {
+ .base = marker + 6, /* base address for intra-TIFF offsets */
+ .buf = marker + 14, /* current write position */
+ .data_offset = 8 + ifds_size, /* where to start storing data */
+ };
+
+ /* Write IFD 0 */
+ /* Note that tags are stored in numerical order */
+ put_uint16(writing.buf, ifd0_tagcount);
+ writing.buf += 2;
+
+ if (description)
+ put_stringentry(&writing, TIFF_TAG_IMAGE_DESCRIPTION, description, 0);
+
+ if (datetime)
+ put_stringentry(&writing, TIFF_TAG_DATETIME, datetime, 1);
+
+ if (ifd1_tagcount > 0) {
+ /* Offset of IFD1 - TIFF header + IFD0 size. */
+ unsigned ifd1_offset = 8 + 6 + ( 12 * ifd0_tagcount );
+ memcpy(writing.buf, exif_subifd_tag, 8);
+ put_uint32(writing.buf + 8, ifd1_offset);
+ writing.buf += 12;
+ }
+
+ if (datetime) {
+ memcpy(writing.buf, exif_tzoffset_tag, 12);
+ put_sint16(writing.buf+8, timestamp->tm_gmtoff / 3600);
+ writing.buf += 12;
+ }
+
+ put_uint32(writing.buf, 0); /* Next IFD offset = 0 (no next IFD) */
+ writing.buf += 4;
+
+ /* Write IFD 1 */
+ if (ifd1_tagcount > 0) {
+ /* (remember that the tags in any IFD must be in numerical order
+ * by tag) */
+ put_uint16(writing.buf, ifd1_tagcount);
+ memcpy(writing.buf + 2, exif_version_tag, 12); /* tag 0x9000 */
+ writing.buf += 14;
+
+ if (datetime)
+ put_stringentry(&writing, EXIF_TAG_ORIGINAL_DATETIME, datetime, 1);
+
+ if (box)
+ put_subjectarea(&writing, box);
+
+ if (subtime)
+ put_stringentry(&writing, EXIF_TAG_ORIGINAL_DATETIME_SS, subtime, 0);
+
+ put_uint32(writing.buf, 0); /* Next IFD = 0 (no next IFD) */
+ writing.buf += 4;
+ }
+
+ /* We should have met up with the OOL data */
+ assert( (writing.buf - writing.base) == 8 + ifds_size );
+
+ /* The buffer is complete; write it out */
+ unsigned marker_len = 6 + writing.data_offset;
+
+ /* assert we didn't underestimate the original buffer size */
+ assert(marker_len <= buffer_size);
+
+ /* EXIF data lives in a JPEG APP1 marker */
+ jpeg_write_marker(cinfo, JPEG_APP0 + 1, marker, marker_len);
+
+ if (description)
+ free(description);
+
+ free(marker);
+}
/**
* put_jpeg_yuv420p_memory
@@ -97,7 +414,9 @@ static GLOBAL(int) _jpeg_mem_size(j_compress_ptr cinfo)
* Returns buffer size of jpeg image
*/
static int put_jpeg_yuv420p_memory(unsigned char *dest_image, int image_size,
- unsigned char *input_image, int width, int height, int quality)
+ unsigned char *input_image, int width, int height, int quality,
+ struct context *cnt, struct tm *tm, struct coord *box)
+
{
int i, j, jpeg_image_size;
@@ -140,6 +459,8 @@ static int put_jpeg_yuv420p_memory(unsigned char *dest_image, int image_size,
jpeg_start_compress (&cinfo, TRUE);
+ put_jpeg_exif(&cinfo, cnt, tm, box);
+
for (j = 0; j < height; j += 16) {
for (i = 0; i < 16; i++) {
y[i] = input_image + width * (i + j);
@@ -196,6 +517,8 @@ static int put_jpeg_grey_memory(unsigned char *dest_image, int image_size, unsig
jpeg_start_compress (&cjpeg, TRUE);
+ put_jpeg_exif(&cjpeg, NULL, NULL, NULL);
+
row_ptr[0] = input_image;
for (y = 0; y < height; y++) {
@@ -225,7 +548,10 @@ static int put_jpeg_grey_memory(unsigned char *dest_image, int image_size, unsig
*
* Returns nothing
*/
-static void put_jpeg_yuv420p_file(FILE *fp, unsigned char *image, int width, int height, int quality)
+static void put_jpeg_yuv420p_file(FILE *fp,
+ unsigned char *image, int width, int height,
+ int quality,
+ struct context *cnt, struct tm *tm, struct coord *box)
{
int i, j;
@@ -267,6 +593,8 @@ static void put_jpeg_yuv420p_file(FILE *fp, unsigned char *image, int width, int
jpeg_stdio_dest(&cinfo, fp); // Data written to file
jpeg_start_compress(&cinfo, TRUE);
+ put_jpeg_exif(&cinfo, cnt, tm, box);
+
for (j = 0; j < height; j += 16) {
for (i = 0; i < 16; i++) {
y[i] = image + width * (i + j);
@@ -319,6 +647,8 @@ static void put_jpeg_grey_file(FILE *picture, unsigned char *image, int width, i
jpeg_start_compress(&cjpeg, TRUE);
+ put_jpeg_exif(&cjpeg, NULL, NULL, NULL);
+
row_ptr[0] = image;
for (y = 0; y < height; y++) {
@@ -557,7 +887,7 @@ int put_picture_memory(struct context *cnt, unsigned char* dest_image, int image
switch (cnt->imgs.type) {
case VIDEO_PALETTE_YUV420P:
return put_jpeg_yuv420p_memory(dest_image, image_size, image,
- cnt->imgs.width, cnt->imgs.height, quality);
+ cnt->imgs.width, cnt->imgs.height, quality, cnt, &(cnt->current_image->timestamp_tm), &(cnt->current_image->location));
case VIDEO_PALETTE_GREY:
return put_jpeg_grey_memory(dest_image, image_size, image,
cnt->imgs.width, cnt->imgs.height, quality);
@@ -576,7 +906,7 @@ void put_picture_fd(struct context *cnt, FILE *picture, unsigned char *image, in
} else {
switch (cnt->imgs.type) {
case VIDEO_PALETTE_YUV420P:
- put_jpeg_yuv420p_file(picture, image, cnt->imgs.width, cnt->imgs.height, quality);
+ put_jpeg_yuv420p_file(picture, image, cnt->imgs.width, cnt->imgs.height, quality, cnt, &(cnt->current_image->timestamp_tm), &(cnt->current_image->location));
break;
case VIDEO_PALETTE_GREY:
put_jpeg_grey_file(picture, image, cnt->imgs.width, cnt->imgs.height, quality);
3  video2.c
View
@@ -260,7 +260,8 @@ static int v4l2_select_input(struct config *conf, struct video_dev *viddev,
if (xioctl(vid_source->fd, VIDIOC_ENUMINPUT, &input) == -1) {
MOTION_LOG(ERR, TYPE_VIDEO, SHOW_ERRNO, "%s: Unable to query input %d."
- " VIDIOC_ENUMINPUT", input.index);
+ " VIDIOC_ENUMINPUT, if you use a WEBCAM change input value in conf by -1",
+ input.index);
return -1;
}
Please sign in to comment.
Something went wrong with that request. Please try again.