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