rjhead 2.88.1 → 2.88.2

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