/* * Scan the JPEG file for Exif data and parse it. */ static int doit(FILE *fp, int n) { int mark, gotapp1, first, rc; unsigned int len, rlen; unsigned char *exifbuf; struct exiftags *t; long app1; gotapp1 = FALSE; first = 0; exifbuf = NULL; rc = 0; while (jpegscan(fp, &mark, &len, !(first++))) { if (mark != JPEG_M_APP1) { if (fseek(fp, len, SEEK_CUR)) exifdie((const char *)strerror(errno)); continue; } exifbuf = (unsigned char *)malloc(len); if (!exifbuf) exifdie((const char *)strerror(errno)); app1 = ftell(fp); rlen = fread(exifbuf, 1, len, fp); if (rlen != len) { fprintf(stderr, "%s: error reading JPEG (length " "mismatch)\n", fname); free(exifbuf); return (1); } t = exifscan(exifbuf, len, FALSE); if (t && t->props) { gotapp1 = TRUE; if (lflag) rc = listts(t, &lorder[n]); else rc = procall(fp, app1, t, exifbuf); } exiffree(t); free(exifbuf); } if (!gotapp1) { fprintf(stderr, "%s: couldn't find Exif data\n", fname); return (1); } return (rc); }
/* * Overwrite a timestamp with the adjusted value. */ static int writets(FILE *fp, long pos, struct exiftags *t, struct exifprop *p, const unsigned char *buf, const char *ttype, const char *nts) { int ch, checkch; long psave; /* Some sanity checking. */ if (strlen(nts) != EXIFTIMELEN - 1) { fprintf(stderr, "%s: invalid timestamp -- %s\n", fname, nts); return (1); } if (!strcmp(nts, p->str)) { fprintf(stderr, "%s: new %s timestamp identical to old\n", fname, ttype); return (1); } /* Prompt user, if desired. */ if (iflag) { fprintf(stderr, "adjust time %s in %s from\n %s to %s? " "(y/n [n]) ", ttype, fname, p->str, nts); checkch = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (checkch != 'y' && checkch != 'Y') { fprintf(stderr, "not adjusted\n"); return (1); } } /* Remember where we are and move to the comment in our file. */ psave = ftell(fp); if (fseek(fp, pos + (t->md.btiff - buf) + p->value, SEEK_SET)) exifdie((const char *)strerror(errno)); /* Write the new timestamp. */ if (fwrite(nts, EXIFTIMELEN, 1, fp) != 1) exifdie((const char *)strerror(errno)); /* Restore the file pointer. */ if (fseek(fp, psave, SEEK_SET)) exifdie((const char *)strerror(errno)); if (!qflag) printf("%s%s%s -> %s\n", p->descr, delim, p->str, nts); return (0); }
/* * Collect image data from the start of frame. * XXX Note that we clobber any previously collected info... */ static void sofmrk(int mark) { int i; unsigned int l; l = mkrlen(); jpg_prcsn = jpg1byte(); jpg_height = jpg2byte(); jpg_width = jpg2byte(); jpg_cmpnts = jpg1byte(); for (i = 0; process[i].sof < JPEG_M_ERR; i++) if (process[i].sof == mark) break; jpg_prcss = process[i].str; /* Verify length. */ if (l != (unsigned int)(6 + jpg_cmpnts * 3)) { exifdie("invalid JPEG SOF marker (length mismatch)"); return; } /* Skip over component info we don't care about. */ for (i = 0; i < jpg_cmpnts; i++) { jpg1byte(); jpg1byte(); jpg1byte(); } seensof = TRUE; }
/* * Fetch one byte of the JPEG file. */ static int jpg1byte(void) { int b; b = fgetc(infile); if (b == EOF) return(-1); #if 0 exifdie("invalid JPEG format"); #endif return (b); }
/* * Fetch the length of a marker. */ static unsigned int mkrlen(void) { unsigned int l; /* Length includes itself. */ if ((l = jpg2byte()) < 2) { exifdie("invalid JPEG marker (length mismatch)"); return(-1); } return (l - 2); }
/* * Fetch two bytes of the JPEG file. */ static unsigned int jpg2byte(void) { unsigned int b1, b2; b1 = fgetc(infile); b2 = fgetc(infile); if (b1 == EOF || b2 == EOF) return(-1); #if 0 exifdie("invalid JPEG format"); #endif return ((b1 << 8) | b2); }
static Epsilon_Exif_Info * epsilon_read_exif_data (FILE * fp, int dumplvl, int pas) { int mark, gotapp1, first; unsigned int len, rlen; unsigned char *exifbuf; struct exiftags *t = NULL; gotapp1 = FALSE; first = 0; exifbuf = NULL; while (jpegscan (fp, &mark, &len, !(first++))) { if (mark != JPEG_M_APP1) { if (fseek (fp, len, SEEK_CUR)) exifdie ((const char *) strerror (errno)); continue; } exifbuf = (unsigned char *) malloc (len); if (!exifbuf) exifdie ((const char *) strerror (errno)); rlen = fread (exifbuf, 1, len, fp); if (rlen != len) { exifwarn ("error reading JPEG (length mismatch)"); free (exifbuf); return (NULL); } t = exifparse (exifbuf, len); if (t && t->props) { gotapp1 = TRUE; #if 0 if (dumplvl & ED_CAM) _epsilon_exif_info_props_get (t->props, ED_CAM, pas); if (dumplvl & ED_IMG) _epsilon_exif_info_props_get (t->props, ED_IMG, pas); if (dumplvl & ED_VRB) _epsilon_exif_info_props_get (t->props, ED_VRB, pas); if (dumplvl & ED_UNK) _epsilon_exif_info_props_get (t->props, ED_UNK, pas); #endif } free (exifbuf); } if (!gotapp1) { exifwarn ("couldn't find Exif data"); if (t) exiffree (t); return (NULL); } return ((Epsilon_Exif_Info *) t); }
/* * Scan the JPEG file for Exif data and parse it. */ static int doit(FILE *fp, const char *fname) { int mark, gotapp1, first, rc; unsigned int len, rlen; unsigned char *exifbuf; struct exiftags *t; long app1; gotapp1 = FALSE; first = 0; exifbuf = NULL; rc = 0; while (jpegscan(fp, &mark, &len, !(first++))) { if (mark != JPEG_M_APP1) { if (fseek(fp, len, SEEK_CUR)) exifdie((const char *)strerror(errno)); continue; } exifbuf = (unsigned char *)malloc(len); if (!exifbuf) exifdie((const char *)strerror(errno)); app1 = ftell(fp); rlen = fread(exifbuf, 1, len, fp); if (rlen != len) { fprintf(stderr, "%s: error reading JPEG (length " "mismatch)\n", fname); free(exifbuf); return (1); } gotapp1 = TRUE; t = exifscan(exifbuf, len, FALSE); if (t && t->props) { if (bflag || com) rc = writecom(fp, fname, app1, findprop(t->props, tags, EXIF_T_USERCOMMENT), exifbuf, t->md.btiff); else rc = printcom(fname, findprop(t->props, tags, EXIF_T_USERCOMMENT), t->md.btiff); } else { fprintf(stderr, "%s: couldn't find Exif properties\n", fname); rc = 1; } exiffree(t); free(exifbuf); } if (!gotapp1) { fprintf(stderr, "%s: couldn't find Exif data\n", fname); return (1); } return (rc); }
/* * Blank or write a comment. */ static int writecom(FILE *fp, const char *fname, long pos, struct exifprop *p, const unsigned char *buf, const unsigned char *btiff) { u_int32_t l; int ch, checkch; long psave; /* No comment tag or it's zero length. */ if (!p) { fprintf(stderr, "%s: comment not available\n", fname); return (1); } if (p->count < 9) { fprintf(stderr, "%s: comment size zero\n", fname); return (1); } /* Be careful with existing or unsupported comments. */ if (iflag && *(btiff + p->value)) { if (memcmp(ASCCOM, btiff + p->value, 8)) { if (nflag) return (1); fprintf(stderr, "overwrite %.8s comment in %s? " "(y/n [n]) ", btiff + p->value, fname); checkch = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (checkch != 'y' && checkch != 'Y') { fprintf(stderr, "not overwritten\n"); return (1); } } else if (p->str && *(p->str)) { if (nflag) return (1); fprintf(stderr, "overwrite comment in %s? (y/n [n]) ", fname); checkch = ch = getchar(); while (ch != '\n' && ch != EOF) ch = getchar(); if (checkch != 'y' && checkch != 'Y') { fprintf(stderr, "not overwritten\n"); return (1); } } } /* Remember where we are and move to the comment in our file. */ psave = ftell(fp); if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET)) exifdie((const char *)strerror(errno)); /* * Blank it with zeros and then reset the position. * XXX Note that this is counter to the spec's recommendation: * "When a UserComment area is set aside, it is recommended that * the ID code be ASCII and that the following user comment part * be filled with blank characters [20.H]." However, cameras (Canon) * seem to make it all zero; the rationale is that one can restore * the image file to its original state. */ if (bflag) { for (l = 0; l < p->count; l++) if (fwrite("\0", 1, 1, fp) != 1) exifdie((const char *)strerror(errno)); if (fseek(fp, pos + (btiff - buf) + p->value, SEEK_SET)) exifdie((const char *)strerror(errno)); } /* Write the character code and comment. */ if (com) { l = strlen(com); if (l > p->count - 8) { fprintf(stderr, "%s: truncating comment to fit\n", fname); l = p->count - 8; } /* Character code. */ if (fwrite(ASCCOM, 8, 1, fp) != 1) exifdie((const char *)strerror(errno)); /* Comment. */ if (fwrite(com, l, 1, fp) != 1) exifdie((const char *)strerror(errno)); /* * Pad with spaces (this seems to be standard practice). * XXX For now we're not NUL terminating the string. * This doesn't appear to be required by the spec, but it's * always possible that something out there will break. * I've seen some utilities pad with spaces and set the * last byte to NUL. */ for (l = p->count - 8 - l; l; l--) if (fwrite(" ", 1, 1, fp) != 1) exifdie((const char *)strerror(errno)); } /* Restore the file pointer. */ if (fseek(fp, psave, SEEK_SET)) exifdie((const char *)strerror(errno)); return (0); }
int main(int argc, char **argv) { register int ch; int eval, fnum, wantall; char *rmode, *wmode; FILE *fp; progname = argv[0]; debug = FALSE; ttags = wantall = eval = 0; lflag = qflag = wflag = FALSE; iflag = TRUE; v = NULL; #ifdef WIN32 rmode = "rb"; wmode = "r+b"; #else rmode = "r"; wmode = "r+"; #endif while ((ch = getopt(argc, argv, "filqs:t:v:w")) != -1) switch (ch) { case 'f': iflag = FALSE; break; case 'i': iflag = TRUE; break; case 'l': lflag = TRUE; break; case 'q': qflag = TRUE; break; case 's': delim = optarg; break; case 't': while (*optarg) { switch (*optarg) { case 'c': ttags |= ET_CREATE; break; case 'd': ttags |= ET_DIGI; break; case 'g': ttags |= ET_GEN; break; case 'a': wantall = 1; break; default: usage(); } optarg++; } if (wantall) ttags = 0; break; case 'v': v = vary_append(v, optarg); break; case 'w': wflag = TRUE; break; case '?': default: usage(); } argc -= optind; argv += optind; if (!*argv) usage(); /* Don't be quiet if we're not writing. */ if (qflag && !wflag) qflag = FALSE; /* Prepare an array for sorting. */ if (lflag) { wflag = 0; lorder = (struct linfo *)calloc(argc, sizeof(struct linfo)); if (!lorder) exifdie((const char *)strerror(errno)); } /* Run through the files... */ for (fnum = 0; *argv; ++argv, fnum++) { fname = *argv; /* Only open for read+write if we need to. */ if ((fp = fopen(*argv, wflag ? wmode : rmode)) == NULL) { exifwarn2(strerror(errno), *argv); eval = 1; continue; } /* Print filenames if more than one. */ if (argc > 1 && !lflag && !qflag) printf("%s%s:\n", fnum == 0 ? "" : "\n", *argv); if (lflag) lorder[fnum].fn = fname; if (doit(fp, fnum)) eval = 1; fclose(fp); } /* * We'd like to use mergesort() here (instead of qsort()) because * qsort() isn't stable w/members that compare equal and we exepect * the list of files to frequently already be in order. However, * FreeBSD seems to be the only platform with mergesort(), and I'm * not inclined to include the function... */ if (lflag) { qsort(lorder, argc, sizeof(struct linfo), lcomp); for (fnum = 0; fnum < argc; fnum++) printf("%s\n", lorder[fnum].fn); free(lorder); /* XXX Over in usage()? */ } vary_destroy(v); return (eval); }