jhead.c revision 34a2564d3268a5ca1472c5076675782fbaf724d6
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//
5// Version 2.71
6//
7// Compiling under Windows:
8//   Make sure you have Microsoft's compiler on the path, then run make.bat
9//
10// Dec 1999 - Feb 2007
11//
12// by Matthias Wandel   www.sentex.net/~mwandel
13//--------------------------------------------------------------------------
14#include "jhead.h"
15
16#include <sys/stat.h>
17#include <utils/Log.h>
18
19#define JHEAD_VERSION "2.71"
20
21// This #define turns on features that are too very specific to
22// how I organize my photos.  Best to ignore everything inside #ifdef MATTHIAS
23#define MATTHIAS
24
25#ifdef _WIN32
26    #include <io.h>
27#endif
28
29static int FilesMatched;
30static int FileSequence;
31
32static const char * CurrentFile;
33
34static const char * progname;   // program name for error messages
35
36//--------------------------------------------------------------------------
37// Command line options flags
38static int TrimExif = FALSE;        // Cut off exif beyond interesting data.
39static int RenameToDate = FALSE;
40static int RenameAssociatedFiles = FALSE;
41static char * strftime_args = NULL; // Format for new file name.
42static int Exif2FileTime  = FALSE;
43static int DoModify     = FALSE;
44static int DoReadAction = FALSE;
45       int ShowTags     = FALSE;    // Do not show raw by default.
46static int Quiet        = FALSE;    // Be quiet on success (like unix programs)
47       int DumpExifMap  = FALSE;
48static int ShowConcise  = FALSE;
49static int CreateExifSection = FALSE;
50static char * ApplyCommand = NULL;  // Apply this command to all images.
51static char * FilterModel = NULL;
52static int    ExifOnly    = FALSE;
53static int    PortraitOnly = FALSE;
54static time_t ExifTimeAdjust = 0;   // Timezone adjust
55static time_t ExifTimeSet = 0;      // Set exif time to a value.
56static char DateSet[11];
57static unsigned DateSetChars = 0;
58static unsigned FileTimeToExif = FALSE;
59
60static int DeleteComments = FALSE;
61static int DeleteExif = FALSE;
62static int DeleteIptc = FALSE;
63static int DeleteUnknown = FALSE;
64static char * ThumbSaveName = NULL; // If not NULL, use this string to make up
65                                    // the filename to store the thumbnail to.
66
67static char * ThumbInsertName = NULL; // If not NULL, use this string to make up
68                                    // the filename to retrieve the thumbnail from.
69
70static int RegenThumbnail = FALSE;
71
72static char * ExifXferScrFile = NULL;// Extract Exif header from this file, and
73                                    // put it into the Jpegs processed.
74
75static int EditComment = FALSE;     // Invoke an editor for editing the comment
76static int SupressNonFatalErrors = FALSE; // Wether or not to pint warnings on recoverable errors
77
78static char * CommentSavefileName = NULL; // Save comment to this file.
79static char * CommentInsertfileName = NULL; // Insert comment from this file.
80static char * CommentInsertLiteral = NULL;  // Insert this comment (from command line)
81
82static int AutoRotate = FALSE;
83static int ZeroRotateTagOnly = FALSE;
84
85static int ShowFileInfo = TRUE;     // Indicates to show standard file info
86                                    // (file name, file size, file date)
87
88
89
90#ifdef MATTHIAS
91    // This #ifdef to take out less than elegant stuff for editing
92    // the comments in a JPEG.  The programs rdjpgcom and wrjpgcom
93    // included with Linux distributions do a better job.
94
95    static char * AddComment = NULL; // Add this tag.
96    static char * RemComment = NULL; // Remove this tag
97    static int AutoResize = FALSE;
98#endif // MATTHIAS
99
100//--------------------------------------------------------------------------
101// Error exit handler
102//--------------------------------------------------------------------------
103void ErrFatal(char * msg)
104{
105    LOGE("Error : %s\n", msg);
106    fprintf(stderr,"Error : %s\n", msg);
107    if (CurrentFile) fprintf(stderr,"in file '%s'\n",CurrentFile);
108    exit(EXIT_FAILURE);
109}
110
111//--------------------------------------------------------------------------
112// Report non fatal errors.  Now that microsoft.net modifies exif headers,
113// there's corrupted ones, and there could be more in the future.
114//--------------------------------------------------------------------------
115void ErrNonfatal(char * msg, int a1, int a2)
116{
117    LOGE("Nonfatal Error : ");
118    LOGE(msg, a1, a2);
119    if (SupressNonFatalErrors) return;
120
121    fprintf(stderr,"Nonfatal Error : ");
122    if (CurrentFile) fprintf(stderr,"'%s' ",CurrentFile);
123    fprintf(stderr, msg, a1, a2);
124    fprintf(stderr, "\n");
125}
126
127//--------------------------------------------------------------------------
128// Set file time as exif time.
129//--------------------------------------------------------------------------
130void FileTimeAsString(char * TimeStr)
131{
132    struct tm ts;
133    ts = *localtime(&ImageInfo.FileDateTime);
134    strftime(TimeStr, 20, "%Y:%m:%d %H:%M:%S", &ts);
135}
136
137#if 0       // not used -- possible security risk with use of system, sprintf, etc.
138//--------------------------------------------------------------------------
139// Invoke an editor for editing a string.
140//--------------------------------------------------------------------------
141static int FileEditComment(char * TempFileName, char * Comment, int CommentSize)
142{
143    FILE * file;
144    int a;
145    char QuotedPath[PATH_MAX];
146
147    file = fopen(TempFileName, "w");
148    if (file == NULL){
149        fprintf(stderr, "Can't create file '%s'\n",TempFileName);
150        ErrFatal("could not create temporary file");
151    }
152    fwrite(Comment, CommentSize, 1, file);
153
154    fclose(file);
155
156    fflush(stdout); // So logs are contiguous.
157
158    {
159    char * Editor;
160    Editor = getenv("EDITOR");
161        if (Editor == NULL){
162#ifdef _WIN32
163            Editor = "notepad";
164#else
165            Editor = "vi";
166#endif
167        }
168
169        sprintf(QuotedPath, "%s \"%s\"",Editor, TempFileName);
170        a = system(QuotedPath);
171    }
172
173    if (a != 0){
174        perror("Editor failed to launch");
175        exit(-1);
176    }
177
178    file = fopen(TempFileName, "r");
179    if (file == NULL){
180        ErrFatal("could not open temp file for read");
181    }
182
183    // Read the file back in.
184    CommentSize = fread(Comment, 1, 999, file);
185
186    fclose(file);
187
188    unlink(TempFileName);
189
190    return CommentSize;
191}
192
193#ifdef MATTHIAS
194//--------------------------------------------------------------------------
195// Modify one of the lines in the comment field.
196// This very specific to the photo album program stuff.
197//--------------------------------------------------------------------------
198static char KnownTags[][10] = {"date", "desc","scan_date","author",
199                               "keyword","videograb",
200                               "show_raw","panorama","titlepix",""};
201
202static int ModifyDescriptComment(char * OutComment, char * SrcComment)
203{
204    char Line[500];
205    int Len;
206    int a,i;
207    unsigned l;
208    int HasScandate = FALSE;
209    int TagExists = FALSE;
210    int Modified = FALSE;
211    Len = 0;
212
213    OutComment[0] = 0;
214
215
216    for (i=0;;i++){
217        if (SrcComment[i] == '\r' || SrcComment[i] == '\n' || SrcComment[i] == 0 || Len >= 199){
218            // Process the line.
219            if (Len > 0){
220                Line[Len] = 0;
221                //printf("Line: '%s'\n",Line);
222                for (a=0;;a++){
223                    l = strlen(KnownTags[a]);
224                    if (!l){
225                        // Unknown tag.  Discard it.
226                        printf("Error: Unknown tag '%s'\n", Line); // Deletes the tag.
227                        Modified = TRUE;
228                        break;
229                    }
230                    if (memcmp(Line, KnownTags[a], l) == 0){
231                        if (Line[l] == ' ' || Line[l] == '=' || Line[l] == 0){
232                            // Its a good tag.
233                            if (Line[l] == ' ') Line[l] = '='; // Use equal sign for clarity.
234                            if (a == 2) break; // Delete 'orig_path' tag.
235                            if (a == 3) HasScandate = TRUE;
236                            if (RemComment){
237                                if (strlen(RemComment) == l){
238                                    if (!memcmp(Line, RemComment, l)){
239                                        Modified = TRUE;
240                                        break;
241                                    }
242                                }
243                            }
244                            if (AddComment){
245                                // Overwrite old comment of same tag with new one.
246                                if (!memcmp(Line, AddComment, l+1)){
247                                    TagExists = TRUE;
248                                    strcpy(Line, AddComment);
249                                    Modified = TRUE;
250                                }
251                            }
252                            strcat(OutComment, Line);
253                            strcat(OutComment, "\n");
254                            break;
255                        }
256                    }
257                }
258            }
259            Line[Len = 0] = 0;
260            if (SrcComment[i] == 0) break;
261        }else{
262            Line[Len++] = SrcComment[i];
263        }
264    }
265
266    if (AddComment && TagExists == FALSE){
267        strcat(OutComment, AddComment);
268        strcat(OutComment, "\n");
269        Modified = TRUE;
270    }
271
272    if (!HasScandate && !ImageInfo.DateTime[0]){
273        // Scan date is not in the file yet, and it doesn't have one built in.  Add it.
274        char Temp[30];
275        sprintf(Temp, "scan_date=%s", ctime(&ImageInfo.FileDateTime));
276        strcat(OutComment, Temp);
277        Modified = TRUE;
278    }
279    return Modified;
280}
281//--------------------------------------------------------------------------
282// Automatic make smaller command stuff
283//--------------------------------------------------------------------------
284static int AutoResizeCmdStuff(void)
285{
286    static char CommandString[500];
287    double scale;
288
289    ApplyCommand = CommandString;
290
291    if (ImageInfo.Height <= 1280 && ImageInfo.Width <= 1280){
292        printf("not resizing %dx%x '%s'\n",ImageInfo.Height, ImageInfo.Width, ImageInfo.FileName);
293        return FALSE;
294    }
295
296    scale = 1024.0 / ImageInfo.Height;
297    if (1024.0 / ImageInfo.Width < scale) scale = 1024.0 / ImageInfo.Width;
298
299    if (scale < 0.5) scale = 0.5; // Don't scale down by more than a factor of two.
300
301    sprintf(CommandString, "mogrify -geometry %dx%d -quality 85 &i",(int)(ImageInfo.Width*scale), (int)(ImageInfo.Height*scale));
302    return TRUE;
303}
304
305
306#endif // MATTHIAS
307
308
309//--------------------------------------------------------------------------
310// Apply the specified command to the JPEG file.
311//--------------------------------------------------------------------------
312static void DoCommand(const char * FileName, int ShowIt)
313{
314    int a,e;
315    char ExecString[400];
316    char TempName[200];
317    int TempUsed = FALSE;
318
319    e = 0;
320
321    // Make a temporary file in the destination directory by changing last char.
322    strcpy(TempName, FileName);
323    a = strlen(TempName)-1;
324    TempName[a] = (char)(TempName[a] == 't' ? 'z' : 't');
325
326    // Build the exec string.  &i and &o in the exec string get replaced by input and output files.
327    for (a=0;;a++){
328        if (ApplyCommand[a] == '&'){
329            if (ApplyCommand[a+1] == 'i'){
330                // Input file.
331                e += sprintf(ExecString+e, "\"%s\"",FileName);
332                a += 1;
333                continue;
334            }
335            if (ApplyCommand[a+1] == 'o'){
336                // Needs an output file distinct from the input file.
337                e += sprintf(ExecString+e, "\"%s\"",TempName);
338                a += 1;
339                TempUsed = TRUE;
340                unlink(TempName);// Remove any pre-existing temp file
341                continue;
342            }
343        }
344        ExecString[e++] = ApplyCommand[a];
345        if (ApplyCommand[a] == 0) break;
346    }
347
348    if (ShowIt) printf("Cmd:%s\n",ExecString);
349
350    errno = 0;
351    a = system(ExecString);
352
353    if (a || errno){
354        // A command can however fail without errno getting set or system returning an error.
355        if (errno) perror("system");
356        ErrFatal("Problem executing specified command");
357    }
358
359    if (TempUsed){
360        // Don't delete original file until we know a new one was created by the command.
361        struct stat dummy;
362        if (stat(TempName, &dummy) == 0){
363            unlink(FileName);
364            rename(TempName, FileName);
365        }else{
366            ErrFatal("specified command did not produce expected output file");
367        }
368    }
369}
370
371//--------------------------------------------------------------------------
372// check if this file should be skipped based on contents.
373//--------------------------------------------------------------------------
374static int CheckFileSkip(void)
375{
376    // I sometimes add code here to only process images based on certain
377    // criteria - for example, only to convert non progressive Jpegs to progressives, etc..
378
379    if (FilterModel){
380        // Filtering processing by camera model.
381        // This feature is useful when pictures from multiple cameras are colated,
382        // the its found that one of the cameras has the time set incorrectly.
383        if (strstr(ImageInfo.CameraModel, FilterModel) == NULL){
384            // Skip.
385            return TRUE;
386        }
387    }
388
389    if (ExifOnly){
390        // Filtering by EXIF only.  Skip all files that have no Exif.
391        if (FindSection(M_EXIF) == NULL){
392            return TRUE;
393        }
394    }
395
396    if (PortraitOnly == 1){
397        if (ImageInfo.Width > ImageInfo.Height) return TRUE;
398    }
399
400    if (PortraitOnly == -1){
401        if (ImageInfo.Width < ImageInfo.Height) return TRUE;
402    }
403
404    return FALSE;
405}
406
407//--------------------------------------------------------------------------
408// Subsititute original name for '&i' if present in specified name.
409// This to allow specifying relative names when manipulating multiple files.
410//--------------------------------------------------------------------------
411static void RelativeName(char * OutFileName, const char * NamePattern, const char * OrigName)
412{
413    // If the filename contains substring "&i", then substitute the
414    // filename for that.  This gives flexibility in terms of processing
415    // multiple files at a time.
416    char * Subst;
417    Subst = strstr(NamePattern, "&i");
418    if (Subst){
419        strncpy(OutFileName, NamePattern, Subst-NamePattern);
420        OutFileName[Subst-NamePattern] = 0;
421        strncat(OutFileName, OrigName, PATH_MAX);
422        strncat(OutFileName, Subst+2, PATH_MAX);
423    }else{
424        strcpy(OutFileName, NamePattern);
425    }
426}
427
428
429#ifdef _WIN32
430//--------------------------------------------------------------------------
431// Rename associated files
432//--------------------------------------------------------------------------
433void RenameAssociated(const char * FileName, char * NewBaseName)
434{
435    int a;
436    int PathLen;
437    int ExtPos;
438    char FilePattern[_MAX_PATH];
439    char NewName[_MAX_PATH];
440    struct _finddata_t finddata;
441    long find_handle;
442
443    for(ExtPos = strlen(FileName);FileName[ExtPos-1] != '.';){
444        if (--ExtPos == 0) return; // No extension!
445    }
446
447    memcpy(FilePattern, FileName, ExtPos);
448    FilePattern[ExtPos] = '*';
449    FilePattern[ExtPos+1] = '\0';
450
451    for(PathLen = strlen(FileName);FileName[PathLen-1] != '\\';){
452        if (--PathLen == 0) break;
453    }
454
455    find_handle = _findfirst(FilePattern, &finddata);
456
457    for (;;){
458        if (find_handle == -1) break;
459
460        // Eliminate the obvious patterns.
461        if (!memcmp(finddata.name, ".",2)) goto next_file;
462        if (!memcmp(finddata.name, "..",3)) goto next_file;
463        if (finddata.attrib & _A_SUBDIR) goto next_file;
464
465        strcpy(FilePattern+PathLen, finddata.name); // full name with path
466
467        strcpy(NewName, NewBaseName);
468        for(a = strlen(finddata.name);finddata.name[a] != '.';){
469            if (--a == 0) goto next_file;
470        }
471        strcat(NewName, finddata.name+a); // add extension to new name
472
473        if (rename(FilePattern, NewName) == 0){
474            if (!Quiet){
475                printf("%s --> %s\n",FilePattern, NewName);
476            }
477        }
478
479        next_file:
480        if (_findnext(find_handle, &finddata) != 0) break;
481    }
482    _findclose(find_handle);
483}
484#endif
485
486//--------------------------------------------------------------------------
487// Handle renaming of files by date.
488//--------------------------------------------------------------------------
489static void DoFileRenaming(const char * FileName)
490{
491    int NumAlpha = 0;
492    int NumDigit = 0;
493    int PrefixPart = 0;
494    int ExtensionPart = strlen(FileName);
495    int a;
496    struct tm tm;
497    char NewBaseName[PATH_MAX*2];
498
499    for (a=0;FileName[a];a++){
500        if (FileName[a] == '/' || FileName[a] == '\\'){
501            // Don't count path component.
502            NumAlpha = 0;
503            NumDigit = 0;
504            PrefixPart = a+1;
505        }
506
507        if (FileName[a] == '.') ExtensionPart = a;  // Remember where extension starts.
508
509        if (isalpha(FileName[a])) NumAlpha += 1;    // Tally up alpha vs. digits to judge wether to rename.
510        if (isdigit(FileName[a])) NumDigit += 1;
511    }
512
513    if (RenameToDate <= 1){
514        // If naming isn't forced, ensure name is mostly digits, or leave it alone.
515        if (NumAlpha > 8 || NumDigit < 4){
516            return;
517        }
518    }
519
520    if (!Exif2tm(&tm, ImageInfo.DateTime)){
521        printf("File '%s' contains no exif date stamp.  Using file date\n",FileName);
522        // Use file date/time instead.
523        tm = *localtime(&ImageInfo.FileDateTime);
524    }
525
526
527    strcpy(NewBaseName, FileName); // Get path component of name.
528
529    if (strftime_args){
530        // Complicated scheme for flexibility.  Just pass the args to strftime.
531        time_t UnixTime;
532
533        char *s;
534        char pattern[PATH_MAX+20];
535        int n = ExtensionPart - PrefixPart;
536
537        // Call mktime to get weekday and such filled in.
538        UnixTime = mktime(&tm);
539        if ((int)UnixTime == -1){
540            printf("Could not convert %s to unix time",ImageInfo.DateTime);
541            return;
542        }
543
544        // Substitute "%f" for the original name (minus path & extension)
545        pattern[PATH_MAX-1]=0;
546        strncpy(pattern, strftime_args, PATH_MAX-1);
547        while ((s = strstr(pattern, "%f")) && strlen(pattern) + n < PATH_MAX-1){
548            memmove(s + n, s + 2, strlen(s+2) + 1);
549            memmove(s, FileName + PrefixPart, n);
550        }
551
552        {
553            // Sequential number renaming part.
554            // '%i' type pattern becomes sequence number.
555            int ppos = -1;
556            for (a=0;pattern[a];a++){
557                if (pattern[a] == '%'){
558                     ppos = a;
559                }else if (pattern[a] == 'i'){
560                    if (ppos >= 0 && a<ppos+4){
561                        // Replace this part with a number.
562                        char pat[8];
563                        char num[16];
564                        memcpy(pat, pattern+ppos, 4);
565                        pat[a-ppos] = 'd'; // Replace 'i' with 'd' for '%d'
566                        pat[a-ppos+1] = '\0';
567                        sprintf(num, pat, FileSequence); // let printf do the number formatting.
568                        memmove(pattern+ppos+strlen(num), pattern+a+1, strlen(pattern+a+1)+1);
569                        memcpy(pattern+ppos, num, strlen(num));
570                        break;
571                    }
572                }else if (!isdigit(pattern[a])){
573                    ppos = -1;
574                }
575            }
576        }
577
578        strftime(NewBaseName+PrefixPart, PATH_MAX, pattern, &tm);
579    }else{
580        // My favourite scheme.
581        sprintf(NewBaseName+PrefixPart, "%02d%02d-%02d%02d%02d",
582             tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
583    }
584
585    for (a=0;;a++){
586        char NewName[PATH_MAX];
587        char NameExtra[3];
588        struct stat dummy;
589
590        if (a){
591            // Generate a suffix for the file name if previous choice of names is taken.
592            // depending on wether the name ends in a letter or digit, pick the opposite to separate
593            // it.  This to avoid using a separator character - this because any good separator
594            // is before the '.' in ascii, and so sorting the names would put the later name before
595            // the name without suffix, causing the pictures to more likely be out of order.
596            if (isdigit(NewBaseName[strlen(NewBaseName)-1])){
597                NameExtra[0] = (char)('a'-1+a); // Try a,b,c,d... for suffix if it ends in a letter.
598            }else{
599                NameExtra[0] = (char)('0'-1+a); // Try 1,2,3,4... for suffix if it ends in a char.
600            }
601            NameExtra[1] = 0;
602        }else{
603            NameExtra[0] = 0;
604        }
605
606        sprintf(NewName, "%s%s.jpg", NewBaseName, NameExtra);
607
608        if (!strcmp(FileName, NewName)) break; // Skip if its already this name.
609
610        if (stat(NewName, &dummy)){
611            // This name does not pre-exist.
612            if (rename(FileName, NewName) == 0){
613                printf("%s --> %s\n",FileName, NewName);
614#ifdef _WIN32
615                if (RenameAssociatedFiles){
616                    sprintf(NewName, "%s%s", NewBaseName, NameExtra);
617                    RenameAssociated(FileName, NewName);
618                }
619#endif
620            }else{
621                printf("Error: Couldn't rename '%s' to '%s'\n",FileName, NewName);
622            }
623            break;
624        }
625
626        if (a >= 9){
627            printf("Possible new names for for '%s' already exist\n",FileName);
628            break;
629        }
630    }
631}
632
633//--------------------------------------------------------------------------
634// Rotate the image and its thumbnail
635//--------------------------------------------------------------------------
636static int DoAutoRotate(const char * FileName)
637{
638    if (ImageInfo.Orientation >= 2 && ImageInfo.Orientation <= 8){
639        const char * Argument;
640        Argument = ClearOrientation();
641
642        if (!ZeroRotateTagOnly){
643            char RotateCommand[PATH_MAX*2+50];
644            if (Argument == NULL){
645                ErrFatal("Orientation screwup");
646            }
647
648            sprintf(RotateCommand, "jpegtran -%s -outfile &o &i", Argument);
649            ApplyCommand = RotateCommand;
650            DoCommand(FileName, FALSE);
651            ApplyCommand = NULL;
652
653            // Now rotate the thumbnail, if there is one.
654            if (ImageInfo.ThumbnailOffset &&
655                ImageInfo.ThumbnailSize &&
656                ImageInfo.ThumbnailAtEnd){
657                // Must have a thumbnail that exists and is modifieable.
658
659                char ThumbTempName_in[PATH_MAX+4];
660                char ThumbTempName_out[PATH_MAX+4];
661
662                strcpy(ThumbTempName_in, FileName);
663                strcat(ThumbTempName_in, ".thi");
664                strcpy(ThumbTempName_out, FileName);
665                strcat(ThumbTempName_out, ".tho");
666                SaveThumbnail(ThumbTempName_in);
667                sprintf(RotateCommand,"jpegtran -%s -outfile \"%s\" \"%s\"",
668                    Argument, ThumbTempName_out, ThumbTempName_in);
669
670                if (system(RotateCommand) == 0){
671                    // Put the thumbnail back in the header
672                    ReplaceThumbnail(ThumbTempName_out);
673                }
674
675                unlink(ThumbTempName_in);
676                unlink(ThumbTempName_out);
677            }
678        }
679        return TRUE;
680    }
681    return FALSE;
682}
683
684//--------------------------------------------------------------------------
685// Regenerate the thumbnail using mogrify
686//--------------------------------------------------------------------------
687static int RegenerateThumbnail(const char * FileName)
688{
689    char ThumbnailGenCommand[PATH_MAX*2+50];
690    if (ImageInfo.ThumbnailOffset == 0 || ImageInfo.ThumbnailAtEnd == FALSE){
691        // There is no thumbnail, or the thumbnail is not at the end.
692        return FALSE;
693    }
694
695    sprintf(ThumbnailGenCommand, "mogrify -thumbnail %dx%d \"%s\"",
696        RegenThumbnail, RegenThumbnail, FileName);
697
698    if (system(ThumbnailGenCommand) == 0){
699        // Put the thumbnail back in the header
700        return ReplaceThumbnail(FileName);
701    }else{
702        ErrFatal("Unable to run 'mogrify' command");
703        return FALSE;
704    }
705}
706
707//--------------------------------------------------------------------------
708// Do selected operations to one file at a time.
709//--------------------------------------------------------------------------
710void ProcessFile(const char * FileName)
711{
712    int Modified = FALSE;
713    ReadMode_t ReadMode = READ_METADATA;
714    CurrentFile = FileName;
715    FilesMatched = 1;
716
717    ResetJpgfile();
718
719    // Start with an empty image information structure.
720    memset(&ImageInfo, 0, sizeof(ImageInfo));
721    ImageInfo.FlashUsed = -1;
722    ImageInfo.MeteringMode = -1;
723    ImageInfo.Whitebalance = -1;
724
725    // Store file date/time.
726    {
727        struct stat st;
728        if (stat(FileName, &st) >= 0){
729            ImageInfo.FileDateTime = st.st_mtime;
730            ImageInfo.FileSize = st.st_size;
731        }else{
732            ErrFatal("No such file");
733        }
734    }
735
736    if (DoModify || RenameToDate || Exif2FileTime){
737        if (access(FileName, 2 /*W_OK*/)){
738            printf("Skipping readonly file '%s'\n",FileName);
739            return;
740        }
741    }
742
743    strncpy(ImageInfo.FileName, FileName, PATH_MAX);
744
745
746    if (ApplyCommand || AutoRotate){
747        // Applying a command is special - the headers from the file have to be
748        // pre-read, then the command executed, and then the image part of the file read.
749
750        if (!ReadJpegFile(FileName, READ_METADATA)) return;
751
752        #ifdef MATTHIAS
753            if (AutoResize){
754                // Automatic resize computation - to customize for each run...
755                if (AutoResizeCmdStuff() == 0){
756                    DiscardData();
757                    return;
758                }
759            }
760        #endif // MATTHIAS
761
762
763        if (CheckFileSkip()){
764            DiscardData();
765            return;
766        }
767
768        DiscardAllButExif();
769
770        if (AutoRotate){
771            if (DoAutoRotate(FileName)){
772                Modified = TRUE;
773            }
774        }else{
775            struct stat dummy;
776            DoCommand(FileName, Quiet ? FALSE : TRUE);
777
778            if (stat(FileName, &dummy)){
779                // The file is not there anymore. Perhaps the command
780                // was a delete or a move.  So we are all done.
781                return;
782            }
783            Modified = TRUE;
784        }
785        ReadMode = READ_IMAGE;   // Don't re-read exif section again on next read.
786
787    }else if (ExifXferScrFile){
788        char RelativeExifName[PATH_MAX+1];
789
790        // Make a relative name.
791        RelativeName(RelativeExifName, ExifXferScrFile, FileName);
792
793        if(!ReadJpegFile(RelativeExifName, READ_METADATA)) return;
794
795        DiscardAllButExif();    // Don't re-read exif section again on next read.
796
797        Modified = TRUE;
798        ReadMode = READ_IMAGE;
799    }
800
801    if (DoModify){
802        ReadMode |= READ_IMAGE;
803    }
804
805    if (!ReadJpegFile(FileName, ReadMode)) return;
806
807    if (CheckFileSkip()){
808        DiscardData();
809        return;
810    }
811
812    FileSequence += 1; // Count files processed.
813
814    if (ShowConcise){
815        ShowConciseImageInfo();
816    }else{
817        if (!(DoModify || DoReadAction) || ShowTags){
818            ShowImageInfo(ShowFileInfo);
819
820            {
821                // if IPTC section is present, show it also.
822                Section_t * IptcSection;
823                IptcSection = FindSection(M_IPTC);
824
825                if (IptcSection){
826                    show_IPTC(IptcSection->Data, IptcSection->Size);
827                }
828            }
829            printf("\n");
830        }
831    }
832
833    if (ThumbSaveName){
834        char OutFileName[PATH_MAX+1];
835        // Make a relative name.
836        RelativeName(OutFileName, ThumbSaveName, FileName);
837
838        if (SaveThumbnail(OutFileName)){
839            printf("Created: '%s'\n", OutFileName);
840        }
841    }
842
843    if (CreateExifSection){
844        // Make a new minimal exif section
845        create_EXIF(NULL, 0, 0);
846        Modified = TRUE;
847    }
848
849    if (RegenThumbnail){
850        if (RegenerateThumbnail(FileName)){
851            Modified = TRUE;
852        }
853    }
854
855    if (ThumbInsertName){
856        char ThumbFileName[PATH_MAX+1];
857        // Make a relative name.
858        RelativeName(ThumbFileName, ThumbInsertName, FileName);
859
860        if (ReplaceThumbnail(ThumbFileName)){
861            Modified = TRUE;
862        }
863    }else if (TrimExif){
864        // Deleting thumbnail is just replacing it with a null thumbnail.
865        if (ReplaceThumbnail(NULL)){
866            Modified = TRUE;
867        }
868    }
869
870    if (
871#ifdef MATTHIAS
872        AddComment || RemComment ||
873#endif
874                   EditComment || CommentInsertfileName || CommentInsertLiteral){
875
876        Section_t * CommentSec;
877        char Comment[1001];
878        int CommentSize;
879
880        CommentSec = FindSection(M_COM);
881
882        if (CommentSec == NULL){
883            unsigned char * DummyData;
884
885            DummyData = (uchar *) malloc(3);
886            DummyData[0] = 0;
887            DummyData[1] = 2;
888            DummyData[2] = 0;
889            CommentSec = CreateSection(M_COM, DummyData, 2);
890        }
891
892        CommentSize = CommentSec->Size-2;
893        if (CommentSize > 1000){
894            fprintf(stderr, "Truncating comment at 1000 chars\n");
895            CommentSize = 1000;
896        }
897
898        if (CommentInsertfileName){
899            // Read a new comment section from file.
900            char CommentFileName[PATH_MAX+1];
901            FILE * CommentFile;
902
903            // Make a relative name.
904            RelativeName(CommentFileName, CommentInsertfileName, FileName);
905
906            CommentFile = fopen(CommentFileName,"r");
907            if (CommentFile == NULL){
908                printf("Could not open '%s'\n",CommentFileName);
909            }else{
910                // Read it in.
911                // Replace the section.
912                CommentSize = fread(Comment, 1, 999, CommentFile);
913                fclose(CommentFile);
914                if (CommentSize < 0) CommentSize = 0;
915            }
916        }else if (CommentInsertLiteral){
917            strncpy(Comment, CommentInsertLiteral, 1000);
918            CommentSize = strlen(Comment);
919        }else{
920#ifdef MATTHIAS
921            char CommentZt[1001];
922            memcpy(CommentZt, (char *)CommentSec->Data+2, CommentSize);
923            CommentZt[CommentSize] = '\0';
924            if (ModifyDescriptComment(Comment, CommentZt)){
925                Modified = TRUE;
926                CommentSize = strlen(Comment);
927            }
928            if (EditComment)
929#else
930            memcpy(Comment, (char *)CommentSec->Data+2, CommentSize);
931#endif
932            {
933                char EditFileName[PATH_MAX+4];
934                strcpy(EditFileName, FileName);
935                strcat(EditFileName, ".txt");
936
937                CommentSize = FileEditComment(EditFileName, Comment, CommentSize);
938            }
939        }
940
941        if (strcmp(Comment, (char *)CommentSec->Data+2)){
942            // Discard old comment section and put a new one in.
943            int size;
944            size = CommentSize+2;
945            free(CommentSec->Data);
946            CommentSec->Size = size;
947            CommentSec->Data = malloc(size);
948            CommentSec->Data[0] = (uchar)(size >> 8);
949            CommentSec->Data[1] = (uchar)(size);
950            memcpy((CommentSec->Data)+2, Comment, size-2);
951            Modified = TRUE;
952        }
953        if (!Modified){
954            printf("Comment not modified\n");
955        }
956    }
957
958
959    if (CommentSavefileName){
960        Section_t * CommentSec;
961        CommentSec = FindSection(M_COM);
962
963        if (CommentSec != NULL){
964            char OutFileName[PATH_MAX+1];
965            FILE * CommentFile;
966
967            // Make a relative name.
968            RelativeName(OutFileName, CommentSavefileName, FileName);
969
970            CommentFile = fopen(OutFileName,"w");
971
972            if (CommentFile){
973                fwrite((char *)CommentSec->Data+2, CommentSec->Size-2 ,1, CommentFile);
974                fclose(CommentFile);
975            }else{
976                ErrFatal("Could not write comment file");
977            }
978        }else{
979            printf("File '%s' contains no comment section\n",FileName);
980        }
981    }
982
983    if (ExifTimeAdjust || ExifTimeSet || DateSetChars || FileTimeToExif){
984       if (ImageInfo.numDateTimeTags){
985            struct tm tm;
986            time_t UnixTime;
987            char TempBuf[50];
988            int a;
989            Section_t * ExifSection;
990            if (ExifTimeSet){
991                // A time to set was specified.
992                UnixTime = ExifTimeSet;
993            }else{
994                if (FileTimeToExif){
995                    FileTimeAsString(ImageInfo.DateTime);
996                }
997                if (DateSetChars){
998                    memcpy(ImageInfo.DateTime, DateSet, DateSetChars);
999                    a = 1970;
1000                    sscanf(DateSet, "%d", &a);
1001                    if (a < 1970){
1002                        strcpy(TempBuf, ImageInfo.DateTime);
1003                        goto skip_unixtime;
1004                    }
1005                }
1006                // A time offset to adjust by was specified.
1007                if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
1008
1009                // Convert to unix 32 bit time value, add offset, and convert back.
1010                UnixTime = mktime(&tm);
1011                if ((int)UnixTime == -1) goto badtime;
1012                UnixTime += ExifTimeAdjust;
1013            }
1014            tm = *localtime(&UnixTime);
1015
1016            // Print to temp buffer first to avoid putting null termination in destination.
1017            // snprintf() would do the trick, hbut not available everywhere (like FreeBSD 4.4)
1018            sprintf(TempBuf, "%04d:%02d:%02d %02d:%02d:%02d",
1019                tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
1020                tm.tm_hour, tm.tm_min, tm.tm_sec);
1021
1022skip_unixtime:
1023            ExifSection = FindSection(M_EXIF);
1024
1025            for (a = 0; a < ImageInfo.numDateTimeTags; a++) {
1026                uchar * Pointer;
1027                Pointer = ExifSection->Data+ImageInfo.DateTimeOffsets[a]+8;
1028                memcpy(Pointer, TempBuf, 19);
1029            }
1030
1031            Modified = TRUE;
1032        }else{
1033            printf("File '%s' contains no Exif timestamp to change\n", FileName);
1034        }
1035    }
1036
1037    if (DeleteComments){
1038        if (RemoveSectionType(M_COM)) Modified = TRUE;
1039    }
1040    if (DeleteExif){
1041        if (RemoveSectionType(M_EXIF)) Modified = TRUE;
1042    }
1043    if (DeleteIptc){
1044        if (RemoveSectionType(M_IPTC)) Modified = TRUE;
1045    }
1046    if (DeleteUnknown){
1047        if (RemoveUnknownSections()) Modified = TRUE;
1048    }
1049
1050
1051    if (Modified){
1052        char BackupName[400];
1053        struct stat buf;
1054
1055        if (!Quiet) printf("Modified: %s\n",FileName);
1056
1057        strcpy(BackupName, FileName);
1058        strcat(BackupName, ".t");
1059
1060        // Remove any .old file name that may pre-exist
1061        unlink(BackupName);
1062
1063        // Rename the old file.
1064        rename(FileName, BackupName);
1065
1066        // Write the new file.
1067        if (WriteJpegFile(FileName)) {
1068
1069            // Copy the access rights from original file
1070            if (stat(BackupName, &buf) == 0){
1071                // set Unix access rights and time to new file
1072                struct utimbuf mtime;
1073                chmod(FileName, buf.st_mode);
1074
1075                mtime.actime = buf.st_mtime;
1076                mtime.modtime = buf.st_mtime;
1077
1078                utime(FileName, &mtime);
1079            }
1080
1081            // Now that we are done, remove original file.
1082            unlink(BackupName);
1083        } else {
1084            // move back the backup file
1085            rename(BackupName, FileName);
1086        }
1087    }
1088
1089
1090    if (Exif2FileTime){
1091        // Set the file date to the date from the exif header.
1092        if (ImageInfo.numDateTimeTags){
1093            // Converte the file date to Unix time.
1094            struct tm tm;
1095            time_t UnixTime;
1096            struct utimbuf mtime;
1097            if (!Exif2tm(&tm, ImageInfo.DateTime)) goto badtime;
1098
1099            UnixTime = mktime(&tm);
1100            if ((int)UnixTime == -1){
1101                goto badtime;
1102            }
1103
1104            mtime.actime = UnixTime;
1105            mtime.modtime = UnixTime;
1106
1107            if (utime(FileName, &mtime) != 0){
1108                printf("Error: Could not change time of file '%s'\n",FileName);
1109            }else{
1110                if (!Quiet) printf("%s\n",FileName);
1111            }
1112        }else{
1113            printf("File '%s' contains no Exif timestamp\n", FileName);
1114        }
1115    }
1116
1117    // Feature to rename image according to date and time from camera.
1118    // I use this feature to put images from multiple digicams in sequence.
1119
1120    if (RenameToDate){
1121        DoFileRenaming(FileName);
1122    }
1123    DiscardData();
1124    return;
1125badtime:
1126    printf("Error: Time '%s': cannot convert to Unix time\n",ImageInfo.DateTime);
1127    DiscardData();
1128}
1129
1130//--------------------------------------------------------------------------
1131// complain about bad state of the command line.
1132//--------------------------------------------------------------------------
1133static void Usage (void)
1134{
1135    printf("Jhead is a program for manipulating settings and thumnails in Exif jpeg headers\n"
1136           "used by most Digital Cameras.  v"JHEAD_VERSION" Matthias Wandel, April 29 2006.\n"
1137           "http://www.sentex.net/~mwandel/jhead\n"
1138           "\n");
1139
1140    printf("Usage: %s [options] files\n", progname);
1141    printf("Where:\n"
1142           " files       path/filenames with or without wildcards\n"
1143
1144           "[options] are:\n"
1145           "\nGENERAL METADATA:\n"
1146           "  -te <name> Transfer exif header from another image file <name>\n"
1147           "             Uses same name mangling as '-st' option\n"
1148           "  -dc        Delete comment field (as left by progs like Photoshop & Compupic)\n"
1149           "  -de        Strip Exif section (smaller JPEG file, but lose digicam info)\n"
1150           "  -di        Delete IPTC section (from Photoshop, or Picasa)\n"
1151           "  -du        Delete non image sections except for Exif and comment sections\n"
1152           "  -purejpg   Strip all unnecessary data from jpeg (combines -dc -de and -du)\n"
1153           "  -mkexif    Create new minimal exif section (overwrites pre-existing exif)\n"
1154           "  -ce        Edit comment field.  Uses environment variable 'editor' to\n"
1155           "             determine which editor to use.  If editor not set, uses VI\n"
1156           "             under Unix and notepad with windows\n"
1157           "  -cs <name> Save comment section to a file\n"
1158           "  -ci <name> Insert comment section from a file.  -cs and -ci use same naming\n"
1159           "             scheme as used by the -st option\n"
1160           "  -cl string Insert literal comment string\n"
1161
1162           "\nDATE / TIME MANIPULATION:\n"
1163           "  -ft        Set file modification time to Exif time\n"
1164           "  -dsft      Set Exif time to file modification time\n"
1165           "  -n[format-string]\n"
1166           "             Rename files according to date.  Uses exif date if present, file\n"
1167           "             date otherwise.  If the optional format-string is not supplied,\n"
1168           "             the format is mmdd-hhmmss.  If a format-string is given, it is\n"
1169           "             is passed to the 'strftime' function for formatting\n"
1170           "             In addition to strftime format codes:\n"
1171           "             '%%f' as part of the string will include the original file name\n"
1172           "             '%%i' will include a sequence number, starting from 1. You can\n"
1173           "             You can specify '%%03i' for example to get leading zeros.\n"
1174           "             This feature is useful for ordering files from multiple digicams to\n"
1175           "             sequence of taking.  Only renames files whose names are mostly\n"
1176           "             numerical (as assigned by digicam)\n"
1177           "             The '.jpg' is automatically added to the end of the name.  If the\n"
1178           "             destination name already exists, a letter or digit is added to \n"
1179           "             the end of the name to make it unique.\n"
1180           "  -nf[format-string]\n"
1181           "             Same as -n, but rename regardless of original name\n"
1182           "  -a         (Windows only) Rename files with same name but different extension\n"
1183           "             Use together with -n to rename .AVI files from exif in .THM files\n"
1184           "             for example\n"
1185           "  -ta<+|->h[:mm[:ss]]\n"
1186           "             Adjust time by h:mm backwards or forwards.  Useful when having\n"
1187           "             taken pictures with the wrong time set on the camera, such as when\n"
1188           "             traveling across time zones or DST changes. Dates can be adjusted\n"
1189           "             by offsetting by 24 hours or more.  For large date adjustments,\n"
1190           "             use the -da option\n"
1191           "  -da<date>-<date>\n"
1192           "             Adjust date by large amounts.  This is used to fix photos from\n"
1193           "             cameras where the date got set back to the default camera date\n"
1194           "             by accident or battery removal.\n"
1195           "             To deal with different months and years having different numbers of\n"
1196           "             days, a simple date-month-year offset would result in unexpected\n"
1197           "             results.  Instead, the difference is specified as desired date\n"
1198           "             minus original date.  Date is specified as yyyy:mmm:dd or as date\n"
1199           "             and time in the format yyyy:mmm:dd/hh:mm:ss\n"
1200           "  -ts<time>  Set the Exif internal time to <time>.  <time> is in the format\n"
1201           "             yyyy:mm:dd-hh:mm:ss\n"
1202           "  -ds<date>  Set the Exif internal date.  <date> is in the format YYYY:MM:DD\n"
1203           "             or YYYY:MM or YYYY\n"
1204
1205           "\nTHUMBNAIL MANIPULATION:\n"
1206           "  -dt        Remove exif integral thumbnails.   Typically trims 10k\n"
1207           "  -st <name> Save Exif thumbnail, if there is one, in file <name>\n"
1208           "             If output file name contains the substring \"&i\" then the\n"
1209           "             image file name is substitute for the &i.  Note that quotes around\n"
1210           "             the argument are required for the '&' to be passed to the program.\n"
1211#ifndef _WIN32
1212           "             An output name of '-' causes thumbnail to be written to stdout\n"
1213#endif
1214           "  -rt <name> Replace Exif thumbnail.  Can only be done with headers that\n"
1215           "             already contain a thumbnail.\n"
1216           "  -rgt[size] Regnerate exif thumbnail.  Only works if image already\n"
1217           "             contains a thumbail.  size specifies maximum height or width of\n"
1218           "             thumbnail.  Relies on 'mogrify' programs to be on path\n"
1219
1220           "\nROTATION TAG MANIPULATION:\n"
1221           "  -autorot   Invoke jpegtran to rotate images according to Exif orientation tag\n"
1222           "             Note: Windows users must get jpegtran for this to work\n"
1223           "  -norot     Zero out the rotation tag.  This to avoid some browsers from\n"
1224           "             rotating the image again after you rotated it but neglected to\n"
1225           "             clear the rotation tag\n"
1226
1227           "\nOUTPUT VERBOSITY CONTROL:\n"
1228           "  -h         help (this text)\n"
1229           "  -v         even more verbose output\n"
1230           "  -q         Quiet (no messages on success, like Unix)\n"
1231           "  -V         Show jhead version\n"
1232           "  -exifmap   Dump header bytes, annotate.  Pipe thru sort for better viewing\n"
1233           "  -se        Supress error messages relating to corrupt exif header structure\n"
1234           "  -c         concise output\n"
1235           "  -nofinfo   Don't show file info (name/size/date)\n"
1236
1237           "\nFILE MATCHING AND SELECTION:\n"
1238           "  -model model\n"
1239           "             Only process files from digicam containing model substring in\n"
1240           "             camera model description\n"
1241           "  -exonly    Skip all files that don't have an exif header (skip all jpegs that\n"
1242           "             were not created by digicam)\n"
1243           "  -cmd command\n"
1244           "             Apply 'command' to every file, then re-insert exif and command\n"
1245           "             sections into the image. &i will be substituted for the input file\n"
1246           "             name, and &o (if &o is used). Use quotes around the command string\n"
1247           "             This is most useful in conjunction with the free ImageMagick tool. \n"
1248           "             For example, with my Canon S100, which suboptimally compresses\n"
1249           "             jpegs I can specify\n"
1250           "                jhead -cmd \"mogrify -quality 80 &i\" *.jpg\n"
1251           "             to re-compress a lot of images using ImageMagick to half the size,\n"
1252           "             and no visible loss of quality while keeping the exif header\n"
1253           "             Another invocation I like to use is jpegtran (hard to find for\n"
1254           "             windows).  I type:\n"
1255           "                jhead -cmd \"jpegtran -progressive &i &o\" *.jpg\n"
1256           "             to convert jpegs to progressive jpegs (Unix jpegtran syntax\n"
1257           "             differs slightly)\n"
1258           "  -orp       Only operate on 'portrait' aspect ratio images\n"
1259           "  -orl       Only operate on 'landscape' aspect ratio images\n"
1260#ifdef _WIN32
1261           "  -r         No longer supported.  Use the ** wildcard to recurse directories\n"
1262           "             with instead.\n"
1263           "             examples:\n"
1264           "                 jhead **/*.jpg\n"
1265           "                 jhead \"c:\\my photos\\**\\*.jpg\"\n"
1266#endif
1267
1268
1269#ifdef MATTHIAS
1270           "\n"
1271           "  -cr        Remove comment tag (my way)\n"
1272           "  -ca        Add comment tag (my way)\n"
1273           "  -ar        Auto resize to fit in 1024x1024, but never less than half\n"
1274#endif //MATTHIAS
1275
1276
1277           );
1278
1279    exit(EXIT_FAILURE);
1280}
1281
1282
1283//--------------------------------------------------------------------------
1284// Parse specified date or date+time from command line.
1285//--------------------------------------------------------------------------
1286time_t ParseCmdDate(char * DateSpecified)
1287{
1288    int a;
1289    struct tm tm;
1290    time_t UnixTime;
1291
1292    tm.tm_wday = -1;
1293    tm.tm_hour = tm.tm_min = tm.tm_sec = 0;
1294
1295    a = sscanf(DateSpecified, "%d:%d:%d/%d:%d:%d",
1296            &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
1297            &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
1298
1299    if (a != 3 && a < 5){
1300        // Date must be YYYY:MM:DD, YYYY:MM:DD+HH:MM
1301        // or YYYY:MM:DD+HH:MM:SS
1302        ErrFatal("Could not parse specified date");
1303    }
1304    tm.tm_isdst = -1;
1305    tm.tm_mon -= 1;      // Adjust for unix zero-based months
1306    tm.tm_year -= 1900;  // Adjust for year starting at 1900
1307
1308    UnixTime = mktime(&tm);
1309    if (UnixTime == -1){
1310        ErrFatal("Specified time is invalid or out of range");
1311    }
1312
1313    return UnixTime;
1314}
1315
1316//--------------------------------------------------------------------------
1317// The main program.
1318//--------------------------------------------------------------------------
1319#if 0
1320int main (int argc, char **argv)
1321{
1322    int argn;
1323    char * arg;
1324    progname = argv[0];
1325
1326    for (argn=1;argn<argc;argn++){
1327        arg = argv[argn];
1328        if (arg[0] != '-') break; // Filenames from here on.
1329
1330    // General metadata options:
1331        if (!strcmp(arg,"-te")){
1332            ExifXferScrFile = argv[++argn];
1333            DoModify = TRUE;
1334        }else if (!strcmp(arg,"-dc")){
1335            DeleteComments = TRUE;
1336            DoModify = TRUE;
1337        }else if (!strcmp(arg,"-de")){
1338            DeleteExif = TRUE;
1339            DoModify = TRUE;
1340        }else if (!strcmp(arg,"-di")){
1341            DeleteIptc = TRUE;
1342            DoModify = TRUE;
1343        }else if (!strcmp(arg, "-du")){
1344            DeleteUnknown = TRUE;
1345            DoModify = TRUE;
1346        }else if (!strcmp(arg, "-purejpg")){
1347            DeleteExif = TRUE;
1348            DeleteComments = TRUE;
1349            DeleteIptc = TRUE;
1350            DeleteUnknown = TRUE;
1351            DoModify = TRUE;
1352        }else if (!strcmp(arg,"-ce")){
1353            EditComment = TRUE;
1354            DoModify = TRUE;
1355        }else if (!strcmp(arg,"-cs")){
1356            CommentSavefileName = argv[++argn];
1357        }else if (!strcmp(arg,"-ci")){
1358            CommentInsertfileName = argv[++argn];
1359            DoModify = TRUE;
1360        }else if (!strcmp(arg,"-cl")){
1361            CommentInsertLiteral = argv[++argn];
1362            DoModify = TRUE;
1363        }else if (!strcmp(arg,"-mkexif")){
1364            CreateExifSection = TRUE;
1365            DoModify = TRUE;
1366
1367    // Output verbosity control
1368        }else if (!strcmp(arg,"-h")){
1369            Usage();
1370        }else if (!strcmp(arg,"-v")){
1371            ShowTags = TRUE;
1372        }else if (!strcmp(arg,"-q")){
1373            Quiet = TRUE;
1374        }else if (!strcmp(arg,"-V")){
1375            printf("Jhead version: "JHEAD_VERSION"   Compiled: "__DATE__"\n");
1376            exit(0);
1377        }else if (!strcmp(arg,"-exifmap")){
1378            DumpExifMap = TRUE;
1379        }else if (!strcmp(arg,"-se")){
1380            SupressNonFatalErrors = TRUE;
1381        }else if (!strcmp(arg,"-c")){
1382            ShowConcise = TRUE;
1383        }else if (!strcmp(arg,"-nofinfo")){
1384            ShowFileInfo = 0;
1385
1386    // Thumbnail manipulation options
1387        }else if (!strcmp(arg,"-dt")){
1388            TrimExif = TRUE;
1389            DoModify = TRUE;
1390        }else if (!strcmp(arg,"-st")){
1391            ThumbSaveName = argv[++argn];
1392            DoReadAction = TRUE;
1393        }else if (!strcmp(arg,"-rt")){
1394            ThumbInsertName = argv[++argn];
1395            DoModify = TRUE;
1396        }else if (!memcmp(arg,"-rgt", 4)){
1397            RegenThumbnail = 160;
1398            sscanf(arg+4, "%d", &RegenThumbnail);
1399            if (RegenThumbnail > 320){
1400                ErrFatal("Specified thumbnail geometry too big!");
1401            }
1402            DoModify = TRUE;
1403
1404    // Rotation tag manipulation
1405        }else if (!strcmp(arg,"-autorot")){
1406            AutoRotate = 1;
1407            DoModify = TRUE;
1408        }else if (!strcmp(arg,"-norot")){
1409            AutoRotate = 1;
1410            ZeroRotateTagOnly = 1;
1411            DoModify = TRUE;
1412
1413    // Date/Time manipulation options
1414        }else if (!memcmp(arg,"-n",2)){
1415            RenameToDate = 1;
1416            DoReadAction = TRUE; // Rename doesn't modify file, so count as read action.
1417            arg+=2;
1418            if (*arg == 'f'){
1419                RenameToDate = 2;
1420                arg++;
1421            }
1422            if (*arg){
1423                // A strftime format string is supplied.
1424                strftime_args = arg;
1425                //printf("strftime_args = %s\n",arg);
1426            }
1427        }else if (!strcmp(arg,"-a")){
1428            RenameAssociatedFiles = TRUE;
1429            #ifndef _WIN32
1430                ErrFatal("Error: -a only supported in Windows version");
1431            #endif
1432        }else if (!strcmp(arg,"-ft")){
1433            Exif2FileTime = TRUE;
1434            DoReadAction = TRUE;
1435        }else if (!memcmp(arg,"-ta",3)){
1436            // Time adjust feature.
1437            int hours, minutes, seconds, n;
1438            minutes = seconds = 0;
1439            if (arg[3] != '-' && arg[3] != '+'){
1440                ErrFatal("Error: -ta must be followed by +/- and a time");
1441            }
1442            n = sscanf(arg+4, "%d:%d:%d", &hours, &minutes, &seconds);
1443
1444            if (n < 1){
1445                ErrFatal("Error: -ta must be immediately followed by time");
1446            }
1447            if (ExifTimeAdjust) ErrFatal("Can only use one of -da or -ta options at once");
1448            ExifTimeAdjust = hours*3600 + minutes*60 + seconds;
1449            if (arg[3] == '-') ExifTimeAdjust = -ExifTimeAdjust;
1450            DoModify = TRUE;
1451        }else if (!memcmp(arg,"-da",3)){
1452            // Date adjust feature (large time adjustments)
1453            time_t NewDate, OldDate = 0;
1454            char * pOldDate;
1455            NewDate = ParseCmdDate(arg+3);
1456            pOldDate = strstr(arg+1, "-");
1457            if (pOldDate){
1458                OldDate = ParseCmdDate(pOldDate+1);
1459            }else{
1460                ErrFatal("Must specifiy second date for -da option");
1461            }
1462            if (ExifTimeAdjust) ErrFatal("Can only use one of -da or -ta options at once");
1463            ExifTimeAdjust = NewDate-OldDate;
1464            DoModify = TRUE;
1465        }else if (!memcmp(arg,"-dsft",5)){
1466            // Set file time to date/time in exif
1467            FileTimeToExif = TRUE;
1468            DoModify = TRUE;
1469        }else if (!memcmp(arg,"-ds",3)){
1470            // Set date feature
1471            int a;
1472            // Check date validity and copy it.  Could be incompletely specified.
1473            strcpy(DateSet, "0000:01:01");
1474            for (a=0;arg[a+3];a++){
1475                if (isdigit(DateSet[a])){
1476                    if (!isdigit(arg[a+3])){
1477                        a = 0;
1478                        break;
1479                    }
1480                }else{
1481                    if (arg[a+3] != ':'){
1482                        a=0;
1483                        break;
1484                    }
1485                }
1486                DateSet[a] = arg[a+3];
1487            }
1488            if (a < 4 || a > 10){
1489                ErrFatal("Date must be in format YYYY, YYYY:MM, or YYYY:MM:DD");
1490            }
1491            DateSetChars = a;
1492            DoModify = TRUE;
1493        }else if (!memcmp(arg,"-ts",3)){
1494            // Set the exif time.
1495            // Time must be specified as "yyyy:mm:dd-hh:mm:ss"
1496            char * c;
1497            struct tm tm;
1498
1499            c = strstr(arg+1, "-");
1500            if (c) *c = ' '; // Replace '-' with a space.
1501
1502            if (!Exif2tm(&tm, arg+3)){
1503                ErrFatal("-ts option must be followed by time in format yyyy:mmm:dd-hh:mm:ss\n"
1504                        "Example: jhead -ts2001:01:01-12:00:00 foo.jpg");
1505            }
1506
1507            ExifTimeSet  = mktime(&tm);
1508
1509            if ((int)ExifTimeSet == -1) ErrFatal("Time specified is out of range");
1510            DoModify = TRUE;
1511
1512    // File matching and selection
1513        }else if (!strcmp(arg,"-model")){
1514            if (argn+1 >= argc) Usage(); // No extra argument.
1515            FilterModel = argv[++argn];
1516        }else if (!strcmp(arg,"-exonly")){
1517            ExifOnly = 1;
1518        }else if (!strcmp(arg,"-orp")){
1519            PortraitOnly = 1;
1520        }else if (!strcmp(arg,"-orl")){
1521            PortraitOnly = -1;
1522        }else if (!strcmp(arg,"-cmd")){
1523            if (argn+1 >= argc) Usage(); // No extra argument.
1524            ApplyCommand = argv[++argn];
1525            DoModify = TRUE;
1526
1527#ifdef MATTHIAS
1528        }else if (!strcmp(arg,"-ca")){
1529            // Its a literal comment.  Add.
1530            AddComment = argv[++argn];
1531            DoModify = TRUE;
1532        }else if (!strcmp(arg,"-cr")){
1533            // Its a literal comment.  Remove this keyword.
1534            RemComment = argv[++argn];
1535            DoModify = TRUE;
1536        }else if (!strcmp(arg,"-ar")){
1537            AutoResize = TRUE;
1538            ShowConcise = TRUE;
1539            ApplyCommand = (char *)1; // Must be non null so it does commands.
1540            DoModify = TRUE;
1541#endif // MATTHIAS
1542        }else{
1543            printf("Argument '%s' not understood\n",arg);
1544            printf("Use jhead -h for list of arguments\n");
1545            exit(-1);
1546        }
1547        if (argn >= argc){
1548            // Used an extra argument - becuase the last argument
1549            // used up an extr argument.
1550            ErrFatal("Extra argument required");
1551        }
1552    }
1553    if (argn == argc){
1554        ErrFatal("No files to process.  Use -h for help");
1555    }
1556
1557    if (ThumbSaveName != NULL && strcmp(ThumbSaveName, "&i") == 0){
1558        printf("Error: By specifying \"&i\" for the thumbail name, your original file\n"
1559               "       will be overwitten.  If this is what you really want,\n"
1560               "       specify  -st \"./&i\"  to override this check\n");
1561        exit(0);
1562    }
1563
1564    if (RegenThumbnail){
1565        if (ThumbSaveName || ThumbInsertName){
1566            printf("Error: Cannot regen and save or insert thumbnail in same run\n");
1567            exit(0);
1568        }
1569    }
1570
1571    if (EditComment){
1572        if (CommentSavefileName != NULL || CommentInsertfileName != NULL){
1573            printf("Error: Cannot use -ce option in combination with -cs or -ci\n");
1574            exit(0);
1575        }
1576    }
1577
1578
1579    if (ExifXferScrFile){
1580        if (FilterModel || ApplyCommand){
1581            ErrFatal("Error: Filter by model and/or applying command to files\n"
1582            "   invalid while transfering Exif headers");
1583        }
1584    }
1585
1586    FileSequence = 0;
1587    for (;argn<argc;argn++){
1588        FilesMatched = FALSE;
1589
1590        #ifdef _WIN32
1591            {
1592                int a;
1593                for (a=0;;a++){
1594                    if (argv[argn][a] == '\0') break;
1595                    if (argv[argn][a] == '/') argv[argn][a] = '\\';
1596                }
1597            }
1598            // Use my globbing module to do fancier wildcard expansion with recursive
1599            // subdirectories under Windows.
1600            MyGlob(argv[argn], ProcessFile);
1601        #else
1602            // Under linux, don't do any extra fancy globbing - shell globbing is
1603            // pretty fancy as it is - although not as good as myglob.c
1604            ProcessFile(argv[argn]);
1605        #endif
1606
1607        if (!FilesMatched){
1608            fprintf(stderr, "Error: No files matched '%s'\n",argv[argn]);
1609        }
1610    }
1611
1612    if (FileSequence == 0){
1613        return EXIT_FAILURE;
1614    }else{
1615        return EXIT_SUCCESS;
1616    }
1617}
1618#endif
1619
1620#endif   // commented out -- security risk
1621
1622