1 //--------------------------------------------------------------------------
2 // Program to pull the information out of various types of EXIF digital
3 // camera files and show it in a reasonably consistent way
4 // Version 1.7
5 //
6 //
7 // Compiling under Unix:
8 // Use: cc -O3 -o jhead jhead.c exif.c -lm
9 //
10 // Compiling under Windows:  Use MSVC5 or MSVC6, from command line:
11 // cl -Ox jhead.c exif.c myglob.c
12 //
13 // Dec 1999 - May 2002
14 //
15 // by Matthias Wandel (mwandel@rim.net),
16 //--------------------------------------------------------------------------
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <memory.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <time.h>
23 #include <sys/stat.h>
24 #include <errno.h>
25 #include <ctype.h>
26 
27 #define JHEAD_VERSION "1.7"
28 
29 // This #define turns on features that are too very specific to
30 // how I organize my photos.  Best to ignore everything inside #ifdef MATTHIAS
31 //#define MATTHIAS
32 
33 #ifdef _WIN32
34     #include <process.h>
35     #include <io.h>
36     #include <sys/utime.h>
37 #else
38     #include <utime.h>
39     #include <sys/types.h>
40     #include <unistd.h>
41     #include <errno.h>
42     #include <limits.h>
43 #endif
44 
45 #include "jhead.h"
46 
47 static int FilesMatched;
48 
49 static const char * CurrentFile;
50 
51 //--------------------------------------------------------------------------
52 // Command line options flags
53 static int DoModify     = FALSE;
54        int ShowTags     = FALSE;    // Do not show raw by default.
55 
56 static int SupressNonFatalErrors = TRUE; // Wether or not to pint warnings on recoverable errors
57 
58 
59 #ifdef MATTHIAS
60     // This #ifdef to take out less than elegant stuff for editing
61     // the comments in a jpeg.  The programs rdjpgcom and wrjpgcom
62     // included with Linux distributions do a better job.
63 
64     static char * AddComment = NULL; // Add this tag.
65     static char * RemComment = NULL; // Remove this tag
66     static int AutoResize = FALSE;
67 #endif // MATTHIAS
68 
69 //--------------------------------------------------------------------------
70 // Error exit handler
71 //--------------------------------------------------------------------------
ErrFatal(char * msg)72 void ErrFatal(char * msg)
73 {
74     fprintf(stderr,"Error : %s\n", msg);
75     if (CurrentFile) fprintf(stderr,"in file '%s'\n",CurrentFile);
76     exit(EXIT_FAILURE);
77 }
78 
79 //--------------------------------------------------------------------------
80 // Report non fatal errors.  Now that microsoft.net modifies exif headers,
81 // there's corrupted ones, and there could be more in the future.
82 //--------------------------------------------------------------------------
ErrNonfatal(char * msg,int a1,int a2)83 void ErrNonfatal(char * msg, int a1, int a2)
84 {
85     if (SupressNonFatalErrors) return;
86 
87     fprintf(stderr,"Nonfatal Error : ");
88     if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile);
89     fprintf(stderr, msg, a1, a2);
90     fprintf(stderr, "\n");
91 }
92 
93 //--------------------------------------------------------------------------
94 // Do selected operations to one file at a time.
95 //--------------------------------------------------------------------------
ProcessFile(const char * FileName)96 void ProcessFile(const char * FileName)
97 {
98 #ifdef APPLY_COMMAND
99     int Modified = FALSE;
100 #endif /* APPLY_COMMAND */
101     ReadMode_t ReadMode = READ_EXIF;
102 
103     CurrentFile = FileName;
104 
105     ResetJpgfile();
106 
107     // Start with an empty image information structure.
108     memset(&ImageInfo, 0, sizeof(ImageInfo));
109     ImageInfo.FlashUsed = -1;
110     ImageInfo.MeteringMode = -1;
111 
112     // Store file date/time.
113     {
114         struct stat st;
115         if (stat(FileName, &st) >= 0){
116             ImageInfo.FileDateTime = st.st_mtime;
117             ImageInfo.FileSize = st.st_size;
118         }else{
119             ErrFatal("No such file");
120         }
121     }
122 
123     strncpy(ImageInfo.FileName, FileName, PATH_MAX);
124 
125     FilesMatched += 1;
126 
127     FilesMatched = TRUE; // Turns off complaining that nothing matched.
128 
129     if (DoModify){
130         ReadMode |= READ_IMAGE;
131     }
132 
133     if (!ReadJpegFile(FileName, ReadMode)) return;
134 
135 #ifdef VERBOSE
136     if (CheckFileSkip()){
137         DiscardData();
138         return;
139     }
140 
141     if (ShowConcise){
142         ShowConciseImageInfo();
143     }else{
144         if (!(DoModify || DoReadAction) || ShowTags){
145             ShowImageInfo();
146         }
147     }
148 
149     if (ThumbnailName){
150         if (ImageInfo.ThumbnailPointer){
151             FILE * ThumbnailFile;
152             char OutFileName[PATH_MAX+1];
153 
154             // Make a relative name.
155             RelativeName(OutFileName, ThumbnailName, FileName);
156 
157 #ifndef _WIN32
158             if (strcmp(ThumbnailName, "-") == 0){
159                 // A filename of '-' indicates thumbnail goes to stdout.
160                 // This doesn't make much sense under Windows, so this feature is unix only.
161                 ThumbnailFile = stdout;
162             }else
163 #endif
164             {
165                 ThumbnailFile = fopen(OutFileName,"wb");
166             }
167 
168             if (ThumbnailFile){
169                 fwrite(ImageInfo.ThumbnailPointer, ImageInfo.ThumbnailSize ,1, ThumbnailFile);
170                 fclose(ThumbnailFile);
171                 if (ThumbnailFile != stdout){
172                     printf("Created: '%s'\n", OutFileName);
173                 }else{
174                     // No point in printing to stdout when that is where the thumbnail goes!
175                 }
176             }else{
177                 ErrFatal("Could not write thumbnail file");
178             }
179         }else{
180             printf("Image '%s' contains no thumbnail\n",FileName);
181         }
182     }
183 
184 #ifdef MATTHIAS
185     if (AddComment || RemComment|| EditComment){
186 #else
187     if (EditComment){
188 #endif
189         Section_t * CommentSec;
190         char Comment[1000];
191         int CommentSize;
192 
193         CommentSec = FindSection(M_COM);
194 
195         if (CommentSec == NULL){
196             unsigned char * DummyData;
197 
198             DummyData = (uchar *) malloc(3);
199             DummyData[0] = 0;
200             DummyData[1] = 2;
201             DummyData[2] = 0;
202             CommentSec = CreateSection(M_COM, DummyData, 2);
203         }
204 
205         CommentSize = CommentSec->Size-2;
206 
207 #ifdef MATTHIAS
208         if (ModifyDescriptComment(Comment, (char *)CommentSec->Data+2)){
209             Modified = TRUE;
210             CommentSize = strlen(Comment);
211         }
212         if (EditComment)
213 #else
214         memcpy(Comment, (char *)CommentSec->Data+2, CommentSize);
215 #endif
216         {
217             char EditFileName[PATH_MAX+4];
218             strcpy(EditFileName, FileName);
219             strcat(EditFileName, ".txt");
220 
221             CommentSize = FileEditComment(EditFileName, Comment, CommentSize);
222         }
223 
224         if (strcmp(Comment, (char *)CommentSec->Data+2)){
225             // Discard old comment section and put a new one in.
226             int size;
227             size = CommentSize+2;
228             free(CommentSec->Data);
229             CommentSec->Size = size;
230             CommentSec->Data = malloc(size);
231             CommentSec->Data[0] = (uchar)(size >> 8);
232             CommentSec->Data[1] = (uchar)(size);
233             memcpy((CommentSec->Data)+2, Comment, size-2);
234             Modified = TRUE;
235         }
236         if (!Modified){
237             printf("Comment not modified\n");
238         }
239     }
240 
241     if (ExifTimeAdjust || ExifTimeSet){
242         if (ImageInfo.DatePointer){
243             struct tm tm;
244             time_t UnixTime;
245             char TempBuf[50];
246 
247             if (ExifTimeSet){
248                 // A time to set was specified.
249                 UnixTime = ExifTimeSet;
250             }else{
251                 // A time offset to adjust by was specified.
252                 if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
253 
254                 // Convert to unix 32 bit time value, add offset, and convert back.
255                 UnixTime = mktime(&tm);
256                 if ((int)UnixTime == -1) goto badtime;
257                 UnixTime += ExifTimeAdjust;
258             }
259             tm = *localtime(&UnixTime);
260 
261             // Print to temp buffer first to avoid putting null termination in destination.
262             // snprintf() would do the trick ,but not available everywhere (like FreeBSD 4.4)
263             sprintf(TempBuf, "%04d:%02d:%02d %02d:%02d:%02d",
264                 tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
265                 tm.tm_hour, tm.tm_min, tm.tm_sec);
266 
267             memcpy(ImageInfo.DatePointer, TempBuf, 19);
268 
269             Modified = TRUE;
270         }else{
271             printf("File '%s' contains no Exif timestamp to change\n", FileName);
272         }
273     }
274 
275     if (TrimExif){
276         if (TrimExifFunc()) Modified = TRUE;
277     }
278 
279     if (DeleteComments){
280         if (RemoveSectionType(M_COM)) Modified = TRUE;
281     }
282     if (DeleteExif){
283         if (RemoveSectionType(M_EXIF)) Modified = TRUE;
284     }
285 
286 
287     if (Modified){
288         char BackupName[400];
289         printf("Modified: %s\n",FileName);
290 
291         strcpy(BackupName, FileName);
292         strcat(BackupName, ".t");
293 
294         // Remove any .old file name that may pre-exist
295         unlink(BackupName);
296 
297         // Rename the old file.
298         rename(FileName, BackupName);
299 
300         // Write the new file.
301         WriteJpegFile(FileName);
302 
303         // Now that we are done, remove original file.
304         unlink(BackupName);
305     }
306 
307 
308     if (Exif2FileTime){
309         // Set the file date to the date from the exif header.
310         if (ImageInfo.DateTime[0]){
311             // Converte the file date to Unix time.
312             struct tm tm;
313             time_t UnixTime;
314             struct utimbuf mtime;
315             if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
316 
317             UnixTime = mktime(&tm);
318             if ((int)UnixTime == -1){
319                 goto badtime;
320             }
321 
322             mtime.actime = UnixTime;
323             mtime.modtime = UnixTime;
324 
325             if (utime(FileName, &mtime) != 0){
326                 printf("Error: Could not change time of file '%s'\n",FileName);
327             }else{
328                 printf("%s\n",FileName);
329             }
330         }else{
331             printf("File '%s' contains no Exif timestamp\n", FileName);
332         }
333     }
334 
335     // Feature to rename image according to date and time from camera.
336     // I use this feature to put images from multiple digicams in sequence.
337 
338     if (RenameToDate){
339         int NumAlpha = 0;
340         int NumDigit = 0;
341         int PrefixPart = 0;
342 
343         for (a=0;FileName[a];a++){
344             if (FileName[a] == '/' || FileName[a] == '\\'){
345                 // Don't count path compoenent.
346                 NumAlpha = 0;
347                 NumDigit = 0;
348                 PrefixPart = a+1;
349             }
350             if (isalpha(FileName[a])) NumAlpha += 1;
351             if (isdigit(FileName[a])) NumDigit += 1;
352         }
353 
354         if ((NumAlpha <= 8 && NumDigit >= 2) || RenameToDate > 1){
355             if (ImageInfo.DateTime[0]){
356                 struct tm tm;
357                 if (Exif2tm(&tm, ImageInfo.DateTime)){
358                     char NewBaseName[PATH_MAX*2];
359 
360                     strcpy(NewBaseName, FileName); // Get path component of name.
361 
362                     if (strftime_args){
363                         // Complicated scheme for flexibility.  Just pass the args to strftime.
364                         time_t UnixTime;
365 
366                         // Call mktime to get weekday and such filled in.
367                         UnixTime = mktime(&tm);
368                         if ((int)UnixTime == -1) goto badtime;
369                         strftime(NewBaseName+PrefixPart, 99, strftime_args, &tm);
370                     }else{
371                         // My favourite scheme.
372                         sprintf(NewBaseName+PrefixPart, "%02d%02d-%02d%02d%02d",
373                              tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
374                     }
375 
376                     for (a=0;;a++){
377                         char NewName[120];
378                         char NameExtra[3];
379                         struct stat dummy;
380 
381                         if (a){
382                             // Generate a suffix for the file name if previous choice of names is taken.
383                             // depending on wether the name ends in a letter or digit, pick the opposite to separate
384                             // it.  This to avoid using a separator character - this because any good separator
385                             // is before the '.' in ascii, and so sorting the names would put the later name before
386                             // the name without suffix, causing the pictures to more likely be out of order.
387                             if (isdigit(NewBaseName[strlen(NewBaseName)-1])){
388                                 NameExtra[0] = 'a'-1+a; // Try a,b,c,d... for suffix if it ends in a letter.
389                             }else{
390                                 NameExtra[0] = '0'-1+a; // Try 1,2,3,4... for suffix if it ends in a char.
391                             }
392                             NameExtra[1] = 0;
393                         }else{
394                             NameExtra[0] = 0;
395                         }
396 
397                         sprintf(NewName, "%s%s.jpg", NewBaseName, NameExtra);
398 
399                         if (!strcmp(FileName, NewName)) break; // Skip if its already this name.
400 
401                         if (stat(NewName, &dummy)){
402                             // This name does not pre-exist.
403                             if (rename(FileName, NewName) == 0){
404                                 printf("%s --> %s\n",FileName, NewName);
405                             }else{
406                                 printf("Error: Couldn't rename '%s' to '%s'\n",FileName, NewName);
407                             }
408                             break;
409                         }
410 
411                         if (a >= 9){
412                             printf("Dest name for '%s' already exists\n",FileName);
413                             break;
414                         }
415                     }
416                 }else{
417                     printf("File '%s' contains no Exif timestamp\n", FileName);
418                 }
419             }
420         }
421     }
422     if(0){
423         badtime:
424         printf("Error: Time '%s': cannot convert to Unix time\n",ImageInfo.DateTime);
425     }
426     DiscardData();
427 #endif /* VERBOSE */
428 }
429 
430