1  /*******************************************************************
2  * $Id: jpeginfo.c,v 1.15 2009/10/12 04:23:40 tjko Exp $
3  *
4  * JPEGinfo
5  * Copyright (c) Timo Kokkonen, 1995-2009.
6  *
7  */
8 
9 #ifdef HAVE_CONFIG_H
10 #include "config.h"
11 #endif
12 
13 #include <stdio.h>
14 #if HAVE_GETOPT_H && HAVE_GETOPT_LONG
15   #include <getopt.h>
16 #else
17   #include "getopt.h"
18 #endif
19 #include <stdlib.h>
20 #include <string.h>
21 #include <setjmp.h>
22 #include <ctype.h>
23 #include <jpeglib.h>
24 
25 #include "md5.h"
26 #include "jpeginfo.h"
27 
28 
29 #define VERSION     "1.6.1"
30 #define BUF_LINES   255
31 
32 #ifndef HOST_TYPE
33 #define HOST_TYPE ""
34 #endif
35 
36 #ifdef BROKEN_METHODDEF
37 #undef METHODDEF
38 #define METHODDEF(x) static x
39 #endif
40 
41 #define EXIF_JPEG_MARKER   JPEG_APP0+1
42 #define EXIF_IDENT_STRING  "Exif\000\000"
43 
44 
45 static char *rcsid = "$Id: jpeginfo.c,v 1.15 2009/10/12 04:23:40 tjko Exp $";
46 
47 struct my_error_mgr {
48   struct jpeg_error_mgr pub;
49   jmp_buf setjmp_buffer;
50 };
51 typedef struct my_error_mgr * my_error_ptr;
52 
53 static struct jpeg_decompress_struct cinfo;
54 static struct my_error_mgr jerr;
55 
56 static struct option long_options[] = {
57   {"verbose",0,0,'v'},
58   {"delete",0,0,'d'},
59   {"mode",1,0,'m'},
60   {"file",1,0,'f'},
61   {"check",0,0,'c'},
62   {"help",0,0,'h'},
63   {"quiet",0,0,'q'},
64   {"lsstyle",0,0,'l'},
65   {"info",0,0,'i'},
66   {"md5",0,0,'5'},
67   {"version",0,0,'V'},
68   {"comments",0,0,'C'},
69   {0,0,0,0}
70 };
71 
72 FILE *infile=NULL;
73 FILE *listfile=NULL;
74 int global_error_counter;
75 int global_total_errors;
76 int verbose_mode = 0;
77 int quiet_mode = 0;
78 int delete_mode = 0;
79 int check_mode = 0;
80 int com_mode = 0;
81 int del_mode = 0;
82 int opt_index = 0;
83 int list_mode = 0;
84 int longinfo_mode = 0;
85 int input_from_file = 0;
86 int md5_mode = 0;
87 char *current = NULL;
88 
89 /*****************************************************************/
90 
91 METHODDEF(void)
my_error_exit(j_common_ptr cinfo)92 my_error_exit (j_common_ptr cinfo)
93 {
94   my_error_ptr myerr = (my_error_ptr)cinfo->err;
95   (*cinfo->err->output_message) (cinfo);
96   longjmp(myerr->setjmp_buffer,1);
97 }
98 
99 
100 METHODDEF(void)
my_output_message(j_common_ptr cinfo)101 my_output_message (j_common_ptr cinfo)
102 {
103   char buffer[JMSG_LENGTH_MAX];
104 
105   (*cinfo->err->format_message) (cinfo, buffer);
106   if (quiet_mode < 2) printf(" %s ",buffer);
107   global_error_counter++;
108   global_total_errors++;
109 }
110 
111 
no_memory(void)112 void no_memory(void)
113 {
114   fprintf(stderr,"jpeginfo: not enough memory!\n");
115   exit(3);
116 }
117 
118 
p_usage(void)119 void p_usage(void)
120 {
121  if (!quiet_mode) {
122   fprintf(stderr,"jpeginfo v" VERSION
123 	  " Copyright (c) Timo Kokkonen, 1995-2009.\n");
124 
125   fprintf(stderr,
126        "Usage: jpeginfo [options] <filenames>\n\n"
127        "  -c, --check     check files also for errors\n"
128        "  -C, --comments  display comments (from COM markers)\n"
129        "  -d, --delete    delete files that have errors\n"
130        "  -f<filename>,  --file<filename>\n"
131        "                  read the filenames to process from given file\n"
132        "                  (for standard input use '-' as a filename)\n"
133        "  -h, --help      display this help and exit\n"
134        "  -5, --md5       calculate MD5 checksum for each file\n"
135        "  -i, --info      display even more information about pictures\n"
136        "  -l, --lsstyle   use alternate listing format (ls -l style)\n"
137        "  -v, --verbose   enable verbose mode (positively chatty)\n"
138        "  --version	  print program version and exit\n"
139        "  -q, --quiet     quiet mode, output just jpeg infos\n"
140        "  -m<mode>, --mode=<mode>\n"
141        "                  defines which jpegs to remove (when using"
142 	                 " the -d option).\n"
143        "                  Mode can be one of the following:\n"
144        "                    erronly     only files with serious errrors\n"
145        "                    all         files ontaining warnings or errors"
146        " (default)\n\n\n");
147  }
148 
149  exit(0);
150 }
151 
152 
153 /*****************************************************************/
main(int argc,char ** argv)154 int main(int argc, char **argv)
155 {
156   JSAMPARRAY buf = malloc(sizeof(JSAMPROW)*BUF_LINES);
157   jpeg_saved_marker_ptr exif_marker, cmarker;
158   MD5_CTX *MD5 = malloc(sizeof(MD5_CTX));
159   volatile int i;
160   int c,j,lines_read, err_count;
161   unsigned char ch;
162   char namebuf[1024];
163   long fs;
164   char *md5buf,digest[16],digest_text[33];
165 
166   global_total_errors=0;
167   if (rcsid); /* to keep compiler from not complaining about rcsid */
168 
169   cinfo.err = jpeg_std_error(&jerr.pub);
170   jpeg_create_decompress(&cinfo);
171   jerr.pub.error_exit=my_error_exit;
172   jerr.pub.output_message=my_output_message;
173 
174   if (!buf || !MD5) no_memory();
175   if (argc<2) {
176     if (quiet_mode < 2) fprintf(stderr,"jpeginfo: file arguments missing\n"
177 			     "Try 'jpeginfo "
178 			     "--help"
179 			     "' for more information.\n");
180     exit(1);
181   }
182 
183   /* parse command line parameters */
184   while(1) {
185     opt_index=0;
186     if ( (c=getopt_long(argc,argv,"livVdcChqm:f:5",
187 			long_options,&opt_index))  == -1)
188       break;
189     switch (c) {
190     case 'm':
191         if (!strcasecmp(optarg,"all")) del_mode=0;
192         else if (!strcasecmp(optarg,"erronly")) del_mode=1;
193 	else if (!quiet_mode)
194 	  fprintf(stderr,"Unknown parameter for -m, --mode.\n");
195       break;
196     case 'f':
197         if (!strcmp(optarg,"-")) listfile=stdin;
198 	else if ((listfile=fopen(optarg,"r"))==NULL) {
199 	  fprintf(stderr,"Cannot open file '%s'.\n",optarg);
200 	  exit(2);
201 	}
202 	input_from_file=1;
203 	break;
204     case 'v':
205       verbose_mode=1;
206       break;
207     case 'V':
208       fprintf(stderr,"jpeginfo v" VERSION "  " HOST_TYPE
209 	      "\nCopyright (c) Timo Kokkonen, 1995-2002.\n");
210       exit(0);
211     case 'd':
212       delete_mode=1;
213       break;
214     case 'c':
215       check_mode=1;
216       break;
217     case 'h':
218       p_usage();
219       break;
220     case 'q':
221       quiet_mode++;
222       break;
223     case 'l':
224       list_mode=1;
225       break;
226     case 'i':
227       longinfo_mode=1;
228       break;
229     case '5':
230       md5_mode=1;
231       break;
232     case 'C':
233       com_mode=1;
234       break;
235     case '?':
236       break;
237 
238     default:
239       if (!quiet_mode)
240 	fprintf(stderr,"jpeginfo: error parsing parameters.\n");
241     }
242   }
243 
244   if (delete_mode && verbose_mode && !quiet_mode)
245     fprintf(stderr,"jpeginfo: delete mode enabled (%s)\n",
246 	    !del_mode?"normal":"errors only");
247 
248   i=1;
249   do {
250    if (input_from_file) {
251      if (!fgetstr(namebuf,sizeof(namebuf),listfile)) break;
252      current=namebuf;
253    }
254    else current=argv[i];
255 
256    if (current[0]==0) continue;
257    if (current[0]=='-' && !input_from_file) continue;
258 
259    if (setjmp(jerr.setjmp_buffer)) {
260       jpeg_abort_decompress(&cinfo);
261       fclose(infile);
262       if (list_mode && quiet_mode < 2) printf(" %s",current);
263       if (quiet_mode < 2) printf(" [ERROR]\n");
264       if (delete_mode) delete_file(current,verbose_mode,quiet_mode);
265       continue;
266    }
267 
268    if ((infile=fopen(current,"r"))==NULL) {
269      if (!quiet_mode) fprintf(stderr, "jpeginfo: can't open '%s'\n", current);
270      continue;
271    }
272    if (is_dir(infile)) {
273      fclose(infile);
274      if (verbose_mode) printf("directory: %s  skipped\n",current);
275      continue;
276    }
277 
278    fs=filesize(infile);
279 
280    if (md5_mode) {
281      md5buf=malloc(fs);
282      if (!md5buf) no_memory();
283      fread(md5buf,1,fs,infile);
284      rewind(infile);
285 
286      MD5Init(MD5);
287      MD5Update(MD5,md5buf,fs);
288      MD5Final(digest,MD5);
289      md2str(digest,digest_text);
290 
291      free(md5buf);
292    }
293 
294    if (!list_mode && quiet_mode < 2) printf("%s ",current);
295 
296    global_error_counter=0;
297    err_count=jerr.pub.num_warnings;
298    if (com_mode) jpeg_save_markers(&cinfo, JPEG_COM, 0xffff);
299    jpeg_save_markers(&cinfo, EXIF_JPEG_MARKER, 0xffff);
300    jpeg_stdio_src(&cinfo, infile);
301    jpeg_read_header(&cinfo, TRUE);
302 
303    /* check for Exif marker */
304    exif_marker=NULL;
305    cmarker=cinfo.marker_list;
306    while (cmarker) {
307      if (cmarker->marker == EXIF_JPEG_MARKER) {
308        if (!memcmp(cmarker->data,EXIF_IDENT_STRING,6)) exif_marker=cmarker;
309      }
310      cmarker=cmarker->next;
311    }
312 
313    if (quiet_mode < 2) {
314      printf("%4d x %-4d %2dbit ",(int)cinfo.image_width,
315             (int)cinfo.image_height,(int)cinfo.num_components*8);
316 
317      if (exif_marker) printf("Exif  ");
318      else if (cinfo.saw_JFIF_marker) printf("JFIF  ");
319      else if (cinfo.saw_Adobe_marker) printf("Adobe ");
320      else printf("n/a   ");
321 
322      if (longinfo_mode) {
323        printf("%s %s",(cinfo.progressive_mode?"Progressive":"Normal"),
324 	      (cinfo.arith_code?"Arithmetic":"Huffman") );
325 
326        if (cinfo.density_unit==1||cinfo.density_unit==2)
327 	 printf(",%ddp%c",MIN(cinfo.X_density,cinfo.Y_density),
328 		(cinfo.density_unit==1?'i':'c') );
329 
330        if (cinfo.CCIR601_sampling) printf(",CCIR601");
331        printf(" %7ld ",fs);
332 
333      } else printf("%c %7ld ",(cinfo.progressive_mode?'P':'N'),fs);
334 
335      if (md5_mode) printf("%s ",digest_text);
336      if (list_mode) printf("%s ",current);
337 
338      if (com_mode) {
339        cmarker=cinfo.marker_list;
340        while (cmarker) {
341 	 if (cmarker->marker == JPEG_COM) {
342 	   printf("\"");
343 	   for (j=0;j<cmarker->data_length;j++) {
344 	     ch = cmarker->data[j];
345 	     if (ch < 32 || iscntrl(ch)) continue;
346 	     printf("%c",cmarker->data[j]);
347 	   }
348 	   printf("\" ");
349 	 }
350 	 cmarker=cmarker->next;
351        }
352      }
353    }
354 
355    if (check_mode) {
356      cinfo.out_color_space=JCS_GRAYSCALE; /* to speed up the process... */
357      cinfo.scale_denom = 8;
358      cinfo.scale_num = 1;
359      jpeg_start_decompress(&cinfo);
360 
361      for (j=0;j<BUF_LINES;j++) {
362         buf[j]=malloc(sizeof(JSAMPLE)*cinfo.output_width*
363                                       cinfo.out_color_components);
364         if (!buf[j]) no_memory();
365      }
366 
367      while (cinfo.output_scanline < cinfo.output_height) {
368        lines_read = jpeg_read_scanlines(&cinfo, buf,BUF_LINES);
369      }
370 
371      jpeg_finish_decompress(&cinfo);
372      for(j=0;j<BUF_LINES;j++) free(buf[j]);
373 
374      if (!global_error_counter) {
375        if (quiet_mode < 2) printf(" [OK]\n");
376      }
377      else {
378        if (quiet_mode < 2) printf(" [WARNING]\n");
379        if (delete_mode && !del_mode)
380 	 delete_file(current,verbose_mode,quiet_mode);
381      }
382    }
383    else { /* !check_mode */
384      if (quiet_mode < 2) printf("\n");
385      jpeg_abort_decompress(&cinfo);
386    }
387 
388    fclose(infile);
389 
390   } while (++i<argc || input_from_file);
391 
392   jpeg_destroy_decompress(&cinfo);
393   free(buf);
394   free(MD5);
395 
396   return (global_total_errors>0?1:0); /* return 1 if any errors found file(s)
397 					 we checked */
398 }
399 
400 /* :-) */
401