"Fossies" - the Fresh Open Source Software Archive

Member "jhead-3.04/jhead.c" (22 Nov 2019, 64589 Bytes) of package /linux/privat/jhead-3.04.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "jhead.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.03_vs_3.04.

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