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