1 /*
2  * Copyright (c) 2004-2007, Eric M. Johnston <emj@postal.net>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by Eric M. Johnston.
16  * 4. Neither the name of the author nor the names of any co-contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: exiftime.c,v 1.13 2007/12/16 02:18:51 ejohnst Exp $
33  */
34 
35 /*
36  * exiftime: display or adjust date & time Exif tags; list files
37  * ordered by time.
38  *
39  */
40 
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <errno.h>
45 
46 /* For getopt(). */
47 
48 #ifndef WIN32
49 #include <unistd.h>
50 #else
51 extern char *optarg;
52 extern int optind, opterr, optopt;
53 int getopt(int, char * const [], const char *);
54 #endif
55 
56 #include "jpeg.h"
57 #include "exif.h"
58 #include "timevary.h"
59 
60 
61 struct linfo {
62 	const char *fn;
63 	time_t ts;
64 };
65 
66 static const char *version = "1.01";
67 static int iflag, lflag, qflag, wflag, ttags;
68 static const char *delim = ": ";
69 static const char *fname;
70 static struct vary *v;
71 static struct linfo *lorder;
72 
73 #define EXIFTIMEFMT	"%Y:%m:%d %H:%M:%S"
74 #define EXIFTIMELEN	20
75 
76 #define ET_CREATE	0x01
77 #define ET_GEN		0x02
78 #define ET_DIGI		0x04
79 
80 
81 /*
82  * Some helpful info...
83  */
84 static
usage()85 void usage()
86 {
87 	fprintf(stderr, "Usage: %s [options] file ...\n", progname);
88 	fprintf(stderr, "Displays or adjusts Exif timestamps in the specified "
89 	    "files.\n");
90 	fprintf(stderr, "Version: %s\n\n", version);
91 	fprintf(stderr, "Available options:\n");
92 	fprintf(stderr, "  -f\tAnswer 'yes' to any confirmation prompts.\n");
93 	fprintf(stderr, "  -i\tConfirm overwrites (default).\n");
94 	fprintf(stderr, "  -l\tList files ordered by timestamp, using image "
95 	    "created by default;\n\toverrides -v, -w.  Use -t to change "
96 	    "timestamp preference.\n");
97 	fprintf(stderr, "  -q\tBe quiet when writing timestamps.\n");
98 	fprintf(stderr, "  -s\tSet delimiter to provided string "
99 	    "(default: \": \").\n");
100 	fprintf(stderr, "  -t[acdg]\n\tDisplay/adjust specified timestamp(s), "
101 	    "if they exist:\n");
102 	fprintf(stderr, "\t  a: All available timestamps (default);\n\t  "
103 	    "c: Image created;\n\t  g: Image generated; or\n\t  d: Image "
104 	    "digitized.\n");
105 	fprintf(stderr, "  -v[+|-]val[ymwdHMS]\n\tAdjust the timestamp(s) by "
106 	    "the given amount.\n");
107 	fprintf(stderr, "  -w\tWrite adjusted timestamp(s).\n");
108 
109 	vary_destroy(v);
110 	exit(1);
111 }
112 
113 
114 /*
115  * Compare two linfo members for our sort.
116  */
117 int
lcomp(const void * a,const void * b)118 lcomp(const void *a, const void *b)
119 {
120 
121 	if (((struct linfo *)a)->ts < ((struct linfo *)b)->ts)
122 		return (-1);
123 	if (((struct linfo *)a)->ts == ((struct linfo *)b)->ts)
124 		return (0);
125 	return (1);
126 }
127 
128 
129 /*
130  * Stuff a standard Exif timestamp into a struct *tm.
131  * I've got to say that it's pretty annoying that Win32 doesn't have
132  * strptime()...
133  */
134 static int
etstrptm(const char * buf,struct tm * tp)135 etstrptm(const char *buf, struct tm *tp)
136 {
137 	int n;
138 
139 	memset(tp, 0, sizeof(struct tm));
140 	n = sscanf(buf, "%d:%d:%d %d:%d:%d", &tp->tm_year, &tp->tm_mon,
141 	    &tp->tm_mday, &tp->tm_hour, &tp->tm_min, &tp->tm_sec);
142 	tp->tm_year -= 1900;
143 	tp->tm_mon -= 1;
144 
145 	return (n != 6);
146 }
147 
148 
149 /*
150  * Grab the timestamps for listing in the specified order of preference.
151  * Doesn't modify the file.
152  */
153 static int
listts(struct exiftags * t,struct linfo * li,u_int16_t * tpref)154 listts(struct exiftags *t, struct linfo *li, u_int16_t *tpref)
155 {
156 	struct exifprop *p;
157 	struct tm tv;
158 
159 	/* If no timestamp is found, print error and list first. */
160 
161 	p = findprop(t->props, tags, tpref[0]);
162 
163 	if (!p || !p->str || etstrptm(p->str, &tv)) {
164 		p = findprop(t->props, tags, tpref[1]);
165 
166 		if (!p || !p->str || etstrptm(p->str, &tv)) {
167 			p = findprop(t->props, tags, tpref[2]);
168 
169 			if (!p || !p->str || etstrptm(p->str, &tv)) {
170 				exifwarn("no timestamp available");
171 				li->ts = 0;
172 				return (1);
173 			}
174 		}
175 	}
176 
177 	li->ts = mktime(&tv);
178 	return (0);
179 }
180 
181 
182 /*
183  * Grab the specified timestamp and vary it, if necessary.
184  * The provided buffer must be at least EXIFTIMELEN bytes.
185  */
186 static int
ettime(char * b,struct exifprop * p)187 ettime(char *b, struct exifprop *p)
188 {
189 	struct tm tv;
190 	const struct vary *badv;
191 
192 	/* Slurp the timestamp into tv. */
193 
194 	if (!p || !p->str || etstrptm(p->str, &tv))
195 		return (1);
196 
197 	/* Apply any adjustments.  (Bad adjustment = fatal.) */
198 
199 	badv = vary_apply(v, &tv);
200 	if (badv) {
201 		exifwarn2("cannot apply adjustment", badv->arg);
202 		usage();
203 	}
204 
205 	if (strftime(b, EXIFTIMELEN, EXIFTIMEFMT, &tv) != EXIFTIMELEN - 1)
206 		return (1);
207 
208 	return (0);
209 }
210 
211 
212 /*
213  * Overwrite a timestamp with the adjusted value.
214  */
215 static int
writets(FILE * fp,long pos,struct exiftags * t,struct exifprop * p,const unsigned char * buf,const char * ttype,const char * nts)216 writets(FILE *fp, long pos, struct exiftags *t, struct exifprop *p,
217     const unsigned char *buf, const char *ttype, const char *nts)
218 {
219 	int ch, checkch;
220 	long psave;
221 
222 	/* Some sanity checking. */
223 
224 	if (strlen(nts) != EXIFTIMELEN - 1) {
225 		fprintf(stderr, "%s: invalid timestamp -- %s\n", fname, nts);
226 		return (1);
227 	}
228 
229 	if (!strcmp(nts, p->str)) {
230 		fprintf(stderr, "%s: new %s timestamp identical to old\n",
231 		    fname, ttype);
232 		return (1);
233 	}
234 
235 	/* Prompt user, if desired. */
236 
237 	if (iflag) {
238 		fprintf(stderr, "adjust time %s in %s from\n  %s to %s? "
239 		    "(y/n [n]) ", ttype, fname, p->str, nts);
240 		checkch = ch = getchar();
241 		while (ch != '\n' && ch != EOF)
242 			ch = getchar();
243 		if (checkch != 'y' && checkch != 'Y') {
244 			fprintf(stderr, "not adjusted\n");
245 			return (1);
246 		}
247 	}
248 
249 	/* Remember where we are and move to the comment in our file. */
250 
251 	psave = ftell(fp);
252 	if (fseek(fp, pos + (t->md.btiff - buf) + p->value, SEEK_SET))
253 		exifdie((const char *)strerror(errno));
254 
255 	/* Write the new timestamp. */
256 
257 	if (fwrite(nts, EXIFTIMELEN, 1, fp) != 1)
258 		exifdie((const char *)strerror(errno));
259 
260 	/* Restore the file pointer. */
261 
262 	if (fseek(fp, psave, SEEK_SET))
263 		exifdie((const char *)strerror(errno));
264 
265 	if (!qflag)
266 		printf("%s%s%s -> %s\n", p->descr, delim, p->str, nts);
267 	return (0);
268 }
269 
270 
271 /*
272  * Process a timestamp.
273  * Returns 0 on success, 1 if it had trouble writing, 2 if the tag wasn't
274  * found (and it was explictly requested), or -1 if the tag wasn't found.
275  */
276 static int
procts(FILE * fp,long pos,struct exiftags * t,const unsigned char * buf,u_int16_t tag,const char * ttype)277 procts(FILE *fp, long pos, struct exiftags *t, const unsigned char *buf,
278     u_int16_t tag, const char *ttype)
279 {
280 	int rc;
281 	char nts[EXIFTIMELEN];
282 	struct exifprop *p;
283 
284 	p = findprop(t->props, tags, tag);
285 	if (ettime(nts, p)) {
286 
287 		/*
288 		 * If ttags != 0, then the user explicitly requested the
289 		 * timestamp, so print an error if it doesn't exist.
290 		 */
291 		if (ttags) {
292 			fprintf(stderr, "%s: image %s time not available\n",
293 			    fname, ttype);
294 			return (2);
295 		}
296 
297 		return (-1);
298 	}
299 
300 	if (wflag)
301 		rc = writets(fp, pos, t, p, buf, ttype, nts);
302 	else {
303 		printf("%s%s%s\n", p->descr, delim, nts);
304 		rc = 0;
305 	}
306 
307 	return (rc);
308 }
309 
310 
311 /*
312  * Process the file's timestamps according to the options chosen.
313  */
314 static int
procall(FILE * fp,long pos,struct exiftags * t,const unsigned char * buf)315 procall(FILE *fp, long pos, struct exiftags *t, const unsigned char *buf)
316 {
317 	int r, rc, found;
318 
319 	found = rc = 0;
320 
321 	/*
322 	 * If ttags = 0, process them all or an error if there are none.
323 	 */
324 
325 	if (ttags & ET_CREATE || !ttags) {
326 		r = procts(fp, pos, t, buf, EXIF_T_DATETIME, "created");
327 		if (r == 0 || r == 1) found++;
328 		if (r > 0) rc = 1;
329 	}
330 
331 	if (ttags & ET_GEN || !ttags) {
332 		r = procts(fp, pos, t, buf, EXIF_T_DATETIMEORIG, "generated");
333 		if (r == 0 || r == 1) found++;
334 		if (r > 0) rc = 1;
335 	}
336 
337 	if (ttags & ET_DIGI || !ttags) {
338 		r = procts(fp, pos, t, buf, EXIF_T_DATETIMEDIGI, "digitized");
339 		if (r == 0 || r == 1) found++;
340 		if (r > 0) rc = 1;
341 	}
342 
343 	/* No timestamp tags. */
344 
345 	if (!ttags && !found) {
346 		fprintf(stderr, "%s: no timestamps available\n", fname);
347 		return (1);
348 	}
349 
350 	return (rc);
351 }
352 
353 
354 /*
355  * Scan the JPEG file for Exif data and parse it.
356  */
357 static int
doit(FILE * fp,int n,u_int16_t * tpref)358 doit(FILE *fp, int n, u_int16_t *tpref)
359 {
360 	int mark, gotapp1, first, rc;
361 	unsigned int len, rlen;
362 	unsigned char *exifbuf;
363 	struct exiftags *t;
364 	long app1;
365 
366 	gotapp1 = FALSE;
367 	first = 0;
368 	exifbuf = NULL;
369 	rc = 0;
370 
371 	while (jpegscan(fp, &mark, &len, !(first++))) {
372 
373 		if (mark != JPEG_M_APP1) {
374 			if (fseek(fp, len, SEEK_CUR))
375 				exifdie((const char *)strerror(errno));
376 			continue;
377 		}
378 
379 		exifbuf = (unsigned char *)malloc(len);
380 		if (!exifbuf)
381 			exifdie((const char *)strerror(errno));
382 
383 		app1 = ftell(fp);
384 		rlen = fread(exifbuf, 1, len, fp);
385 		if (rlen != len) {
386 			fprintf(stderr, "%s: error reading JPEG (length "
387 			    "mismatch)\n", fname);
388 			free(exifbuf);
389 			return (1);
390 		}
391 
392 		t = exifscan(exifbuf, len, FALSE);
393 
394 		if (t && t->props) {
395 			gotapp1 = TRUE;
396 			if (lflag)
397 				rc = listts(t, &lorder[n], tpref);
398 			else
399 				rc = procall(fp, app1, t, exifbuf);
400 		}
401 		exiffree(t);
402 		free(exifbuf);
403 	}
404 
405 	if (!gotapp1) {
406 		fprintf(stderr, "%s: couldn't find Exif data\n", fname);
407 		return (1);
408 	}
409 
410 	return (rc);
411 }
412 
413 
414 /*
415  * Fill in array of timestamps in order of preference.
416  */
417 void
settpref(u_int16_t * tpref,u_int16_t tag)418 settpref(u_int16_t *tpref, u_int16_t tag)
419 {
420 	int i;
421 
422 	/* Recursively fill in the remaining timestamps. */
423 
424 	if (tag == EXIF_T_UNKNOWN) {
425 		settpref(tpref, EXIF_T_DATETIME);
426 		settpref(tpref, EXIF_T_DATETIMEORIG);
427 		settpref(tpref, EXIF_T_DATETIMEDIGI);
428 		return;
429 	}
430 
431 	for (i = 0; i < 3; i++) {
432 		if (tpref[i] == tag)
433 			break;
434 		if (tpref[i] == EXIF_T_UNKNOWN) {
435 			tpref[i] = tag;
436 			break;
437 		}
438 	}
439 }
440 
441 
442 int
main(int argc,char ** argv)443 main(int argc, char **argv)
444 {
445 	register int ch;
446 	int eval, fnum, wantall;
447 	char *rmode, *wmode;
448 	FILE *fp;
449 	u_int16_t tpref[3];
450 
451 	progname = argv[0];
452 	debug = FALSE;
453 	ttags = wantall = eval = 0;
454 	lflag = qflag = wflag = FALSE;
455 	iflag = TRUE;
456 	v = NULL;
457 	tpref[0] = tpref[1] = tpref[2] = EXIF_T_UNKNOWN;
458 #ifdef WIN32
459 	rmode = "rb";
460 	wmode = "r+b";
461 #else
462 	rmode = "r";
463 	wmode = "r+";
464 #endif
465 
466 	while ((ch = getopt(argc, argv, "filqs:t:v:w")) != -1)
467 		switch (ch) {
468 		case 'f':
469 			iflag = FALSE;
470 			break;
471 		case 'i':
472 			iflag = TRUE;
473 			break;
474 		case 'l':
475 			lflag = TRUE;
476 			break;
477 		case 'q':
478 			qflag = TRUE;
479 			break;
480 		case 's':
481 			delim = optarg;
482 			break;
483 		case 't':
484 			while (*optarg) {
485 				switch (*optarg) {
486 				case 'c':
487 					settpref(tpref, EXIF_T_DATETIME);
488 					ttags |= ET_CREATE;
489 					break;
490 				case 'd':
491 					settpref(tpref, EXIF_T_DATETIMEDIGI);
492 					ttags |= ET_DIGI;
493 					break;
494 				case 'g':
495 					settpref(tpref, EXIF_T_DATETIMEORIG);
496 					ttags |= ET_GEN;
497 					break;
498 				case 'a':
499 					wantall = 1;
500 					break;
501 				default:
502 					usage();
503 				}
504 				optarg++;
505 			}
506 			if (wantall) ttags = 0;
507 			break;
508 		case 'v':
509 			v = vary_append(v, optarg);
510 			break;
511 		case 'w':
512 			wflag = TRUE;
513 			break;
514 		case '?':
515 		default:
516 			usage();
517 		}
518 	argc -= optind;
519 	argv += optind;
520 
521 	if (!*argv)
522 		usage();
523 
524 	/* Finish up timestamp preferences. */
525 
526 	settpref(tpref, EXIF_T_UNKNOWN);
527 
528 	/* Don't be quiet if we're not writing. */
529 
530 	if (qflag && !wflag)
531 		qflag = FALSE;
532 
533 	/* Prepare an array for sorting. */
534 
535 	if (lflag) {
536 		wflag = 0;
537 		lorder = (struct linfo *)calloc(argc, sizeof(struct linfo));
538 		if (!lorder)
539 			exifdie((const char *)strerror(errno));
540 	}
541 
542 	/* Run through the files... */
543 
544 	for (fnum = 0; *argv; ++argv, fnum++) {
545 
546 		fname = *argv;
547 
548 		/* Only open for read+write if we need to. */
549 
550 		if ((fp = fopen(*argv, wflag ? wmode : rmode)) == NULL) {
551 			exifwarn2(strerror(errno), *argv);
552 			eval = 1;
553 			continue;
554 		}
555 
556 		/* Print filenames if more than one. */
557 
558 		if (argc > 1 && !lflag && !qflag)
559 			printf("%s%s:\n", fnum == 0 ? "" : "\n", *argv);
560 
561 		if (lflag)
562 			lorder[fnum].fn = fname;
563 
564 		if (doit(fp, fnum, tpref))
565 			eval = 1;
566 		fclose(fp);
567 	}
568 
569 	/*
570 	 * We'd like to use mergesort() here (instead of qsort()) because
571 	 * qsort() isn't stable w/members that compare equal and we exepect
572 	 * the list of files to frequently already be in order.  However,
573 	 * FreeBSD seems to be the only platform with mergesort(), and I'm
574 	 * not inclined to include the function...
575 	 */
576 	if (lflag) {
577 		qsort(lorder, argc, sizeof(struct linfo), lcomp);
578 		for (fnum = 0; fnum < argc; fnum++)
579 			printf("%s\n", lorder[fnum].fn);
580 		free(lorder);	/* XXX Over in usage()? */
581 	}
582 
583 	vary_destroy(v);
584 	return (eval);
585 }
586