1 /*
2 * Copyright (c) 2002-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: exifcom.c,v 1.16 2007/12/16 02:11:44 ejohnst Exp $
33 */
34
35 /*
36 * exifcom: display or set Exif user comment.
37 *
38 */
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <errno.h>
44
45 /* For getopt(). */
46
47 #ifndef WIN32
48 #include <unistd.h>
49 #else
50 extern char *optarg;
51 extern int optind, opterr, optopt;
52 int getopt(int, char * const [], const char *);
53 #endif
54
55 #include "jpeg.h"
56 #include "exif.h"
57
58
59 static const char *version = "1.01";
60 static int fnum, bflag, iflag, nflag, vflag;
61 static const char *com;
62 static const char *delim = ": ";
63
64 #define ASCCOM "ASCII\0\0\0"
65
66
67 /*
68 * Display the comment. This function just uses what's returned by
69 * exifscan() -- it doesn't touch the file.
70 */
71 static int
printcom(const char * fname,struct exifprop * p,const unsigned char * btiff)72 printcom(const char *fname, struct exifprop *p, const unsigned char *btiff)
73 {
74 int rc = 0;
75
76 /* No comment tag. */
77
78 if (!p) {
79 fprintf(stderr, "%s: comment not available\n", fname);
80 return (1);
81 }
82
83 if (vflag)
84 printf("Supported Length%s%d\n", delim, p->count - 8);
85
86 /* Comment tag unset. */
87
88 if (!*(btiff + p->value))
89 rc = 2;
90
91 else {
92 /* Comment tag not ASCII. */
93
94 if (memcmp(ASCCOM, btiff + p->value, 8)) {
95 if (!vflag)
96 exifwarn2("comment type not supported",
97 (const char *)(btiff + p->value));
98 rc = 3;
99 }
100
101 if (vflag)
102 printf("Type%s%s\n", delim, btiff + p->value);
103 }
104
105 /* Comment tag OK, but blank. */
106
107 if (!rc && (!p->str || !*(p->str)))
108 rc = 2;
109
110 /* Print comment in the normal, non-verbose case. */
111
112 if (!vflag) {
113 printf("%s", rc ? "" : p->str);
114 return (rc);
115 }
116
117 /* Print length and comment if it's supported. */
118
119 if (rc != 1 && rc != 3) {
120 printf("Length%s%d\n", delim, rc ? 0 : (int)strlen(p->str));
121 printf("Comment%s%s\n", delim, rc ? "" : p->str);
122 }
123
124 return (rc);
125 }
126
127
128 /*
129 * Blank or write a comment.
130 */
131 static int
writecom(FILE * fp,const char * fname,long pos,struct exifprop * p,const unsigned char * buf,const unsigned char * btiff)132 writecom(FILE *fp, const char *fname, long pos, struct exifprop *p,
133 const unsigned char *buf, const unsigned char *btiff)
134 {
135 u_int32_t l;
136 int ch, checkch;
137 long psave;
138
139 /* No comment tag or it's zero length. */
140
141 if (!p) {
142 fprintf(stderr, "%s: comment not available\n", fname);
143 return (1);
144 }
145
146 if (p->count < 9) {
147 fprintf(stderr, "%s: comment size zero\n", fname);
148 return (1);
149 }
150
151 /* Be careful with existing or unsupported comments. */
152
153 if (iflag && *(btiff + p->value)) {
154
155 if (memcmp(ASCCOM, btiff + p->value, 8)) {
156 if (nflag)
157 return (1);
158 fprintf(stderr, "overwrite %.8s comment in %s? "
159 "(y/n [n]) ", btiff + p->value, fname);
160
161 checkch = ch = getchar();
162 while (ch != '\n' && ch != EOF)
163 ch = getchar();
164 if (checkch != 'y' && checkch != 'Y') {
165 fprintf(stderr, "not overwritten\n");
166 return (1);
167 }
168
169 } else if (p->str && *(p->str)) {
170 if (nflag)
171 return (1);
172 fprintf(stderr, "overwrite comment in %s? (y/n [n]) ",
173 fname);
174
175 checkch = ch = getchar();
176 while (ch != '\n' && ch != EOF)
177 ch = getchar();
178 if (checkch != 'y' && checkch != 'Y') {
179 fprintf(stderr, "not overwritten\n");
180 return (1);
181 }
182 }
183 }
184
185 /* Remember where we are and move to the comment in our file. */
186
187 psave = ftell(fp);
188 if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET))
189 exifdie((const char *)strerror(errno));
190
191 /*
192 * Blank it with zeros and then reset the position.
193 * XXX Note that this is counter to the spec's recommendation:
194 * "When a UserComment area is set aside, it is recommended that
195 * the ID code be ASCII and that the following user comment part
196 * be filled with blank characters [20.H]." However, cameras (Canon)
197 * seem to make it all zero; the rationale is that one can restore
198 * the image file to its original state.
199 */
200
201 if (bflag) {
202 for (l = 0; l < p->count; l++)
203 if (fwrite("\0", 1, 1, fp) != 1)
204 exifdie((const char *)strerror(errno));
205 if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET))
206 exifdie((const char *)strerror(errno));
207 }
208
209 /* Write the character code and comment. */
210
211 if (com) {
212 l = strlen(com);
213 if (l > p->count - 8) {
214 fprintf(stderr, "%s: truncating comment to fit\n",
215 fname);
216 l = p->count - 8;
217 }
218
219 /* Character code. */
220
221 if (fwrite(ASCCOM, 8, 1, fp) != 1)
222 exifdie((const char *)strerror(errno));
223
224 /* Comment. */
225
226 if (fwrite(com, l, 1, fp) != 1)
227 exifdie((const char *)strerror(errno));
228
229 /*
230 * Pad with spaces (this seems to be standard practice).
231 * XXX For now we're not NUL terminating the string.
232 * This doesn't appear to be required by the spec, but it's
233 * always possible that something out there will break.
234 * I've seen some utilities pad with spaces and set the
235 * last byte to NUL.
236 */
237
238 for (l = p->count - 8 - l; l; l--)
239 if (fwrite(" ", 1, 1, fp) != 1)
240 exifdie((const char *)strerror(errno));
241 }
242
243 /* Restore the file pointer. */
244
245 if (fseek(fp, psave, SEEK_SET))
246 exifdie((const char *)strerror(errno));
247
248 return (0);
249 }
250
251
252 /*
253 * Scan the JPEG file for Exif data and parse it.
254 */
255 static int
doit(FILE * fp,const char * fname)256 doit(FILE *fp, const char *fname)
257 {
258 int mark, gotapp1, first, rc;
259 unsigned int len, rlen;
260 unsigned char *exifbuf;
261 struct exiftags *t;
262 long app1;
263
264 gotapp1 = FALSE;
265 first = 0;
266 exifbuf = NULL;
267 rc = 0;
268
269 while (jpegscan(fp, &mark, &len, !(first++))) {
270
271 if (mark != JPEG_M_APP1) {
272 if (fseek(fp, len, SEEK_CUR))
273 exifdie((const char *)strerror(errno));
274 continue;
275 }
276
277 exifbuf = (unsigned char *)malloc(len);
278 if (!exifbuf)
279 exifdie((const char *)strerror(errno));
280
281 app1 = ftell(fp);
282 rlen = fread(exifbuf, 1, len, fp);
283 if (rlen != len) {
284 fprintf(stderr, "%s: error reading JPEG (length "
285 "mismatch)\n", fname);
286 free(exifbuf);
287 return (1);
288 }
289
290 gotapp1 = TRUE;
291 t = exifscan(exifbuf, len, FALSE);
292
293 if (t && t->props) {
294 if (bflag || com)
295 rc = writecom(fp, fname, app1,
296 findprop(t->props, tags,
297 EXIF_T_USERCOMMENT), exifbuf, t->md.btiff);
298 else
299 rc = printcom(fname, findprop(t->props, tags,
300 EXIF_T_USERCOMMENT), t->md.btiff);
301 } else {
302 fprintf(stderr, "%s: couldn't find Exif properties\n",
303 fname);
304 rc = 1;
305 }
306 exiffree(t);
307 free(exifbuf);
308 }
309
310 if (!gotapp1) {
311 fprintf(stderr, "%s: couldn't find Exif data\n", fname);
312 return (1);
313 }
314
315 return (rc);
316 }
317
318
319 static
usage()320 void usage()
321 {
322 fprintf(stderr, "Usage: %s [options] [-w string] file ...\n", progname);
323 fprintf(stderr, "Displays or sets Exif comment in the specified "
324 "files.\n");
325 fprintf(stderr, "Version: %s\n\n", version);
326 fprintf(stderr, "Available options:\n");
327 fprintf(stderr, " -b\tOverwrite the comment tag with zeros.\n");
328 fprintf(stderr, " -f\tAnswer 'yes' to any confirmation prompts.\n");
329 fprintf(stderr, " -i\tConfirm overwrites (default).\n");
330 fprintf(stderr, " -n\tAnswer 'no' to any confirmation prompts.\n");
331 fprintf(stderr, " -v\tBe verbose about comment information.\n");
332 fprintf(stderr, " -w\tSet comment to provided string.\n");
333 fprintf(stderr, " -s\tSet delimiter to provided string "
334 "(default: \": \").\n");
335
336 exit(1);
337 }
338
339
340 int
main(int argc,char ** argv)341 main(int argc, char **argv)
342 {
343 register int ch;
344 int eval;
345 char *rmode, *wmode;
346 FILE *fp;
347
348 progname = argv[0];
349 eval = 0;
350 debug = FALSE;
351 bflag = nflag = vflag = FALSE;
352 iflag = TRUE;
353 com = NULL;
354 #ifdef WIN32
355 rmode = "rb";
356 wmode = "r+b";
357 #else
358 rmode = "r";
359 wmode = "r+";
360 #endif
361
362 while ((ch = getopt(argc, argv, "bfinvw:s:")) != -1)
363 switch (ch) {
364 case 'b':
365 bflag = TRUE;
366 break;
367 case 'f':
368 iflag = FALSE;
369 break;
370 case 'i':
371 iflag = TRUE;
372 break;
373 case 'n':
374 iflag = nflag = TRUE;
375 break;
376 case 'v':
377 vflag = TRUE;
378 break;
379 case 'w':
380 com = optarg;
381 break;
382 case 's':
383 delim = optarg;
384 break;
385 case '?':
386 default:
387 usage();
388 }
389 argc -= optind;
390 argv += optind;
391
392 if (!*argv)
393 usage();
394
395 for (fnum = 0; *argv; ++argv) {
396
397 /* Only open for read/write if we need to. */
398
399 if ((fp = fopen(*argv,
400 bflag || com ? wmode : rmode)) == NULL) {
401 exifwarn2(strerror(errno), *argv);
402 eval = 1;
403 continue;
404 }
405
406 fnum++;
407
408 if (argc > 1) {
409
410 /* Print filenames if more than one. */
411
412 if (vflag && !(bflag || com))
413 printf("%s%s:\n",
414 fnum == 1 ? "" : "\n", *argv);
415 else if (!(bflag || com))
416 printf("%s%s", *argv, delim);
417
418 /* Don't error >1 with multiple files. */
419
420 eval = (doit(fp, *argv) == 1 || eval);
421
422 } else
423 eval = doit(fp, *argv);
424
425 if (!vflag && !(bflag || com))
426 printf("\n");
427
428 fclose(fp);
429 }
430
431 return (eval);
432 }
433