/ [renrot] / trunk / renrot
To checkout: svn checkout http://svn.gnu.org.ua/sources/renrot/trunk/renrot
Puszcza

Contents of /trunk/renrot

Parent Directory Parent Directory | Revision Log Revision Log


Revision 625 - (show annotations)
Sat Jun 8 17:56:51 2013 UTC (8 years, 5 months ago) by zeus
File size: 83531 byte(s)
version valu corrected
1 #!/usr/bin/env perl
2
3 #
4 # vim: ts=2 sw=2 et :
5 #
6
7 # Analyze crash in third-party modules
8 # (Recipe has been borrowed from http://ilya-dogolazky.livejournal.com/15061.html)
9 BEGIN { $| = 1; $SIG{__DIE__} = sub { use Carp; confess $_[0] . 'Aborted' } }
10
11 use strict;
12 use warnings;
13 use diagnostics;
14 require 5.006;
15 use Pod::Usage;
16 use Image::ExifTool;
17 use Getopt::Long;
18 use File::Spec;
19 use File::Basename;
20
21 # add our 'lib' directory to the include list
22 my $exeDir;
23 BEGIN {
24 $exeDir = ($0 =~ /(.*)[\\\/]/) ? $1 : '.'; # get exe directory
25 unshift @INC, "$exeDir/lib"; # add lib directory at start of include path
26 }
27
28 use Image::RenRot::Util;
29 use Image::RenRot::Logging;
30 use Image::RenRot::Config;
31 use Image::RenRot::TimeUtil;
32 use Image::RenRot::FileUtil;
33
34 # perl 5.8.x memory overflow workaround (perl5.8.1-8 which uses it's own malloc)
35 my $mem_trouble_stub = " "x4_000_000; undef $mem_trouble_stub;
36
37 # Versioning
38 my @svnrevision = split(/ /, '$Revision$');
39 our $VERSION = '1.2.0';
40 our $REVISION = defined $svnrevision[1] ? $svnrevision[1] : '0';
41
42 my $maxVerbosity = 4; # our max verbosity level (internal)
43 my $homeURL = 'http://puszcza.gnu.org.ua/projects/renrot/'; # homepage of the project
44
45 ########################################################################################
46 #
47 # The global definition for a new XMP namespace. The
48 # %Image::ExifTool::UserDefined::RenRot defines XMP-RenRot tags which could be
49 # added to mark the file as processed with RenRot.
50 #
51 if (%Image::ExifTool::UserDefined::RenRot) {
52 die_renrot("Won't redefine Image::ExifTool::UserDefined::RenRot.\n");
53 }
54
55 %Image::ExifTool::UserDefined::RenRot = (
56 GROUPS => { 0 => 'XMP', 1 => 'RenRot', 2 => 'Image' },
57 NAMESPACE => [ 'RenRot' => $homeURL ],
58 WRITABLE => 'string',
59 RenRotFileNameOriginal => { },
60 RenRotProcessingTimestamp => { },
61 RenRotVersion => { },
62 RenRotURL => { },
63 );
64
65 # The %Image::ExifTool::UserDefined hash defines new tags to be added to
66 # existing tables.
67 %Image::ExifTool::UserDefined = (
68 # new XMP namespaces must be added to the Main XMP table
69 'Image::ExifTool::XMP::Main' => {
70 RenRot => {
71 SubDirectory => {
72 TagTable => 'Image::ExifTool::UserDefined::RenRot',
73 },
74 },
75 },
76 );
77
78 ########################################################################################
79 #
80 # Parsed configuration file in hash
81 # Note: unfortunately we still need manual adaptation in getOptions()
82 #
83 my $co_aggr = {
84 # aggregation time delta in seconds (file with delta > $delta is placed in new DIR)
85 'delta' => { type => 'i', default => 900 },
86 # counterless directory name for "delta" type aggregation
87 'directory' => { type => 's', default => 'Images' },
88 # aggregation framework
89 'enabled' => { type => '!', default => 0},
90 # define aggregation mode, possible values are: none, delta or template
91 'mode' => { type => 's', default => 'none' },
92 # template for the files aggregation taken from CLI
93 'template' => { type => 's', default => '%Y%m%d' },
94 # flag to do links instead real file moving while aggregation
95 'virtual' => { type => '!', default => 0 },
96 };
97
98 my $co_cs = {
99 # background, look ImageMagick documentation for montage options
100 'background' => { type => 's', default => 'FFF' },
101 # bordercolor, look ImageMagick documentation for montage options
102 'bordercolor' => { type => 's', default => 'DDD' },
103 # tmp directory for the montage operations
104 'dir' => { type => 's', default => 'Contact.Sheet' },
105 # contact sheet generation
106 'enabled' => { type => '!', default => 0 },
107 # file name for the montage
108 'file' => { type => 's', default => 'cs-%c.jpg' },
109 # fill, look ImageMagick documentation for montage options
110 'fill' => { type => 's', default => '000' },
111 # font, look ImageMagick documentation for montage options
112 'font' => { type => 's', default => 'Helvetica' },
113 # frame, look ImageMagick documentation for montage options
114 'frame' => { type => 's', default => '3' },
115 # label, look ImageMagick documentation for montage options
116 'label' => { type => 's', default => '%t' },
117 # mattecolor, look ImageMagick documentation for montage options
118 'mattecolor' => { type => 's', default => 'CCC' },
119 # pointsize, look ImageMagick documentation for montage options
120 'pointsize' => { type => 's', default => '11' },
121 # ranking
122 'rank' => { type => '!', default => 0 },
123 # file with images ranks
124 'rank file' => { type => 's', default => '.rank' },
125 # shadow, look ImageMagick documentation for montage options
126 'shadow' => { type => '!', default => 1 },
127 # is $extToProcess files are the thumbnails?
128 'thm' => { type => '!', default => 0 },
129 # font, look ImageMagick documentation for montage options
130 'thm font' => { type => 's', default => 'Helvetica' },
131 # fill, look ImageMagick documentation for montage options
132 'thm fill' => { type => 's', default => 'gray34' },
133 # thumbnail background gradient from
134 'thm grad fr' => { type => 's', default => 'FFFFFF' },
135 # thumbnail background gradient to
136 'thm grad to' => { type => 's', default => '909090' },
137 # thumbnail text
138 'thm text' => { type => 's', default => "thumbnail\n\nNA" },
139 # tile in the montage
140 'tile' => { type => 's', default => '7x5' },
141 # title for the montage
142 'title' => { type => 's', default => 'Default Contact Sheet Title' },
143 };
144
145 my $co_g = {
146 'generate thumbnail size' => { type => 's', default => '160x120' },
147 # mtime taken from CLI
148 'mtime' => { type => '!', default => 1 },
149 # template for the filename taken from CLI
150 'name template' => { type => 's', default => '%Y%m%d%H%M%S' },
151 # jpegtran -trim
152 'trim' => { type => '!', default => 1 },
153 # colorized output
154 'use color' => { type => '!', default => 0 },
155 # rotate thumbnail via pipe
156 'use ipc' => { type => '!', default => 0 },
157 };
158
159 my $co_kw = {
160 # whether or not to fill Keywords tag
161 'enabled' => { type => '!', default => 0 },
162 # file with keyword set
163 'file' => { type => 's', default => '.keywords' },
164 # whether to add keywords to the existent ones or replace them
165 'replace' => { type => '!', default => 0 },
166 };
167
168 my $config_opts = {
169 'aggregation' => $co_aggr,
170 'contact sheet' => $co_cs,
171 'general' => $co_g,
172 'keywords' => $co_kw,
173 };
174
175 ########################################################################################
176 #
177 # Command line options
178 #
179 my $backup = 1; # make or not a backup of the original files
180 my $comfile; # file with commentary
181 my $configFile; # configuration file
182
183 my $countFF = 1; # use fixed field for counter
184 my $countStart = 1; # Start value for counter
185 my $countStep = 1; # Step for counter
186 my $dryRun = 0; # show what would have been happened
187 my @excludeList = (); # files that will be excluded from list
188 my $extToProcess = ''; # the extension of files to work with
189 my $gen_thm = 0; # to not to generate ThumbnailImage if empty
190
191 my $noRename = 0; # no rename needed, default is to rename to the YYYYmmddHHMMSS.ext
192 my $noRotation = 0; # no rotation needed, default is to rotate
193 my $noTags = 0; # no tags writing needed
194 my $noRenRoTagMtm = 0; # no rename, no rotate, no tag and no mtime have to be done
195 my $orientTag = 0; # rotate by changing Orientation tag (no real rotation)
196 my $quiet = 0; # suppressing messages
197 my $rotateAngle; # define the angle to rotate on 90, 180 or 270
198 my $rotateThumbnail; # define the angle to rotate on 90, 180 or 270
199 my $subFileSet = ''; # subset of files to process, given in file rather than in commandline
200 my %tagsFromCli = (); # tags are got from CLI
201 my @tag_to_name = (); # tags for name building, theirs values to be used (in theory any EXIF)
202 my $userComment; # text to put into UserComment tag
203 my $verbose = 0; # verbosity of output
204 my $workDir = './'; # we'll work ONLY in current directory
205 my $isThereIM = 0; # is there Image::Magick package?
206
207 ########################################################################################
208 #
209 # Tags hash for injecting to EXIFs when renaming
210 #
211 my %tags = (
212 'RenRotProcessingTimestamp' => {
213 value => now(),
214 group => 'RenRot',
215 },
216 'RenRotVersion' => {
217 value => $VERSION . " (r" . $REVISION . ")",
218 group => 'RenRot',
219 },
220 'RenRotURL' => {
221 value => $homeURL,
222 group => 'RenRot',
223 },
224 ); # define tags for filling
225
226 ########################################################################################
227 #
228 # Global variables (internal)
229 #
230 my @rotparms = (
231 '',
232 '-flip horizontal',
233 '-rotate 180',
234 '-flip vertical',
235 '-transpose',
236 '-rotate 90',
237 '-transverse',
238 '-rotate 270',
239 ); # jpegtran options array to rotate the file
240
241 my @angles = (
242 '',
243 'fh',
244 '180cw',
245 'fv',
246 'tp',
247 '90cw',
248 'tv',
249 '270cw',
250 ); # the array of suffixes to add to the newfilename after rotating
251
252 my %rotangles = (
253 '90' => '-rotate 90',
254 '180' => '-rotate 180',
255 '270' => '-rotate 270'
256 ); # array of options to rotate file "by hands"
257
258 my %rotorient = (
259 1 => 0,
260 6 => 90,
261 3 => 180,
262 8 => 270,
263 );
264
265 my %rotorientrev = reverse %rotorient;
266
267 my @multOpts = (
268 'color',
269 'include',
270 'tag',
271 'tagfile',
272 );
273
274 my @files = (); # array of the sorted filenames to process
275 my %filenameshash = (); # hash for old file names
276
277 ########################################################################################
278 ### GETTINNG OPTIONS FROM CLI ###
279 ########################################################################################
280
281 ########################################################################################
282 # Usage : getOptions(...)
283 # Purpose : parses command line arguments
284 # Returns : nothing
285 # Parameters : many of them, look bellow
286 # Throws : no exceptions
287 # Comments : none
288 # See Also : Getopt::Long GetOptions()
289 sub getOptions {
290 my @tmpTags = ();
291
292 # For 'aggregation'
293 my $s_aggr_opts;
294 my %h_aggr_opts;
295
296 # For 'contact sheet'
297 my $s_cs_opts;
298 my %h_cs_opts;
299
300 # For 'general'
301 my %h_g_opts;
302
303 # For 'keywords'
304 my $s_kw_opts;
305 my %h_kw_opts;
306
307 my $ll_opts = {
308 'aggregation' => { 's' => \$s_aggr_opts, 'h' => \%h_aggr_opts },
309 'contact sheet' => { 's' => \$s_cs_opts, 'h' => \%h_cs_opts },
310 'keywords' => { 's' => \$s_kw_opts, 'h' => \%h_kw_opts },
311 };
312
313 GetOptions (
314 # AGGREGATION
315 "aggr-opts=s" => \$s_aggr_opts,
316 "aggr-delta=i" => \$h_aggr_opts{'delta'},
317 "aggr-directory=s" => \$h_aggr_opts{'directory'},
318 "aggr-mode=s" => \$h_aggr_opts{'mode'},
319 "aggr-template|a=s" => \$h_aggr_opts{'template'},
320 "aggr-virtual!" => \$h_aggr_opts{'virtual'},
321
322 # CONTACT SHEET
323 "contact-sheet-opts|cs-opts=s" => \$s_cs_opts,
324 "contact-sheet-bg|cs-bg=s" => \$h_cs_opts{'background'},
325 "contact-sheet-bd|cs-bd=s" => \$h_cs_opts{'bordercolor'},
326 "contact-sheet-dir|cs-dir=s" => \$h_cs_opts{'dir'},
327 "contact-sheet|cs!" => \$h_cs_opts{'enabled'},
328 "contact-sheet-file|cs-file=s" => \$h_cs_opts{'file'},
329 "contact-sheet-fl|cs-fl=s" => \$h_cs_opts{'fill'},
330 "contact-sheet-fn|cs-fn=s" => \$h_cs_opts{'font'},
331 "contact-sheet-fr|cs-fr=s" => \$h_cs_opts{'frame'},
332 "contact-sheet-lb|cs-lb=s" => \$h_cs_opts{'label'},
333 "contact-sheet-mt|cs-mt=s" => \$h_cs_opts{'mattecolor'},
334 "contact-sheet-pntsz|cs-pntsz=i" => \$h_cs_opts{'pointsize'},
335 "contact-sheet-rank|cs-rank!" => \$h_cs_opts{'rank'},
336 "contact-sheet-rank-file|cs-rank-file=s" => \$h_cs_opts{'rank file'},
337 "contact-sheet-shadow|cs-shadow" => \$h_cs_opts{'shadow'},
338 "contact-sheet-thm|cs-thm" => \$h_cs_opts{'thm'},
339 "contact-sheet-thm-fl|cs-thm-fl=s" => \$h_cs_opts{'thm fill'},
340 "contact-sheet-thm-fn|cs-thm-fn=s" => \$h_cs_opts{'thm font'},
341 "contact-sheet-thm-grfr|cs-thm-grfr=s" => \$h_cs_opts{'thm grad fr'},
342 "contact-sheet-thm-grto|cs-thm-grto=s" => \$h_cs_opts{'thm grad to'},
343 "contact-sheet-thm-text|cs-thm-text=s" => \$h_cs_opts{'thm text'},
344 "contact-sheet-tile|cs-tile=s" => \$h_cs_opts{'tile'},
345 "contact-sheet-title|cs-title=s" => \$h_cs_opts{'title'},
346
347 # GENERAL
348 "mtime!" => \$h_g_opts{'mtime'},
349 "name-template|n=s" => \$h_g_opts{'name template'},
350 "trim!" => \$h_g_opts{'trim'},
351 "use-color!" => \$h_g_opts{'use color'},
352 "use-ipc!" => \$h_g_opts{'use ipc'},
353
354 # KEYWORDIZER
355 "kw-opts=s" => \$s_kw_opts,
356 "keywords!" => \$h_kw_opts{'enabled'},
357 "keywords-file|k=s" => \$h_kw_opts{'file'},
358 "keywords-replace!" => \$h_kw_opts{'replace'},
359
360 # RENAMIMG
361 "counter-fixed-field!" => \$countFF,
362 "counter-start=i" => \$countStart,
363 "counter-step=i" => \$countStep,
364 "no-rename|norename" => \$noRename,
365
366 # ROTATIMG
367 "no-rotate|norotate" => \$noRotation,
368 "only-orientation" => \$orientTag,
369 "rotate-angle|r=i" => \$rotateAngle,
370 "rotate-thumb=i" => \$rotateThumbnail,
371
372 # TAG WRITER
373 "comment-file=s" => \$comfile,
374 "no-tags|notags" => \$noTags,
375 "tag|t=s" => \@tmpTags,
376 "user-comment=s" => \$userComment,
377
378 # OTHERS
379 "backup!" => \$backup,
380 "config-file|c=s" => \$configFile,
381 "dry-run" => \$dryRun,
382 "exclude=s" => \@excludeList,
383 "extension|e=s" => \$extToProcess,
384 "generate-thumb|g" => \$gen_thm,
385 "help|?" => sub { usage(0, 2) },
386 "no-renrot|nochg" => \$noRenRoTagMtm,
387 "quiet|q" => \$quiet,
388 "sub-fileset=s" => \$subFileSet,
389 "v+" => \$verbose,
390 "version" => sub { usage(0, 0) },
391 "work-directory|d=s" => \$workDir,
392 ) or usage(1, 1);
393
394 # Set the verbosity first
395 Image::RenRot::Logging->set(Verbose => $quiet ? -1 : $verbose);
396
397 my $fileCount = scalar(@ARGV);
398
399 foreach my $key (keys %$ll_opts) {
400 # Parse long list option
401 if (defined ${$ll_opts->{$key}{'s'}}) {
402 update_cfg_value($config_opts->{$key}{'enabled'}, 1);
403 parse2hash(${$ll_opts->{$key}{'s'}}, $config_opts, $key);
404 }
405 # Override long list option by set of standalone ones before any other action
406 assign2hash($config_opts, $key, $ll_opts->{$key}{'h'});
407 # Debug result
408 ldbg3opts($config_opts, $key);
409 }
410
411 # For 'general' section
412 assign2hash($config_opts, 'general', \%h_g_opts);
413 ldbg3opts($config_opts, 'general');
414
415 ldbg3("--backup: ", bool2str($backup));
416 ldbg3("--comment-file: $comfile") if (defined $comfile);
417 ldbg3("--config-file: $configFile") if (defined $configFile);
418
419 ldbg3("--counter-start: $countStart",
420 " --counter-step: $countStep",
421 " --counter-fixed-field: ", bool2str($countFF));
422 ldbg3("--dry-run: ", bool2str($dryRun)) if (defined $dryRun);
423 ldbg3("--exclude:\n", join("\n", @excludeList)) if (scalar(@excludeList) > 0);
424 ldbg3("--extension: '$extToProcess'");
425 ldbg3("--generate-thumb: ", bool2str($gen_thm));
426
427 ldbg3("--no-rename: ", bool2str($noRename),
428 " --no-rotate: ", bool2str($noRotation),
429 " --no-tags: ", bool2str($noTags),
430 " --no-renrot: ", bool2str($noRenRoTagMtm));
431 ldbg3("--only-orientation: ", bool2str($orientTag));
432 ldbg3("--rotate-angle: $rotateAngle") if (defined $rotateAngle);
433 ldbg3("--rotate-thumb: $rotateThumbnail") if (defined $rotateThumbnail);
434 ldbg3("--sub-fileset: $subFileSet") if ($subFileSet ne "");
435 ldbg3("--tag:\n", join("\n", @tmpTags)) if (scalar(@tmpTags) > 0);
436 ldbg3("--work-directory: $workDir");
437 ldbg3("ARGV:\n", join("\n", @ARGV)) if ($fileCount > 0);
438
439 if ($extToProcess eq "" and ($fileCount == 0) and $subFileSet eq "") {
440 fatalmsg ("Extension of files is required!\n");
441 exit 1;
442 }
443
444 if ($extToProcess ne "" and ($fileCount != 0)) {
445 warnmsg ("Extension of files will be ignored!\n");
446 }
447
448 if ($noRenRoTagMtm != 0) {
449 $noRename = $noRotation = $noTags = 1;
450 update_cfg_value($config_opts->{'general'}{'mtime'}, 0);
451 }
452
453 # is there ImageMagick?
454 if ($isThereIM == 1) {
455 dbgmsg (1, "We have Image::Magick package and could proceed with --contact-sheet related functionality.\n");
456 } elsif (get_cfg_value($co_cs, 'enabled') == 1) {
457 errmsg ("To use --contact-sheet related functionality you need Image::Magick package!\n",
458 "Contact Sheet generation disabled.\n");
459 } elsif ($gen_thm != 0) {
460 errmsg ("To use --generate-thumb functionality you need Image::Magick package!\n",
461 "ThumbnailImage generation is disabled.\n");
462 }
463
464 # preparing Software tag according the usage or not of ImageMagick
465 if ($isThereIM == 1) {
466 my ($imName, $imVersion) = (split(/ /, Image::Magick->new()->Get('version')))[0,1];
467 $tags{'Software'}{'value'} = sprintf("RenRot v%s (r%s), ExifTool v%s, %s v%s", $VERSION, $REVISION, $Image::ExifTool::VERSION, $imName, $imVersion);
468 } else {
469 $tags{'Software'}{'value'} = sprintf("ExifTool v%s, RenRot v%s (r%s)", $Image::ExifTool::VERSION, $VERSION, $REVISION);
470 }
471 $tags{'Software'}{'group'} = 'EXIF';
472
473 # Change user's parameter '*.ext' or 'ext' to '.ext'
474 $extToProcess =~ s/^\*?\.?/\./ if ($fileCount == 0);
475 dbgmsg (1, "Process with '$extToProcess' extension.\n");
476
477 # Convert multiple tag parameters to tags hash
478 foreach my $tagStr (@tmpTags) {
479 my %tag = str2hash($tagStr);
480 map { $tagsFromCli{$_} = $tag{$_} } keys %tag;
481 }
482 }
483
484 ########################################################################################
485 #
486 # parseConfig() parses user's or standart configuration files to hash
487 #
488 sub parseConfig {
489 my $file = shift;
490 my $hash = shift;
491
492 my @sections = keys %$hash;
493 Image::RenRot::Config->init(Sections => \@sections, MultiOptions => \@multOpts);
494
495 return Image::RenRot::Config->parsefile($file, $hash) if (defined $file);
496
497 my $home = $ENV{"HOME"};
498 my @homeRC = ();
499 $home = $ENV{"USERPROFILE"} if (not defined $home);
500
501 if (defined $home and $home ne "") {
502 push (@homeRC, File::Spec->catfile($home, ".renrotrc"));
503 push (@homeRC, File::Spec->catfile($home, ".renrot", ".renrotrc"));
504 push (@homeRC, File::Spec->catfile($home, ".renrot", "renrot.conf"));
505 } else {
506 warnmsg ("User's home environment variable isn't defined or empty!\n");
507 }
508
509 my @rcFiles = (
510 "/etc/renrot.rc",
511 "/etc/renrot/renrot.rc",
512 "/etc/renrot/renrot.conf",
513 "/usr/local/etc/renrot.rc",
514 "/usr/local/etc/renrot/renrot.rc",
515 "/usr/local/etc/renrot/renrot.conf",
516 @homeRC,
517 );
518
519 map { Image::RenRot::Config->parsefile($_, $hash) } @rcFiles;
520 }
521
522 ########################################################################################
523 #
524 # switchColor() switches to user defined color scheme
525 #
526 sub switchColor {
527 my $colors = ();
528
529 # Parse configuration file color set
530 while (my ($cKey, $v) = each %$config_opts) {
531 next if ($cKey !~ m/^color#\d+#\d+$/); # skip not a color
532 my ($ck, $ch) = str2hash($v, 'reset');
533 $colors->{$ck} = $ch;
534 }
535 Image::RenRot::Logging->set(Color => $colors,
536 UseColor => get_cfg_value($co_g, 'use color'));
537 dbgmsg (1, "Switch to user defined color scheme.\n");
538 }
539
540 ########################################################################################
541 # Usage : keywordizer($keywords_file)
542 # Purpose : validates keywords
543 # Returns : [array of strings] @results with CR and LF symbols removed
544 # Parameters : [string] $keywords_file
545 # Throws : no exceptions
546 # Comments : none
547 # See Also : N/A
548 sub keywordizer {
549 my $file = shift; # keywords file
550 return if (not (-R $file and -f $file and -T $file));
551 dbgmsg (2, "Reading keywords from file: $file\n");
552 my @result = ();
553 my @keywordArr = Image::RenRot::FileUtil->getFileDataLines($file);
554 for (my $i = 0; $i < scalar(@keywordArr); $i++) {
555 $keywordArr[$i] =~ s/\r?\n$//; # remove CR and LF symbols
556 push (@result, trim($keywordArr[$i])) if ($keywordArr[$i] !~ m/^\s*$/);
557 }
558 return @result;
559 }
560
561 ########################################################################################
562 # Usage : renRotProcess($exif_tool_obj, $counter_size)
563 # Purpose : renames and rotates given file set
564 # Returns : nothing
565 # Parameters : $exif_tool_obj [ref]
566 # : $counter_size [num] file name counter digit capacity (number of digits in
567 # : counter. for tens it is 2, for thousands - 4, e.t.c.)
568 # Throws : no exceptions
569 # Comments : none
570 # See Also : N/A
571 sub renRotProcess {
572 my $exifToolObj = shift;
573 my $counterSize = shift;
574 my $fileCounter = $countStart;# file counter
575 my $newFileName; # the name file to be renamed to
576 my $info; # ImageInfo object
577 my @keywordArr = (); # array for keywords
578
579 if (get_cfg_value($co_kw, 'enabled') != 0) {
580 @keywordArr = keywordizer (get_cfg_value($co_kw, 'file'));
581 errmsg ("Keywords file doesn't exist!\n") if (not -e get_cfg_value($co_kw, 'file'));
582 }
583
584 if (scalar(@keywordArr) > 0) {
585 dbgmsg (2, "Keywords count: ", scalar(@keywordArr), "\n");
586 if (get_cfg_value($co_kw, 'replace') != 0) {
587 $exifToolObj->SetNewValue(Keywords => \@keywordArr);
588 } else {
589 $exifToolObj->SetNewValue(Keywords => \@keywordArr, AddValue => 1);
590 }
591 }
592
593 # Convert trim boolean value to string
594 my $trimStr = get_cfg_value($co_g, 'trim') ? '-trim' : '';
595 dbgmsg (1, "Trim string: '$trimStr'\n");
596
597 dbgmsg (1, "Initializing tags...\n");
598 foreach my $key (sort (keys %tags)) {
599 $exifToolObj->SetNewValue($key, $tags{$key}{value}, Group => $tags{$key}{group});
600 }
601
602 procmsg ("RENAMING / ROTATING\n");
603 procmsg ("===================\n");
604
605 my $file_num = scalar(@files);
606 my $file_rem = 0;
607 foreach my $file (@files) {
608 $file_rem++;
609 procmsg ("Processing file: ($file_rem of $file_num) $file...\n");
610
611 # Setup defaults
612 $info = $exifToolObj->ImageInfo($file);
613
614 # analyzing whether to rotate
615 my $angleSuffix = rotateFile($exifToolObj, $info, $file, $trimStr);
616
617 # generating absent ThumbnailImage tag from the original image
618 if (($isThereIM == 1) and ($gen_thm != 0) and not defined ${$$info{ThumbnailImage}}) {
619 thm_gen_orig($file, 1);
620 }
621
622 # analyzing whether and how to rename file
623 $newFileName = renameFile($exifToolObj, $info, $file, $fileCounter, $counterSize, $angleSuffix);
624
625 # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file
626 saveOurHdrs($exifToolObj, $info, $file);
627
628 # Writing tags.
629 tagWriter($exifToolObj, $newFileName) if ($noTags == 0);
630
631 # seting mtime for the file if been asked for
632 mtimeSet($exifToolObj, $info, $newFileName);
633
634 procmsg ("\n");
635
636 $fileCounter += $countStep;
637 }
638 }
639
640 ########################################################################################
641 # Usage : saveOurHdrs($exif_tool_obj, $info_obj, $file)
642 # Purpose : saves native RenRot defined tags to file EXIF
643 # Returns : nothing
644 # Parameters : $exif_tool_obj [ref]
645 # : $info_obj [ref]
646 # : $file [str]
647 # Throws : no exceptions
648 # Comments : none
649 # See Also : N/A
650 sub saveOurHdrs {
651 my $exifToolObj = shift;
652 my $infoObj = shift;
653 my $file = shift;
654
655 my $fileNameOriginal = $exifToolObj->GetValue("RenRotFileNameOriginal");
656
657 if (not defined $fileNameOriginal) {
658 $tags{'RenRotFileNameOriginal'} = {value => $file, group => 'RenRot'};
659 dbgmsg (2, "Set RenRotFileNameOriginal to $file.\n");
660 } else {
661 $tags{'RenRotFileNameOriginal'} = {
662 value => $infoObj->{"RenRotFileNameOriginal"},
663 group => 'RenRot'
664 };
665 dbgmsg (2, "RenRotFileNameOriginal: $fileNameOriginal.\n");
666 }
667
668 $exifToolObj->SetNewValue("RenRotFileNameOriginal", $tags{'RenRotFileNameOriginal'}{value}, Group => $tags{'RenRotFileNameOriginal'}{group});
669 }
670
671 ########################################################################################
672 #
673 # rotateFile() rotates file and its thumbnail if needed, changes Orientation tag
674 #
675 sub rotateFile {
676 my $exifToolObj = shift;
677 my $infoObj = shift;
678 my $file = shift;
679 my $trimStr = shift;
680 my $orientation = $exifToolObj->GetValue("Orientation", 'ValueConv');
681 my $angleSuffix = "0cw";
682
683 if ($noRotation != 0) { dbgmsg (2, "No rotation asked, file orientation is left untouched.\n"); }
684 elsif (defined $rotateAngle) {
685 dbgmsg (2, "We'll deal with: $file and $rotangles{$rotateAngle}.\n");
686 if ($orientTag != 0) {
687 rotateOrient($exifToolObj, $file, $orientation);
688 } else {
689 rotateImg($file, $rotangles{$rotateAngle}, $trimStr);
690 rotateThumbnail($infoObj, $file, $rotangles{$rotateAngle}, $trimStr);
691 }
692 $angleSuffix = $rotateAngle . "cw";
693 } elsif (defined $rotateThumbnail) {
694 rotateThumbnail($infoObj, $file, $rotangles{$rotateThumbnail}, $trimStr);
695 } else {
696 if (defined $orientation) {
697 if ($orientation > 1) {
698 rotateImg($file, $rotparms[$orientation - 1], $trimStr);
699 rotateThumbnail($infoObj, $file, $rotparms[$orientation - 1], $trimStr);
700 $angleSuffix = $angles[$orientation - 1];
701 } elsif ($orientation == 1) {
702 dbgmsg (2, "No need to rotate, orientation is: Horizontal (normal).\n");
703 }
704 else {
705 errmsg ("Something wrong, orientation low than 1: $orientation.\n");
706 }
707 } else {
708 warnmsg ("Orientation tag is absent!\n");
709 }
710 }
711 return $angleSuffix;
712 }
713
714 ########################################################################################
715 #
716 # renameFile() renames file according to user request and EXIF data
717 #
718 sub renameFile {
719 my $exifToolObj = shift;
720 my $infoObj = shift;
721 my $file = shift;
722 my $fileCounter = shift;
723 my $counterSize = shift;
724 my $angleSuffix = shift;
725 my $newFileName;
726 my $unixTime = get_unix_time(getTimestamp($exifToolObj, $infoObj));
727
728 if ($noRename != 0) {
729 dbgmsg (2, "No renaming asked, filename is left untouched.\n");
730 $newFileName = $file;
731 $filenameshash{$newFileName} = $unixTime;
732 } else {
733 $newFileName = template2name (
734 $exifToolObj,
735 $infoObj,
736 get_cfg_value($co_g, 'name template'),
737 $fileCounter,
738 $file,
739 $counterSize,
740 $angleSuffix
741 );
742
743 my $ext = "." . (splitext($file))[1];
744
745 $newFileName .= "." . sprintf($counterSize, $fileCounter) if ($filenameshash{$newFileName . $ext});
746
747 $newFileName .= $ext;
748
749 $filenameshash{$newFileName} = $unixTime;
750
751 if ($file ne $newFileName) {
752 if (-f $newFileName) {
753 die_renrot("File $newFileName already exists!\n");
754 }
755 if ($dryRun == 0) {
756 rename ($file, $newFileName) || die_renrot("Unable to rename $file -> $newFileName.\n");
757 }
758 procmsg ("Renamed: $file -> $newFileName\n");
759 } else { warnmsg ("No renaming needed for $newFileName, it looks as needed!\n"); }
760 }
761 return $newFileName;
762 }
763
764 ########################################################################################
765 #
766 # mtimeSet() sets mtime for the given file
767 #
768 sub mtimeSet {
769 my $exifToolObj = shift;
770 my $infoObj = shift;
771 my $file = shift;
772 if (get_cfg_value($co_g, 'mtime') != 0) {
773 my $mTime = get_unix_time(getTimestamp($exifToolObj, $infoObj));
774 if ($dryRun == 0) { utime $mTime, $mTime, $file; }
775 else { procmsg ("Setting mtime.\n"); }
776 dbgmsg (2, "Changing mtime for $file OK.\n");
777 }
778 }
779
780 ########################################################################################
781 #
782 # tagWriter() writes couple of tags defined via configuration file and command line
783 #
784 sub tagWriter {
785 my $exifToolObj = shift;
786 my $file = shift;
787
788 # writing the changes to the EXIFs
789 if ($dryRun == 0) { exifWriter($exifToolObj, $file); }
790 else { procmsg ("Writing user defined EXIF tags to $file.\n"); }
791 }
792
793 ########################################################################################
794 #
795 # exifWriter() applies EXIF info, set by SetNewValue, to the image taken from
796 # the file, and writes the result to the same file.
797 #
798 sub exifWriter {
799 my $exifToolObject = shift;
800 my $fileRes = shift; # the file the *image* taken from
801
802 my $result = $exifToolObject->WriteInfo($fileRes);
803 if ($result == 1) {
804 dbgmsg (2, "Writing to $fileRes seems to be OK.\n");
805 } elsif ($result == 2) {
806 warnmsg ("No EXIF difference. No EXIF was written.\n");
807 } else {
808 my $errorMessage = $exifToolObject->GetValue('Error');
809 my $warningMessage = $exifToolObject->GetValue('Warning');
810 if (defined $errorMessage) { errmsg ("ExifTool: $errorMessage\n"); }
811 if (defined $warningMessage) { warnmsg ("ExifTool: $warningMessage\n"); }
812 }
813 return $result;
814 }
815
816 ########################################################################################
817 ### AGGREGATION FRAMEWORK ###
818 ########################################################################################
819
820 ########################################################################################
821 #
822 # aggregationProcess() aggregates files to separate directories by request
823 #
824 sub aggregationProcess {
825 return if (get_cfg_value($co_aggr, 'mode') eq "none");
826
827 my $exifToolObj = shift;
828 my $counterSize = shift;
829 my $file;
830 my $info;
831 my $BaseDir = get_cfg_value($co_aggr, 'directory');
832 my $NewDir;
833 my $file_num = scalar(keys(%filenameshash));
834 my $file_rem = 0;
835
836 procmsg ("AGGREGATION\n");
837 procmsg ("===========\n");
838
839 makedir($BaseDir) if ($dryRun == 0);
840
841 if (get_cfg_value($co_aggr, 'mode') eq "template") {
842 dbgmsg (1, "Template: ", get_cfg_value($co_aggr, 'template'), "\n");
843 my $fileCounter = $countStart;
844
845 foreach $file (sort (keys %filenameshash)) {
846 $file_rem++;
847 $info = $exifToolObj->ImageInfo($file);
848 $NewDir = template2name (
849 $exifToolObj,
850 $info,
851 get_cfg_value($co_aggr, 'template'),
852 $fileCounter,
853 $file,
854 $counterSize,
855 "0cw"
856 );
857 $NewDir = File::Spec->catdir($BaseDir, $NewDir);
858 aggregateFile($file, $NewDir) if ($dryRun == 0);
859 procmsg ("Aggregate: ($file_rem of $file_num) $file -> $NewDir\n", "\n");
860 $fileCounter += $countStep;
861 }
862 } elsif (get_cfg_value($co_aggr, 'mode') eq "delta") {
863 my $DirCounter = 1;
864 my $timestampPrev;
865 my $filePrev;
866 my $filetmp;
867
868 foreach $file (sort (keys %filenameshash)) {
869 $filetmp = $file;
870 $file_rem++;
871
872 if ($DirCounter == 1) {
873 $timestampPrev = $filenameshash{$filetmp};
874 $filePrev = $filetmp;
875 $NewDir = $BaseDir . "." . sprintf($counterSize, $DirCounter);
876 $DirCounter++;
877 aggregateFile($file, $NewDir) if ($dryRun == 0);
878 } else {
879 # Check for new direcroty creation
880 if (($filenameshash{$filetmp} - $timestampPrev) > get_cfg_value($co_aggr, 'delta')) {
881 $NewDir = $BaseDir . "." . sprintf($counterSize, $DirCounter);
882 $DirCounter++;
883 }
884 aggregateFile($file, $NewDir) if ($dryRun == 0);
885 $timestampPrev = $filenameshash{$filetmp};
886 }
887 procmsg ("Aggregate: ($file_rem of $file_num) $file -> $NewDir\n", "\n");
888 }
889 } else {
890 errmsg ("Aggregation mode ", get_cfg_value($co_aggr, 'mode'), " isn't implemented!\n");
891 }
892 }
893
894 ########################################################################################
895 # Usage : contactSheetGenerator();
896 # Purpose : builds contact sheet(s)
897 # Returns : none
898 # Parameters : $exifTool ref -
899 # Throws : no exceptions
900 # Comments : requires --ext; --counter-start is valid
901 # See Also : template2name()
902 sub contactSheetGenerator {
903 return if (not get_cfg_value($co_cs, 'enabled'));
904
905 use File::Copy;
906
907 my $exifToolObj = shift;
908 my $workdir = get_cfg_value($co_cs, 'dir');
909 my $file;
910 my $info;
911 my $infothm;
912 my $ThumbnailOriginal;
913 my $width;
914 my $height;
915 my $size = 0;
916 my @thumbnailes = ();
917 my @thumbnailes_sorted = ();
918 my $orientation;
919 my $filefull;
920 my $ranks; # reference to the hash of files ranks
921
922 # ranks file processing
923 if (get_cfg_value($co_cs, 'rank') == 1 and -f get_cfg_value($co_cs, 'rank file')) {
924 $ranks = Image::RenRot::FileUtil->getFileDatLns(get_cfg_value($co_cs, 'rank file'));
925 if ($ranks != 0) {
926 dbgmsg (3, "Ranks successfully processed.\n");
927 } else {
928 errmsg ("Ranks processing failed.\n");
929 }
930 }
931
932 makedir($workdir) if ($dryRun == 0);
933
934 procmsg ("CONTACT SHEET GENERATION\n");
935 procmsg ("========================\n");
936
937 # no sort since it'll be sorted below
938 foreach $file (keys %filenameshash) {
939 $info = $exifToolObj->ImageInfo($file);
940 $orientation = $exifToolObj->GetValue("Orientation", 'ValueConv');
941
942 $filefull = $file;
943
944 if (get_cfg_value($co_cs, 'thm') != 0 and defined $orientation) {
945 if ($orientation > 1) {
946 $filefull = rot_thm_cs ($file, $rotorient{$orientation}, $workdir);
947 } elsif ($orientation == 1) {
948 # We need this since rotated imgage will be at $workdir, but others are
949 # in current
950 $ThumbnailOriginal = File::Spec->catfile($workdir, $file);
951 if ($dryRun == 0) {
952 copy ($file, $ThumbnailOriginal) || die_renrot("copy failed: $!");
953 }
954 $filefull = $ThumbnailOriginal;
955 }
956 } elsif (get_cfg_value($co_cs, 'thm') == 0) {
957 if (defined ${$$info{ThumbnailImage}}) {
958 $ThumbnailOriginal = File::Spec->catfile($workdir, $file);
959 if ($dryRun == 0) {
960 open (OLDTHUMBNAIL, ">$ThumbnailOriginal") || die_renrot("$ThumbnailOriginal wasn't opened!\n");
961 binmode OLDTHUMBNAIL;
962 print OLDTHUMBNAIL ${$$info{ThumbnailImage}};
963 unless (close (OLDTHUMBNAIL)) { warnmsg ("$ThumbnailOriginal wasn't closed!\n"); }
964 }
965
966 if (not defined $orientation) {
967 $orientation = $exifToolObj->GetValue("Rotation", 'ValueConv');
968 }
969
970 if (defined $orientation and $orientation > 1) {
971 $filefull = rot_thm_cs ($ThumbnailOriginal, $orientation, File::Spec->curdir());
972 } else {
973 $filefull = $ThumbnailOriginal;
974 }
975 } else {
976 my $ffwf = File::Spec->catfile($workdir, $file);
977 if ($gen_thm == 0) {
978 warnmsg ("$filefull has no ThumbnailImage tag. Stub thumbnail image will be used.\n");
979 my $thmbstubjpg = File::Spec->catfile($workdir, "thmbstub.jpg");
980 thm_gen_stub($thmbstubjpg) if (not -f $thmbstubjpg);
981 copy ($thmbstubjpg, $ffwf) if ($dryRun == 0);
982 } else {
983 warnmsg ("$filefull has no ThumbnailImage tag. Thumbnail image will be generated.\n");
984 move (thm_gen_orig($file, 0), $ffwf) if ($dryRun == 0);
985 }
986 $filefull = $ffwf;
987 }
988 }
989
990 if ($dryRun == 0) {
991 $infothm = $exifToolObj->ImageInfo($filefull);
992
993 $width = $exifToolObj->GetValue("ImageWidth");
994 if ($width > $size) { $size = $width; }
995
996 $height = $exifToolObj->GetValue("ImageHeight");
997 if ($height > $size) { $size = $height; }
998 }
999
1000 push (@thumbnailes, $filefull);
1001 }
1002 @thumbnailes_sorted = sort {$a cmp $b} @thumbnailes;
1003
1004 # here it's iteration by tile
1005 my ($tileX, $tileY) = split ("x", get_cfg_value($co_cs, 'tile'));
1006 my $tileMul = $tileX * $tileY;
1007 my $csIterationNumber = scalar @thumbnailes_sorted;
1008 my $csFullIterations = int($csIterationNumber/$tileMul);
1009 my $csRest = $csIterationNumber % $tileMul;
1010 my $fileCounter = $countStart;
1011 my $csIteration = 0;
1012 my $csIterator = 0;
1013 my $csIter = 0;
1014 my $readres;
1015 my $image;
1016 my $writeres;
1017 my $montage;
1018 my $montagename;
1019 my $infoMontage;
1020 my @left_up_row = ("RenRot v$VERSION (r$REVISION)", $homeURL);
1021 my $substrFile;
1022 my $readIndex;
1023 my $counter_size;
1024
1025 if ($csFullIterations != 0) {
1026 $counter_size = "%." . length($csFullIterations * $countStep + $countStart) . "d";
1027 dbgmsg (1, "Counter size: $size (CS files num: $csFullIterations)\n");
1028 } else {
1029 $counter_size = "%d";
1030 }
1031
1032 if ($csFullIterations > 0) {
1033 # we need this for the case when $csRest == 0 and $csIterationNumber > $tileMul
1034 if (not $csRest) {
1035 --$csFullIterations;
1036 }
1037 for (; $csIterator < $csFullIterations; $csIterator++, $fileCounter += $countStep) {
1038 $image = Image::Magick->new;
1039
1040 $montagename = template2name (
1041 $exifToolObj,
1042 $info,
1043 get_cfg_value($co_cs, 'file'),
1044 $fileCounter,
1045 "stub",
1046 $counter_size,
1047 "none"
1048 );
1049 for ($readIndex = 0, $csIter = $csIterator * $tileMul; $csIter < ($csIterator * $tileMul + $tileMul); $csIter++, $readIndex++) {
1050 $readres = $image->Read($thumbnailes_sorted[$csIter]);
1051 if (not $readres) {
1052 dbgmsg (4, "$thumbnailes_sorted[$csIter] was successfully read.\n");
1053 } else { errmsg ("Image::Magick error: $readres\n"); }
1054
1055 # ranking
1056 $substrFile = substr($thumbnailes_sorted[$csIter], length(get_cfg_value($co_cs, 'dir')) + 1);
1057 if (defined $ranks->{$substrFile}->[1]) {
1058 dbgmsg (4, "$substrFile mattecolor is \"$ranks->{$substrFile}->[1]\"\n");
1059 $image->[$readIndex]->Set(mattecolor => $ranks->{$substrFile}->[1]);
1060 } else {
1061 dbgmsg (4, "$substrFile mattecolor is undefined, using \"", get_cfg_value($co_cs, 'mattecolor'), "\" from config\n");
1062 $image->[$readIndex]->Set(mattecolor => normalize_color(get_cfg_value($co_cs, 'mattecolor')));
1063 }
1064 }
1065 dbgmsg (1, "$csIterator montage is started, wait a bit please...\n");
1066 $montage = $image->Montage(background => normalize_color(get_cfg_value($co_cs, 'background')),
1067 bordercolor => normalize_color(get_cfg_value($co_cs, 'bordercolor')),
1068 font => get_cfg_value($co_cs, 'font'),
1069 fill => normalize_color(get_cfg_value($co_cs, 'fill')),
1070 label => get_cfg_value($co_cs, 'label'),
1071 frame => get_cfg_value($co_cs, 'frame'),
1072 geometry => $size . "x" . $size . "+4+4",
1073 pointsize => get_cfg_value($co_cs, 'pointsize'),
1074 shadow => get_cfg_value($co_cs, 'shadow'),
1075 title => get_cfg_value($co_cs, 'title'),
1076 tile => get_cfg_value($co_cs, 'tile'),
1077 stroke => 'none',
1078 );
1079
1080 if (not ref($montage)) { errmsg ("Image::Magick error: $montage\n"); }
1081 else { dbgmsg (1, "$csIterator montage've finished successfully.\n"); }
1082
1083 $montage->Set (
1084 filename => 'jpg:' . $montagename,
1085 quality => 95,
1086 interlace => 'Partition',
1087 gravity => 'Center',
1088 stroke => 'none',
1089 );
1090 $montage->Annotate (
1091 font => get_cfg_value($co_cs, 'font'),
1092 pointsize => 9,
1093 x => 5,
1094 y => 13,
1095 fill => 'lightgray',
1096 text => $left_up_row[0],
1097 );
1098 $montage->Annotate (
1099 font => get_cfg_value($co_cs, 'font'),
1100 pointsize => 9,
1101 x => 5,
1102 y => 23,
1103 fill => 'lightgray',
1104 text => $left_up_row[1],
1105 );
1106 $writeres = $montage->Write();
1107 if (not $writeres) { dbgmsg (1, "Successfully written $montagename file.\n"); }
1108 else { errmsg ("Image::Magick error: $writeres\n"); }
1109
1110 $infoMontage = $exifToolObj->ImageInfo($montagename);
1111
1112 # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file
1113 saveOurHdrs($exifToolObj, $infoMontage, $montagename);
1114
1115 # Writing tags.
1116 tagWriter($exifToolObj, $montagename);
1117
1118 # Writing ThumbnailImage tag with generated thumbnail
1119 thm_gen_orig($montagename, 1);
1120
1121 undef $image;
1122 }
1123 }
1124
1125 $image = Image::Magick->new;
1126
1127 $montagename = template2name (
1128 $exifToolObj,
1129 $info,
1130 get_cfg_value($co_cs, 'file'),
1131 $fileCounter,
1132 "stub",
1133 $counter_size,
1134 "none"
1135 );
1136
1137 for ($readIndex = 0, $csIteration = $csIterator * $tileMul; $csIteration < $csIterationNumber; $csIteration++, $readIndex++) {
1138 $readres = $image->Read($thumbnailes_sorted[$csIteration]);
1139 if (not $readres) { dbgmsg (4, "$thumbnailes_sorted[$csIteration] was successfully red.\n"); }
1140 else { errmsg ("Image::Magick error: $readres\n"); }
1141
1142 # ranking
1143 $substrFile = substr($thumbnailes_sorted[$csIteration], length(get_cfg_value($co_cs, 'dir')) + 1);
1144 if (defined $ranks->{$substrFile}->[1] and length($ranks->{$substrFile}->[1]) > 1) {
1145 dbgmsg (4, "[last] $substrFile mattecolor is \"$ranks->{$substrFile}->[1]\"\n");
1146 $image->[$readIndex]->Set(mattecolor => $ranks->{$substrFile}->[1]) if ($dryRun == 0);
1147 } else {
1148 dbgmsg (4, "[last] $substrFile mattecolor is undefined, using \"", get_cfg_value($co_cs, 'mattecolor'), "\" from config.\n");
1149 $image->[$readIndex]->Set(mattecolor => normalize_color(get_cfg_value($co_cs, 'mattecolor'))) if ($dryRun == 0);
1150 }
1151 }
1152 dbgmsg (1, "Final, $csIterator montage is started, wait a bit please...\n");
1153
1154 # the final invocation of ->Montage() method for the the rest of files didn't fit
1155 # previous loop ->Montage() calls when $csIterationNumber < $tileX * $tileY
1156 $montage = $image->Montage (
1157 background => normalize_color(get_cfg_value($co_cs, 'background')),
1158 bordercolor => normalize_color(get_cfg_value($co_cs, 'bordercolor')),
1159 font => get_cfg_value($co_cs, 'font'),
1160 fill => normalize_color(get_cfg_value($co_cs, 'fill')),
1161 label => get_cfg_value($co_cs, 'label'),
1162 frame => get_cfg_value($co_cs, 'frame'),
1163 geometry => $size . "x" . $size . "+4+4",
1164 pointsize => get_cfg_value($co_cs, 'pointsize'),
1165 shadow => get_cfg_value($co_cs, 'shadow'),
1166 title => get_cfg_value($co_cs, 'title'),
1167 tile => get_cfg_value($co_cs, 'tile'),
1168 stroke => 'none',
1169 );
1170
1171 if (not ref($montage)) { errmsg ("Image::Magick error: $montage\n"); }
1172 else { dbgmsg (1, "Montage've finished successfully.\n"); }
1173
1174 if ($dryRun == 0) {
1175 $montage->Set (
1176 filename => 'jpg:' . $montagename,
1177 quality => 95,
1178 interlace => 'Partition',
1179 gravity => 'Center',
1180 stroke => 'none',
1181 );
1182 $montage->Annotate (
1183 font => get_cfg_value($co_cs, 'font'),
1184 pointsize => 9,
1185 x => 5,
1186 y => 13,
1187 fill => 'lightgray',
1188 text => $left_up_row[0],
1189 );
1190 $montage->Annotate (
1191 font => get_cfg_value($co_cs, 'font'),
1192 pointsize => 9,
1193 x => 5,
1194 y => 23,
1195 fill => 'lightgray',
1196 text => $left_up_row[1],
1197 );
1198 $writeres = $montage->Write();
1199 }
1200 if (not $writeres) { dbgmsg (1, "Successfully written $montagename file.\n"); }
1201 else { errmsg ("Image::Magick error: $writeres\n"); }
1202 undef $image;
1203
1204 $infoMontage = $exifToolObj->ImageInfo($montagename);
1205
1206 # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file
1207 saveOurHdrs($exifToolObj, $infoMontage, $montagename);
1208
1209 # Writing tags.
1210 tagWriter($exifToolObj, $montagename);
1211
1212 # Writing ThumbnailImage tag with generated thumbnail
1213 thm_gen_orig($montagename, 1);
1214
1215 if (-d $workdir) {
1216 chdir $workdir;
1217 unlink <*>;
1218 chdir "..";
1219 }
1220 rmdir $workdir;
1221
1222 procmsg ("\n");
1223 }
1224
1225 ########################################################################################
1226 # Usage : thm_gen_stub($thm_name);
1227 # Purpose : thumbnail stub generator
1228 # Returns : none
1229 # Parameters : $thm_name str - thumbnail image pathname
1230 # Throws : no exceptions
1231 # Comments : none
1232 # See Also : contactSheetGenerator();
1233 sub thm_gen_stub {
1234 my $thm_name = shift;
1235 my $size = get_cfg_value($co_g, 'generate thumbnail size');
1236 my $thmb = Image::Magick->new;
1237
1238 $thmb->Set(size => $size, filename => $thm_name, quality => 95, interlace => 'Partition');
1239
1240 my $gf = normalize_color(get_cfg_value($co_cs, 'thm grad fr'));
1241 my $gt = normalize_color(get_cfg_value($co_cs, 'thm grad to'));
1242 $thmb->ReadImage("gradient:$gf-$gt");
1243 $thmb->Annotate(
1244 pointsize => 25,
1245 fill => normalize_color(get_cfg_value($co_cs, 'thm fill')),
1246 font => get_cfg_value($co_cs, 'thm font'),
1247 text => get_cfg_value($co_cs, 'thm text'),
1248 gravity => 'Center',
1249 );
1250
1251 if ($dryRun == 0) {
1252 my $thmbnum = $thmb->Write();
1253 if ($thmbnum) {
1254 errmsg ("$thmbnum\n");
1255 undef $thmb;
1256 return;
1257 }
1258 }
1259
1260 undef $thmb;
1261
1262 procmsg ("Stub thumbnail image has been created.\n");
1263 }
1264
1265 ########################################################################################
1266 # Usage : thm_gen_orig($file, $unlink);
1267 # Purpose : generate thumbnail image from the original file and write it
1268 # : to the ThumbnailImage tag
1269 # Returns : thumbnail file name if unlink not asked
1270 # Parameters : $file str - original image name
1271 # : $unlink bin - 0 not to kill generated thumbnail
1272 # : 1 to kill generated thumbnail
1273 # Throws : no exceptions
1274 # Comments : none
1275 # See Also : n/a
1276 sub thm_gen_orig {
1277 my $file = shift;
1278 my $unlink = shift;
1279 my $thm_name = "thm_" . $file;
1280 my $size = get_cfg_value($co_g, 'generate thumbnail size');
1281 my $thmb = Image::Magick->new;
1282
1283 $thmb->Set(size => $size, filename => $thm_name, quality => 95, interlace => 'Partition');
1284
1285 $thmb->ReadImage($file);
1286 $thmb->Thumbnail(geometry => $size);
1287
1288 if ($dryRun == 0) {
1289 my $thmbnum = $thmb->Write();
1290 if ($thmbnum) {
1291 errmsg ("$thmbnum\n");
1292 undef $thmb;
1293 return;
1294 }
1295 }
1296
1297 undef $thmb;
1298
1299 procmsg ("ThumbnailImage was generated for $file\n");
1300
1301 if ($unlink) {
1302 dbgmsg (3, "Attempt to write ThumbnailImage tag to $file\n");
1303 thumbWriter($file, Image::RenRot::FileUtil->getFileData($thm_name));
1304 unlink $thm_name;
1305 return;
1306 } else {
1307 return $thm_name;
1308 }
1309 }
1310
1311 ########################################################################################
1312 # Usage : rot_thm_cs($base_orig, $angle, $workdir);
1313 # Purpose : rotates thumbnails for contact sheet and put it to the workdir
1314 # Returns : none
1315 # Parameters : $base_orig str - pathless filename of the rotated file
1316 # : $angle num - rotation angle
1317 # : $workdir str - the work directory thumbnails kept in
1318 # Throws : no exceptions
1319 # Comments : works only with JPEGs
1320 # See Also : contactSheetGenerator();
1321 sub rot_thm_cs {
1322 my $base_orig = shift;
1323 my $angle = shift;
1324 my $workdir = shift;
1325 my $image_to_rotate = Image::Magick->new;
1326 my $result = File::Spec->catfile($workdir, $base_orig);
1327
1328 $image_to_rotate->Read("JPEG:" . $base_orig);
1329 $image_to_rotate->Rotate(degrees => $angle);
1330 $image_to_rotate->Write(filename => "JPEG:" . $result, quality => 95, compression => 'JPEG2000');
1331 undef $image_to_rotate;
1332 return $result;
1333 }
1334
1335 ########################################################################################
1336 # Usage : normalize_color($color);
1337 # Purpose : validates given string as color suitable for ImageMagick
1338 # Returns : none
1339 # Parameters : $color str - color
1340 # Throws : no exceptions
1341 # Comments : none
1342 # See Also : convert -list color
1343 sub normalize_color {
1344 my $color = shift;
1345 if ($color =~ /^#?([[:xdigit:]]{3})$/ or $color =~ /^#?([[:xdigit:]]{6})$/) {
1346 return "#" . $1;
1347 }
1348 return $color;
1349 }
1350
1351 ########################################################################################
1352 # Usage : aggregateFile($file, $NewDir);
1353 # Purpose : moves or links file to the aggregation directory
1354 # Returns : none
1355 # Parameters : $file [str] - name of file to be moved
1356 # : $new_dir [str] - name of dir the file to be moved to
1357 # Throws : no exceptions
1358 # Comments : none
1359 # See Also : N/A
1360 sub aggregateFile {
1361 my $file = shift;
1362 my $new_dir = shift;
1363 my $base_name = basename($file);
1364
1365 makedir($new_dir);
1366
1367 if (get_cfg_value($co_aggr, 'virtual') == 0) {
1368 my $newname = File::Spec->catfile($new_dir, $base_name);
1369 rename ($file, $newname) || die_renrot("While moving $file -> $newname, perhaps a different file system is using\n");
1370 dbgmsg (3, "$file moved to $newname\n");
1371 } else {
1372 my $newfile = File::Spec->catfile(File::Spec->abs2rel($new_dir), $base_name);
1373 if (not -l $newfile) {
1374 # Make relative path to original file from new directory
1375 my $oldfile = File::Spec->catfile(File::Spec->abs2rel(File::Spec->curdir(), $new_dir), $file);
1376 symlink ($oldfile, $newfile) || die_renrot("While linking $oldfile -> $newfile\n");
1377 dbgmsg (3, "Following link was created $newfile -> $oldfile\n");
1378 } else {
1379 warnmsg ("Link $newfile already exists.\n");
1380 }
1381 }
1382 }
1383
1384 ########################################################################################
1385 #
1386 # getTimestamp() returns EXIF timestamp in form YYYYmmddHHMMSS if exists, otherwise
1387 # it returns Image::RenRot::TimeUtil->now()
1388 #
1389 sub getTimestamp {
1390 my $exifToolObj = shift;
1391 my $infoObj = shift;
1392
1393 my $timestamp;
1394
1395 if (defined $infoObj->{"DateTimeOriginal"} and not time_validator($infoObj->{"DateTimeOriginal"})) {
1396 $timestamp = $infoObj->{"DateTimeOriginal"};
1397 }
1398 elsif (defined $infoObj->{"FileModifyDate"} and not time_validator($infoObj->{"FileModifyDate"})) {
1399 $timestamp = $infoObj->{"FileModifyDate"};
1400 } else {
1401 $timestamp = now();
1402 $exifToolObj->SetNewValue('FileModifyDate', $timestamp, Group => 'File');
1403 warnmsg ("EXIF timestamp isn't correct, using current time!\n");
1404 }
1405 return $timestamp;
1406 }
1407
1408 ########################################################################################
1409 #
1410 # rotateOrient() rotates image by changing Orientation tag. No real rotation
1411 # will be made.
1412 #
1413 sub rotateOrient {
1414 my $exifToolObj = shift;
1415 my $fileOrient = shift;
1416 my $orientation = shift;
1417
1418 dbgmsg (4, "Original Orientation: $orientation\n");
1419 my $angleTmp = $rotorient{$orientation};
1420 if (not defined $angleTmp) {
1421 errmsg ("Operation not permited for mirror type orientation.\n");
1422 return;
1423 }
1424
1425 $angleTmp += $rotateAngle;
1426 $angleTmp -= 360 if ($angleTmp >= 360);
1427
1428 $orientation = $rotorientrev{$angleTmp};
1429 dbgmsg (4, "New Orientation: $orientation\n");
1430
1431 $exifToolObj->SetNewValue("Orientation", $orientation, Type => 'ValueConv');
1432 if ($dryRun == 0) { exifWriter($exifToolObj, $fileOrient); }
1433 else { procmsg ("Rotating Orientation tag value.\n"); }
1434 }
1435
1436 ########################################################################################
1437 #
1438 # rotateImg() rotates the image file by given angle
1439 #
1440 sub rotateImg {
1441 my $oldfile = shift; # original name to transform with jpegtran
1442 my $origfile = $oldfile . "_orig"; # backup original name
1443 my $newfile = $oldfile . "_rotated"; # temporay name to store rotated file
1444 my @addon = @_; # the switches for jpegtran to transform the image
1445
1446 # jpegtran the image
1447 my $cmd = "jpegtran -copy none @addon -outfile \"$newfile\" \"$oldfile\"";
1448 dbgmsg (3, "$cmd\n");
1449
1450 if ($dryRun == 0) {
1451 system $cmd || die_renrot("System $cmd failed: $?\n");
1452 }
1453
1454 # preparing to write tags to the just rotated file
1455 my $exifAfterRot = new Image::ExifTool;
1456 $exifAfterRot->Options(Binary => 1);
1457 $exifAfterRot->SetNewValuesFromFile($oldfile, '*:*');
1458 $exifAfterRot->SetNewValue("Orientation", 1, Type => 'ValueConv');
1459
1460 if ($dryRun == 0) {
1461 if ($backup != 0) {
1462 rename ($oldfile, $origfile) || die_renrot("$oldfile -> $origfile\n");
1463 }
1464 rename ($newfile, $oldfile) || die_renrot("$newfile -> $oldfile\n");
1465 }
1466
1467 procmsg ("$oldfile was rotated\n");
1468
1469 # writing the changes to the EXIFs
1470 exifWriter($exifAfterRot, $oldfile) if ($dryRun == 0);
1471 }
1472
1473 ########################################################################################
1474 #
1475 # thumbWriter() writes binary data as thumbnail to given file
1476 #
1477 sub thumbWriter {
1478 my $file = shift;
1479 my $thethumb = shift;
1480
1481 # preparing to write thumbnale to the just rotated file
1482 my $exifThumbnailed = new Image::ExifTool;
1483 $exifThumbnailed->Options(Binary => 1);
1484 $exifThumbnailed->SetNewValue("ThumbnailImage", $thethumb, Type => 'ValueConv');
1485
1486 # writing the changes to the EXIFs
1487 procmsg ("Writing thumbnail to $file...\n");
1488 exifWriter($exifThumbnailed, $file) if ($dryRun == 0);
1489 }
1490
1491 ########################################################################################
1492 #
1493 # rotateThumbnail() rotates thumbnail only, where the file was rotated but
1494 # thumbnail was left untouched
1495 #
1496 sub rotateThumbnail {
1497 my $infoObj = shift;
1498 my $file = shift; # file, which thumbnale to transform with jpegtran
1499 my @addon = @_; # the switches for jpegtran to rotate the thumbnail
1500
1501 if (not defined ${$$infoObj{ThumbnailImage}}) {
1502 warnmsg ("No thumbnail found.\n");
1503 return;
1504 }
1505
1506 my $origThumb = ${$$infoObj{ThumbnailImage}};
1507
1508 if (get_cfg_value($co_g, 'use ipc') == 0) {
1509 # extracting the thumbnail image
1510 my $ThumbnailOriginal = $file . "_thumborig";
1511 unless (open (OLDTHUMBNAIL, ">$ThumbnailOriginal")) {
1512 errmsg ("$ThumbnailOriginal wasn't opened!\n");
1513 return;
1514 }
1515 binmode OLDTHUMBNAIL;
1516 print OLDTHUMBNAIL $origThumb;
1517 unless (close (OLDTHUMBNAIL)) { warnmsg ("$ThumbnailOriginal wasn't closed!\n"); }
1518
1519 # rotating the thumbnail
1520 my $ThumbnailOriginalRotated = $ThumbnailOriginal . "_rotated";
1521 my $cmd = "jpegtran -copy none @addon -outfile \"$ThumbnailOriginalRotated\" \"$ThumbnailOriginal\"";
1522 dbgmsg (3, "$cmd\n");
1523
1524 if ($dryRun == 0) {
1525 system $cmd || die_renrot("System $cmd failed: $?\n");
1526 }
1527
1528 # write the just rotated thumbnail back to file
1529 thumbWriter($file, Image::RenRot::FileUtil->getFileData($ThumbnailOriginalRotated));
1530
1531 if ($dryRun == 0) {
1532 unlink ($ThumbnailOriginalRotated) || die_renrot("While killing $ThumbnailOriginalRotated.\n");
1533 }
1534 unlink ($ThumbnailOriginal) || die_renrot("While killing $ThumbnailOriginal.\n");
1535 } else {
1536 my $cmd = "jpegtran -copy none @addon";
1537 dbgmsg (3, "$cmd\n");
1538
1539 # write the just rotated thumbnail back to file
1540 thumbWriter($file, Image::RenRot::FileUtil->piper($origThumb, $cmd));
1541 }
1542 }
1543
1544 ########################################################################################
1545 #
1546 # usage() prints the instructions how to use the script
1547 #
1548 sub usage {
1549 my $exitcode = shift;
1550 my $v = shift;
1551
1552 if ($v == 0) {
1553 infomsg ("RenRot version $VERSION (r$REVISION)\n");
1554 } elsif ($v == 1) {
1555 infomsg (
1556 "Usage: renrot <--extension EXTENSION> [--quiet] [--no-rotate] [--no-rename]
1557 [--name-template TPL] [--comment-file FILE] [--work-directory DIR]
1558 [[--] FILE1 FILE2 ...]
1559
1560 Options:
1561 -c, --config-file <FILE> configuration file to use
1562 -d, --work-directory <DIR> set working directory
1563 -e, --extension <EXTENSION> extension of files to process: JPG, jpeg, ...
1564
1565 Renaming options:
1566 -n, --name-template <TPL> filename template (see manual for details)
1567
1568 Rotating options:
1569 -r, --rotate-angle <ANGLE> angle to rotate files and thumbnails by 90,
1570 180, 270
1571 --rotate-thumb <ANGLE> rotate only thumbnails by 90, 180, 270
1572 --mtime (*) set file mtime according to DateTimeOriginal
1573 tag
1574
1575 Misc options:
1576 --dry-run do nothing, only print would have been done
1577 --use-ipc (*) rotate thumbnail via pipe, rather than via file
1578 -v increment debugging level by 1
1579 -h, --help display this help and exit
1580 --version output version and exit
1581
1582 (*) The options marked with this sign do not take arguments and can be negated,
1583 i.e. prefixed by 'no'. E.g. '--mtime' sets file mtime value, while '--nomtime'
1584 or '--no-mtime' disables setting it.
1585
1586 Consult the documentation for a full list of options.
1587
1588 ");
1589 } elsif ($v == 2) {
1590 pod2usage(-verbose => 2);
1591 }
1592 exit $exitcode;
1593 }
1594
1595 ########################################################################################
1596 # Usage : $name = template2name(...);
1597 # Purpose : builds file name according to the template
1598 # Returns : name as string
1599 # Parameters : $exifToolObj [ref] -
1600 # : $infoObj [ref] -
1601 # : $template [str] - the template to be used
1602 # : $fileNo [num] - counter for %c
1603 # : $fileName [str] - file name for %n and %e
1604 # : $counterSize [num] - the size of the counter for format (like "01", "0012")
1605 # : $angleSuffix [str] - suffix to add to the end of the rotated files
1606 # Throws : no exceptions
1607 # Comments : none
1608 # See Also : n/a
1609 sub template2name {
1610 my $exifToolObj = shift;
1611 my $infoObj = shift;
1612 my $template = shift;
1613 my $fileNo = shift;
1614 my $fileName = shift;
1615 my $counterSize = shift;
1616 my $angleSuffix = shift;
1617
1618 if (not defined $template) {
1619 die_renrot("Template isn't given!\n");
1620 }
1621
1622 my $timestamp = getTimestamp($exifToolObj, $infoObj);
1623 my @tm = ($timestamp =~ m/(\d\d(\d\d))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/);
1624 dbgmsg (4, "tm: @tm\n");
1625
1626 my $ExposureTime = "";
1627 my $FileNumber = 'NA';
1628 my $FNumber = "";
1629 my $ISO = "";
1630 my $WhiteBalance = "";
1631 my $fileNameOriginal = "";
1632 my $fileNameOriginalCounter = ""; # we can not use 0 as default value
1633
1634 if (defined $infoObj->{"FileNumber"}) {
1635 $FileNumber = $infoObj->{"FileNumber"};
1636 }
1637
1638 if (defined $infoObj->{"ExposureTime"}) {
1639 $ExposureTime = $infoObj->{"ExposureTime"};
1640 $ExposureTime =~ s/\//by/g;
1641 }
1642
1643 if (defined $infoObj->{"FNumber"}) {
1644 $FNumber = $infoObj->{"FNumber"};
1645 }
1646
1647 if (defined $infoObj->{"ISO"}) {
1648 $ISO = $infoObj->{"ISO"};
1649 }
1650
1651 if (defined $infoObj->{"WhiteBalance"}) {
1652 $WhiteBalance = $infoObj->{"WhiteBalance"};
1653 $WhiteBalance =~ s/[\s()]//g;
1654 }
1655
1656 if (defined $infoObj->{"RenRotFileNameOriginal"}) {
1657 $fileNameOriginal = $infoObj->{"RenRotFileNameOriginal"};
1658 } else {
1659 # Temporary stub to make renrot remember original file name for the formats
1660 # ExifTool writing to doesn't support.
1661 # Reported by Neil Hooey <nhooey@gmail.com>
1662 $fileNameOriginal = $infoObj->{"FileName"};
1663 }
1664
1665 # file name starts with letters and ends with digits
1666 if ($fileNameOriginal =~ m/^[[:alpha:]\-_]*(\d+)(\.[^\.]+)?$/) {
1667 $fileNameOriginalCounter = $1;
1668 }
1669
1670 my ($base, $ext) = splitext($fileName); # file name %n and extension %e
1671
1672 my @templatearea = split (//, $template);
1673 my %templatehash = (
1674 '%' => "%",
1675 '#' => "#",
1676 'a' => $angleSuffix,
1677 'C' => $fileNameOriginalCounter,
1678 'c' => sprintf($counterSize, $fileNo),
1679 'd' => $tm[3],
1680 'E' => $ExposureTime,
1681 'e' => $ext,
1682 'F' => $FNumber,
1683 'H' => $tm[4],
1684 'I' => $ISO,
1685 'i' => $FileNumber,
1686 'M' => $tm[5],
1687 'm' => $tm[2],
1688 'n' => $base,
1689 'O' => (splitext($fileNameOriginal))[0],
1690 'o' => $fileNameOriginal,
1691 'S' => $tm[6],
1692 'W' => $WhiteBalance,
1693 'Y' => $tm[0],
1694 'y' => $tm[1],
1695 );
1696 my $thename = "";
1697
1698 my $substroffset = 0;
1699 my $substrchar;
1700 my $tag_to_name_val = "";
1701 my $tag_to_name = "";
1702
1703 dbgmsg (4, "'$template' (length: ", scalar(@templatearea), ")\n");
1704 while ($substroffset < scalar(@templatearea)) {
1705 $substrchar = $templatearea[$substroffset++];
1706 if ($substrchar eq "%" and $substroffset < scalar(@templatearea)) {
1707 $substrchar = $templatearea[$substroffset++];
1708 if (defined $templatehash{$substrchar}) {
1709 $thename .= $templatehash{$substrchar};
1710 }
1711 } elsif ($substrchar eq "#" and $substroffset < scalar(@templatearea)) {
1712 $substrchar = $templatearea[$substroffset++];
1713
1714 until ($substrchar eq "#") {
1715 $tag_to_name .= $substrchar;
1716 $substrchar = $templatearea[$substroffset++];
1717 }
1718 if (defined $infoObj->{$tag_to_name}) {
1719 $tag_to_name_val = $infoObj->{$tag_to_name};
1720 dbgmsg (2, "Tag '$tag_to_name' = $tag_to_name_val\n");
1721 $tag_to_name_val =~ s/[\s()]//g;
1722 $tag_to_name_val =~ s/[\/\\:;\'\"]/_/g;
1723 $thename .= $tag_to_name_val;
1724 }
1725 $tag_to_name_val = $tag_to_name = "";
1726 } else { $thename .= $substrchar; }
1727 }
1728 return $thename;
1729 }
1730
1731 ########################################################################################
1732 #
1733 # MAIN() renames and rotates given files
1734 #
1735
1736 # Check for modules
1737 $isThereIM = 1 if (defined loadpkg("Image::Magick"));
1738
1739 getOptions();
1740 parseConfig($configFile, $config_opts);
1741 switchColor();
1742
1743 # redefining options set in configuration file with set via CLI ones
1744 Image::RenRot::Config->apply($config_opts);
1745
1746 dbgmsg (1, "Show what would have been happened (no real actions).\n") if ($dryRun != 0);
1747
1748 # Validate aggregation mode possible values
1749 my $aggr_mode = get_cfg_value($co_aggr, 'mode');
1750 if (not grep (/^$aggr_mode$/, ('none', 'delta', 'template'))) {
1751 warnmsg ("Aggregation mode isn't correct!\n");
1752 }
1753
1754 # Calculate ExifTool's verbosity
1755 my $exiftoolVerbose = ($verbose > $maxVerbosity) ? ($verbose - $maxVerbosity) : 0;
1756
1757 # ExifTool object configuration
1758 my $exifTool = new Image::ExifTool;
1759 $exifTool->Options(Binary => 1, Unknown => 1, DateFormat => '%Y%m%d%H%M%S', Verbose => $exiftoolVerbose);
1760
1761 chdir ($workDir) || die_renrot("Can't enter to $workDir!\n");
1762
1763 if ($subFileSet eq "") {
1764 # All things in ARGV will be treated as file names to process
1765 @files = @ARGV;
1766 } else {
1767 dbgmsg (2, "Reading file names for processing from file: $subFileSet\n");
1768 @files = Image::RenRot::FileUtil->getFileDataLines($subFileSet);
1769 chomp(@files);
1770 }
1771
1772 # if no file is given
1773 if (scalar(@files) == 0) {
1774 my $fileMask = "*" . $extToProcess;
1775 @files = grep { -f } glob ($fileMask);
1776 }
1777
1778 # independently of @files initialization doing this
1779 my @filenames = ();
1780
1781 foreach my $file (@files) {
1782 next if (not -f $file); # skip absent file or not a file
1783 next if (grep {/^$file$/} @excludeList); # skip excluded file
1784 push (@filenames, $file);
1785 }
1786
1787 # No file to process?
1788 if (scalar(@filenames) == 0) {
1789 fatalmsg ("No files to process!\n");
1790 exit 1;
1791 }
1792
1793 # Parse configuration file tag set
1794 foreach my $cKey (keys %$config_opts) {
1795 next if ($cKey !~ m/^tag(file)?#\d+#\d+$/); # skip not a tag or tagfile
1796 my %tag = str2hash($config_opts->{$cKey});
1797 foreach my $key (keys %tag) {
1798 $tags{$key} = $tag{$key};
1799 if ($cKey =~ m/^tagfile/) {
1800 dbgmsg (4, "Read data from '$tags{$key}{value}' for '$key'\n");
1801 $tags{$key}{value} = Image::RenRot::FileUtil->getFileData($tags{$key}{value});
1802 }
1803 }
1804 }
1805
1806 # Put command line arguments to appropriate tags
1807 $tags{'Comment'} = {value => Image::RenRot::FileUtil->getFileData($comfile)} if (defined $comfile);
1808 $tags{'UserComment'} = {value => $userComment} if (defined $userComment);
1809
1810 # Merge tags from configuration file with command line arguments
1811 map { $tags{$_} = $tagsFromCli{$_} } keys %tagsFromCli;
1812
1813 # Print parsed tags at debug level
1814 my @dbgTags = ();
1815 foreach my $key (sort (keys %tags)) {
1816 my $group = defined $tags{$key}{group} ? $tags{$key}{group} : "";
1817 my $value = defined $tags{$key}{value} ? $tags{$key}{value} : "";
1818 push (@dbgTags, "$key [$group] = $value");
1819 }
1820 dbgmsg (4, "Tags:\n", join("\n", @dbgTags), "\n") if (scalar(@dbgTags) > 0);
1821
1822 # Validate angle value
1823 if ((defined $rotateAngle and not grep(/^$rotateAngle$/, keys %rotangles)) or
1824 (defined $rotateThumbnail and not grep(/^$rotateThumbnail$/, keys %rotangles))) {
1825 fatalmsg ("Angle should be 90, 180 or 270!\n");
1826 exit 1;
1827 }
1828
1829 @files = sort @filenames;
1830 dbgmsg (4, "Pushed files(", scalar(@files), "):\n", join("\n", @files), "\n");
1831
1832 # Preparing the variable, which contains the format of the counter output
1833 my $counterSize;
1834
1835 if ($countFF != 0) {
1836 my $size = length((scalar(@filenames) - 1) * $countStep + $countStart);
1837 $counterSize = "%." . $size . "d";
1838 dbgmsg (1, "Counter size: $size (amount files in cache: ", scalar(@filenames), ")\n");
1839 } else {
1840 $counterSize = "%d";
1841 }
1842
1843 renRotProcess($exifTool, $counterSize);
1844
1845 contactSheetGenerator($exifTool) if ($isThereIM);
1846
1847 aggregationProcess($exifTool, $counterSize);
1848
1849 __END__
1850
1851 =head1 NAME
1852
1853 renrot - rename and rotate images according EXIF data
1854
1855 =head1 SYNOPSIS
1856
1857 renrot [OPTIONS] [[B<-->] FILE1 FILE2 ...]
1858
1859 =head1 DESCRIPTION
1860
1861 B<Renrot> is intended to work with a set of files containing EXIF data and
1862 can do two things to them -- rename and rotate. A set of files can be given
1863 either explicitly or using the B<--extension> option, which select the files
1864 with the given suffix. B<Renrot> operates on files in current working
1865 directory, unless given the B<--work-directory> option, which changes this
1866 default.
1867
1868 B<Renrot> renames input files using a flexible name template (which,
1869 among others, uses DateTimeOriginal and FileModifyDate EXIF tags, if they
1870 exist, otherwise names the file according to the current timestamp). Further,
1871 B<renrot> can aggregate files according to the shooting time period or to a
1872 given template.
1873
1874 Additionally, it rotates files and their thumbnails, as per Orientation EXIF
1875 tag. If that tag is absent, the program allows to set rotation parameters
1876 using B<--rotate-angle> and B<--rotate-thumb> command line options. This is
1877 currently implemented only for JPEG format.
1878
1879 The program can also place commentaries into the following locations:
1880
1881 =over
1882
1883 - Commentary tag from file (see B<--comment-file> option)
1884
1885 - UserComment tag from configuration variable (see L</TAGS> section)
1886
1887 =back
1888
1889 Personal details may be specified via XMP tags defined in a configuration
1890 file, see L</TAGS> section.
1891
1892 In addition, B<renrot> can aggregate all files in different directories,
1893 according to a given date/time pattern template, set with B<--aggr-template>.
1894
1895 =head1 OPTIONS
1896
1897 =over
1898
1899 =item B<-c> or B<--config-file> F<FILE>
1900
1901 Path to the configuration file.
1902
1903 =item B<-d> or B<--work-directory> F<DIR>
1904
1905 Define the working directory.
1906
1907 =item B<--exclude> F<FILE>
1908
1909 Specify files to exclude. Wildcards are not allowed. If a set of files is
1910 given, there must be as many occurrences of this option as there are files in
1911 the set.
1912
1913 =item B<--sub-fileset> F<FILE>
1914
1915 Get names of files to operate upon from F<FILE>. The file must contain a
1916 file name per line. This option is useful when you need to process only a
1917 set of X from Y files in the directory. If specified, the rest of files
1918 given in the command line is ignored.
1919
1920 =item B<-e> or B<--extension> I<EXTENSION>
1921
1922 Process the files with given I<EXTENSION> (JPG, jpeg, CRW, crw, etc).
1923 Depending on the operating system, the extension search might or might not be
1924 case-sensitive.
1925
1926 =item B<--mtime>, B<--no-mtime>
1927
1928 Defines whether to set the file's mtime, using DateTimeOriginal tag value.
1929 Use B<--no-mtime> to set it to current time stamp after processing.
1930
1931 =item B<--no-renrot> or B<--nochg>
1932
1933 Do not rename, rotate, tag and mtime images. It saves files from any changes
1934 while allows to do aggregation, contact sheet generation e.t.c.
1935
1936 =item B<--use-color>, B<--no-use-color>
1937
1938 Colorize output. This does NOT work under Windows.
1939
1940 =item B<--dry-run>
1941
1942 Do not do anything, only print would have been done.
1943
1944 =item B<-g> or B<--generate-thumb>
1945
1946 Generation and writing ThumbnailImage tag. The original value of the ThumbnailImage
1947 tag remains intact. To rewrite it you need to delete it first (look exiftool examples).
1948
1949 =item B<--use-ipc>, B<--no-use-ipc>
1950
1951 Rotate thumbnails using pipe, rather than files. This does NOT work under
1952 Windows.
1953
1954 =item B<-v>
1955
1956 Increase debugging level by 1. Debugging levels from 1 to 4 are internal
1957 levels, the levels from 5 till 9 are equivalent to levels 1-5 levels ExifTool
1958 with the maximum verbosity for B<renrot>.
1959
1960 =item B<-?> or B<--help>
1961
1962 Display short usage summary and exit.
1963
1964 =item B<--version>
1965
1966 Output version information and exit.
1967
1968 =back
1969
1970 =head1 B<AGGREGATION>
1971
1972 =over
1973
1974 =item B<--aggr-mode> I<MODE>
1975
1976 Run aggregation process in given I<MODE>. Possible values are: none, delta or
1977 template.
1978
1979 =item B<--aggr-delta> I<NUMBER>
1980
1981 Aggregation time delta, in seconds. Files with DateTimeOriginal and ones of
1982 the previous file delta, greater than B<--aggr-delta> are placed in the
1983 directories, with the names are constructed by concatenating the value of the
1984 B<--aggr-directory> option and the directory name counter.
1985
1986 =item B<--aggr-directory> F<DIR>
1987
1988 Aggregation directory name prefix (default is I<Images>), have to be on the
1989 same file system (or on the file system which supports symbolic links in case
1990 of virtual aggregation), relative to the current working directory or an
1991 absolute path.
1992
1993 =item B<-a> or B<--aggr-template> I<TEMPLATE>
1994
1995 File name template to use for file aggregation. Images are aggregated by
1996 date/time patterns. You may use combination of B<%d>, B<%H>, B<%M>, B<%m>,
1997 B<%S>, B<%Y>, and B<%y> meta-characters. The template can also be defined
1998 in the configuration file (see Aggregation Template variable). The default
1999 is I<%Y%m%d>. For the detailed description, refer to B<--name-template>
2000 option. For practical uses, see L</TEMPLATE EXAMPLES> section.
2001
2002 =item B<--aggr-virtual>, B<--no-aggr-virtual>
2003
2004 Defines virtualization for existent aggregation modes. The main effect of
2005 B<--aggr-virtual> is that any files to be aggregated remain untouched in their
2006 places, and relative symbolic links pointing to them are stored in the
2007 directory tree created. Use B<--no-aggr-virtual> to prevent virtualization.
2008
2009 =back
2010
2011 =head1 B<CONTACT SHEET GENERATOR>
2012
2013 =over
2014
2015 =item B<--contact-sheet>, B<--no-contact-sheet> or B<--cs>, B<--no-cs>
2016
2017 Create the contact sheet. Currently it works with ThumbnailImage EXIFs and the
2018 files defined as thumbnails (see the option B<--contact-sheet-thm>, below)
2019
2020 =item B<--contact-sheet-file> or B<--cs-file> F<FILE>
2021
2022 Base file name for montage files.
2023
2024 =item B<--contact-sheet-dir> or B<--cs-dir> F<DIR>
2025
2026 Temporary directory for montage (created in the begining and deleted at the
2027 end of the process)
2028
2029 =item B<--contact-sheet-thm> or B<--cs-thm>
2030
2031 Files for the montage are already thumbnails
2032
2033 =back
2034
2035 Options bellow are native ImageMagic montage options look ImageMagick
2036 documentation for montage options: I<montage --help> and
2037 I<http://www.imagemagick.org/>
2038
2039 Note please, for I<COLOR> use RGB triplets only like I<000> for the I<black>
2040 or I<F00> for the I<red>.
2041
2042 =over
2043
2044 =item B<--contact-sheet-tile> or B<--cs-tile> I<GEOMETRY>
2045
2046 Tile MxN (IM: -tile)
2047
2048 =item B<--contact-sheet-title> or B<--cs-title> I<STRING>
2049
2050 Set the title of the contact sheet (IM: -title).
2051
2052 =item B<--contact-sheet-bg> or B<--cs-bg> I<COLOR>
2053
2054 Background color (IM: -background).
2055
2056 =item B<--contact-sheet-bd> or B<--cs-bd> I<COLOR>
2057
2058 Border color (IM: -bordercolor).
2059
2060 =item B<--contact-sheet-mt> or B<--cs-mt> I<COLOR>
2061
2062 Frame color (IM: -mattecolor).
2063
2064 =item B<--contact-sheet-fn> or B<--cs-fn> I<STRING>
2065
2066 Render text with this font (IM: -font).
2067
2068 =item B<--contact-sheet-fl> or B<--cs-fl> I<COLOR>
2069
2070 Color to fill the text (IM: -fill).
2071
2072 =item B<--contact-sheet-lb> or B<--cs-lb> I<STRING>
2073
2074 Assign a label to an image (IM: -label).
2075
2076 =item B<--contact-sheet-fr> or B<--cs-fr> I<GEOMETRY>
2077
2078 Surround image with an ornamental border in N pixels (IM: -frame).
2079
2080 =item B<--contact-sheet-pntsz> or B<--cs-pntsz> I<NUMBER>
2081
2082 Font point size (IM: -pointsize).
2083
2084 =item B<--contact-sheet-shadow> or B<--cs-shadow>
2085
2086 Set the shadow beneath a tile to simulate depth (IM: -shadow).
2087
2088 =item B<--contact-sheet-thm-fl> or B<--cs-thm-fl> I<COLOR>
2089
2090 Color to fill the text in generated thumbnail.
2091
2092 =item B<--contact-sheet-thm-fn> or B<--cs-thm-fn> I<STRING>
2093
2094 Render the generated thumbnail text with this font (IM: -font).
2095
2096 =item B<--contact-sheet-thm-grfr> or B<--cs-thm-grfr> I<COLOR>
2097
2098 Generated thumbnail background gradient COLOR-from
2099
2100 =item B<--contact-sheet-thm-grto> or B<--cs-thm-grto> I<COLOR>
2101
2102 Generated thumbnail background gradient COLOR-to
2103
2104 =item B<--contact-sheet-thm-text> or B<--cs-thm-text> I<STRING>
2105
2106 Generated thumbnail text
2107
2108 =item B<--contact-sheet-rank> or B<--cs-rank>
2109
2110 Run ranking process according to the ranks defined with
2111 B<--contact-sheet-rank-file> The result is the colored frames of the
2112 thumbnails of contact sheets.
2113
2114 =item B<--contact-sheet-rank-file> or B<--cs-rank-file>
2115
2116 Path to the file with ranks. Its format is a "file rankcolor" per line.
2117 Filename separated from the color by space or tabulation.
2118
2119 =over
2120
2121 01.file.jpg red
2122
2123 02.JPG CornflowerBlue
2124
2125 03.jpg aquamarine
2126
2127 04.file.JPG green
2128
2129 =back
2130
2131 Only the files found in the file will be ranked.
2132
2133 =back
2134
2135 =head1 B<KEYWORDIZER>
2136
2137 =over
2138
2139 =item B<--keywords>, B<--no-keywords>
2140
2141 Whether to fill Keywords tag. Default is to not. Be careful, since with this option
2142 enabled, the existing keywords are rewriten. The keywords are taken from
2143 F<.keywords> file or file specified with option B<--keywords-file>.
2144
2145 =item B<-k> or B<--keywords-file> F<FILE>
2146
2147 Path to the file with keywords. Its format is a keyword per line. The CR and
2148 LF symbols are removed. Empty (only whitespace) lines are ignored. Any leading
2149 and trailing whitespace is removed. For example, the line C< _Test_ CRLF> is
2150 read as C<_Test_>.
2151
2152 =item B<--keywords-replace>, B<--no-keywords-replace>
2153
2154 Replace existing Keywords tag list rather than add new values to it. Default
2155 is not to replace.
2156
2157 =back
2158
2159 =head1 B<RENAMING>
2160
2161 =over
2162
2163 =item B<-n> or B<--name-template> I<TEMPLATE>
2164
2165 A template to use for creating new file names while renaming. It can also be
2166 defined in the configuration file (variable Name Template). The default is
2167 I<%Y%m%d%H%M%S>. For practical uses, see L</TEMPLATE EXAMPLES> section.
2168
2169 Interpreted sequences are:
2170
2171 =over
2172
2173 B<%%> a literal %
2174
2175 B<%#> a literal #
2176
2177 B<%C> Numeric part of the original file name. Implemented for the sake
2178 of cameras, that do not supply FileNumber EXIF tag (currently all makes,
2179 except I<Canon>). Such cameras generate file names starting with letters
2180 and ended with digits. No other symbols are allowed in file names, except
2181 C<->, C<.> and C<_>.
2182
2183 B<%c> Ordinal number of file in the processed file set (see also
2184 B<--counter-fixed-field> option).
2185
2186 B<%d> Day of month (01-31).
2187
2188 B<%E> The value of ExposureTime tag, if defined.
2189
2190 B<%e> Old file extension
2191
2192 B<%F> The value of FNumber tag, if defined.
2193
2194 B<%H> Hour (00-23).
2195
2196 B<%I> The value of ISO tag, if defined.
2197
2198 B<%i> FileNumber tag if exists (otherwise, it is replaced by string
2199 C<NA>).
2200
2201 B<%M> Minute (00-59).
2202
2203 B<%m> Month (01-12).
2204
2205 B<%n> Previous filename (the one before B<renrot> started processing).
2206
2207 B<%O> Base part of the original filename (see B<%o>). In other words, the
2208 first part from the beginning to the last dot character.
2209
2210 B<%o> The name file had before it was processed by B<renrot> for the first
2211 time. If the file was processed only once, the tag RenRotFileNameOriginal is
2212 set to the original file name.
2213
2214 B<%S> Second (00-59)
2215
2216 B<%W> The value of WhiteBalance tag, if defined.
2217
2218 B<%Y> Year with the century (1900, 1901, and so on)
2219
2220 B<%y> Year without a century (00..99)
2221
2222 You can use value of any EXIF tag to be included as name part. To do that you
2223 need to embrace tag name with sign B<"#">, while building name template
2224 (see L</TEMPLATE EXAMPLES>).
2225
2226 Be careful, since any binary EXIF (like ThumbnaiImage) can produce totally
2227 unexpected results.
2228
2229 =back
2230
2231 =item B<--no-rename>
2232
2233 Do not rename files (default is to rename them to YYYYmmddHHMMSS.ext)
2234
2235 =item B<--counter-fixed-field>, B<--no-counter-fixed-field>
2236
2237 Set fixed length for file counter, used in file name templates (see B<%c>).
2238 It is enabled by default. Use B<--no-counter-fixed-field> to undo its effect.
2239
2240 =item B<--counter-start> I<NUMBER>
2241
2242 Initial value for the file counter (default is I<1>)
2243
2244 =item B<--counter-step> I<NUMBER>
2245
2246 Step to increment file counter with (default is I<1>)
2247
2248 =back
2249
2250 =head1 B<ROTATING>
2251
2252 =over
2253
2254 =item B<-r> or B<--rotate-angle> I<ANGLE>
2255
2256 Define the angle to rotate files and thumbnails. Allowed values for I<ANGLE>
2257 are 90, 180 or 270. It is useful for files not having Orientation tag.
2258
2259 =item B<--rotate-thumb> I<ANGLE>
2260
2261 Rotate only thumbnails. Allowed values for I<ANGLE> are 90, 180 or 270 degrees.
2262 Use if the files which were already rotated, but their thumbnails were not.
2263
2264 =item B<--only-orientation>
2265
2266 Rotate by changing the value of Orientation tag, no real rotation will be
2267 made. The sequence of values to rotate an image from normal (0 degrees) by
2268 90 degrees clockwise is: 0 -> 90 -> 180 -> 270 -> 0. It means. set Orientation
2269 tag to 90cw after the first rotation, and increase that value by 90 each time
2270 the rotation is applied. For 270cw the rotation algorithm uses the reverted
2271 sequence. Rotation by 180cw triggers values in two pairs: 0 <-> 180
2272 and 90 <-> 270. This option cannot be applied to mirror values of Orientation
2273 tag.
2274
2275 =item B<--trim>, B<--no-trim>
2276
2277 Pass the C<-trim> option to L<jpegtran(1)>, to trim if needed. By default,
2278 trimming is enabled. Use B<--no-trim> to disable it.
2279
2280 =item B<--no-rotate>
2281
2282 Do not rotate images (default is to rotate according to EXIF data).
2283
2284 =back
2285
2286 =head1 B<TAG WRITER>
2287
2288 =over
2289
2290 =item B<--comment-file> F<FILE>
2291
2292 File with commentaries. It is a low priority alias to I<TagFile = Comment: FILE>.
2293
2294 =item B<--user-comment> I<STRING>
2295
2296 A low priority alias to I<--tag UserComment: STRING>
2297
2298 =item B<-t> or B<--tag> I<TAG>
2299
2300 See the section L</TAGS>, for the detailed description
2301
2302 =item B<--no-tags>
2303
2304 No user's defined tags will be written.
2305
2306 =back
2307
2308 =head1 B<TEMPLATE EXAMPLES>
2309
2310 The name template C<01.%c.%Y%m%d%H%M%S.%i.shtr-%E.f-%F.wb-%W.iso-%I> (where
2311 I<F> stays for FNumber, I<E> for ExposureTime, I<I> for ISO and I<W> for
2312 WhiteBalance) can produce the following names:
2313
2314 =over
2315
2316 01.0021.20030414103656.NA.shtr-1by40.f-2.8.wb-Auto.iso-160.jpg
2317
2318 01.0024.20040131230857.100-0078.shtr-1by320.f-2.8.wb-Auto.iso-50.jpg
2319
2320 01.0022.20000820222108.NA.jpg
2321
2322 =back
2323
2324 The name template
2325 C<01.%c.%Y%m%d%H%M%S.%i.shtr-#ExposureTime#.f-#FNumber#.wb-#WhiteBalance#.iso-#ISO#>
2326 can produce the following names:
2327
2328 =over
2329
2330 01.0021.20030414103656.NA.shtr-1_40.f-2.8.wb-Auto.iso-160.jpg
2331
2332 01.0024.20040131230857.100-0078.shtr-1_320.f-2.8.wb-Auto.iso-50.jpg
2333
2334 01.0022.20000820222108.NA.jpg
2335
2336 =back
2337
2338 The aggregation template C<%Y%m%d> produces the following aggregation:
2339
2340 these three files
2341
2342 =over
2343
2344 01.11.20030414103656.NA.jpg
2345
2346 01.12.20030414103813.NA.jpg
2347
2348 01.13.20030414103959.NA.jpg
2349
2350 =back
2351
2352 will be stored in the directory I<20030414>, and
2353
2354 =over
2355
2356 01.14.20040131130857.100-0078.jpg
2357
2358 01.15.20040131131857.100-0079.jpg
2359
2360 01.16.20040131133019.100-0080.jpg
2361
2362 =back
2363
2364 will be stored in the directory F<20040131>.
2365
2366 =head1 CONFIG
2367
2368 A configuration file can be used to set some variables. B<Renrot> looks for
2369 its configuration file, named F<renrot.conf>, in system configuration
2370 directories F</etc/renrot> and F</usr/local/etc/renrot>, and in subdirectory
2371 F<.renrot>. of the current user home directory. An alternate configuration
2372 file can also be explicitly given using the B<--config-file> option.
2373
2374 The configuration file consists of a set of case-insensive keywords and their
2375 values separated by equal sign. Each such keyword/value pair occupies a
2376 separate line. Boolean variables can have one of the following values: 0, No,
2377 False, Off, Disable for false, and 1, Yes, True, On, Enable for true.
2378
2379 The variables defined for use in the configuration file are, for example:
2380
2381 =over
2382
2383 =item B<mtime>
2384
2385 Set to C<Yes> for synchronize mtime with tags, otherwise set it to C<No>.
2386
2387 =item B<name template>
2388
2389 File name template (see B<--name-template>, for the description).
2390
2391 =item B<trim>
2392
2393 Set to C<Yes> to trim rotated images when using L<jpegtran(1)>.
2394
2395 =item B<aggregation mode>
2396
2397 Aggregation mode, possible values are: none, delta or template.
2398
2399 =item B<aggregation template>
2400
2401 Aggregation template, which defines the file aggregation (see
2402 B<--aggr-template>, for the description).
2403
2404 =item B<aggregation virtual>
2405
2406 Defines virtualization for the existing aggregation modes (see the
2407 B<--aggr-virtual> option).
2408
2409 =item B<Tag>, B<TagFile>
2410
2411 Refer to the section L</TAGS>, for the detailed description
2412
2413 =item B<include>
2414
2415 Include the named file.
2416
2417 =back
2418
2419 =head1 TAGS
2420
2421 A I<TAG> is defined by the following combination: I<TagName [Group]: 'value'>.
2422 The defined tags are selected to be set and writen to the EXIF tree using
2423 the command line option B<--tag> and/or configuration file options B<Tag>.
2424
2425 The syntax of the command line option B<--tag> is:
2426
2427 =over
2428
2429 B<--tag> I<TagName [Group]: 'value'>
2430
2431 =back
2432
2433 The syntax of the configuration file option B<Tag>:
2434
2435 =over
2436
2437 B<Tag> = I<TagName [Group]: 'value'>
2438
2439 =back
2440
2441 The parameters I<TagName> and I<Group> are passed to ExifTool as is. The
2442 name of the group must be enclosed in square brackets. Its I<value> (after
2443 the semicolon) can be enclosed in single quotes.
2444
2445 The TagFile keyword allows to set multi-line tags from a file. Its syntax is:
2446
2447 =over
2448
2449 B<TagFile> = I<TagName [Group]:> F<FILE>
2450
2451 =back
2452
2453 The following table summarizes the tags that can be used with the B<--tag>
2454 option and B<Tag> keyword:
2455
2456 =over
2457
2458 =item B<Copyright>
2459
2460 Copyright notes.
2461
2462 =item B<Comment>
2463
2464 General comment.
2465
2466 =item B<UserComment>
2467
2468 Anything you would like to put as a comment.
2469
2470 =item B<CreatorContactInfoCiAdrCity>
2471
2472 A city tag.
2473
2474 =item B<CreatorContactInfoCiAdrCtry>
2475
2476 A country tag.
2477
2478 =item B<CreatorContactInfoCiAdrExtadr>
2479
2480 Extended address (usually includes street and apartment number).
2481
2482 =item B<CreatorContactInfoCiAdrPcode>
2483
2484 Zip code.
2485
2486 =item B<CreatorContactInfoCiAdrRegion>
2487
2488 Region.
2489
2490 =item B<CreatorContactInfoCiEmailWork>
2491
2492 Email.
2493
2494 =item B<CreatorContactInfoCiTelWork>
2495
2496 Phone number.
2497
2498 =item B<CreatorContactInfoCiUrlWork>
2499
2500 URL.
2501
2502 =back
2503
2504 Additionally, you can add any known tag here, using B<Tag> or B<TagFile>
2505 options as described above.
2506
2507 =head1 FILES
2508
2509 =over
2510
2511 =item
2512
2513 The main configuration file B<renrot.conf> is searched in the following
2514 locations (in the order of their appearance):
2515
2516 =over
2517
2518 =item B<~/.renrot/>
2519
2520 directory under user's home place
2521
2522 =item B</usr/local/etc/renrot/>
2523
2524 system directory
2525
2526 =item B</etc/renrot/>
2527
2528 system directory
2529
2530 =back
2531
2532 Take into consideration, the files which are found will be loaded and
2533 parsed in the order when user's configuration has maximal priority.
2534
2535 =item
2536
2537 Other configuration files with additional options could be included into
2538 main file:
2539
2540 =over
2541
2542 =item B<colors.conf>
2543
2544 colors setup for different output facilities
2545
2546 =item B<tags.conf>
2547
2548 different tags setup
2549
2550 =back
2551
2552 =item
2553
2554 The following files could be placed locally in the working directory:
2555
2556 =over
2557
2558 =item B<.keywords>
2559
2560 file with keywords (see B<--keywords-file>)
2561
2562 =item B<.rank>
2563
2564 file with ranks (see B<--contact-sheet-rank-file>)
2565
2566 =back
2567
2568 =back
2569
2570 =head1 BUGS
2571
2572 If you found some bug or have some nice propositions, you are welcome.
2573 Additionally, please, read the section RESTRICTIONS in file README.
2574
2575 =over
2576
2577 =item 1.
2578
2579 It seems that on FreeBSD 6, Perl versions 5.8.x exhibits a bug that causes
2580 B<renrot> to crash. The overal amount of memory for the perl process
2581 increasing up to the user datasize limit (in FreeBSD default is 512M).
2582
2583 It seems to be a bug in perl's own malloc implementation. The bug should not
2584 occur in any perl which uses the system malloc (unless the system malloc has
2585 the same bug).
2586
2587 B<renrot> aborts with the error message:
2588
2589 =over
2590
2591 Out of memory during "large" request for XXXX bytes ...
2592
2593 =back
2594
2595 This, however, does not happen with Perl v.5.6.x
2596
2597 =item 2.
2598
2599 Some versions of ImageMagick render contact sheets incorrectly. it seems
2600 it is the question to ImageMagick.
2601
2602 =back
2603
2604 =head1 AUTHORS
2605
2606 Copyright 2005-2012, Zeus Panchenko, Andy Shevchenko.
2607
2608 This program is free software; you can redistribute it and/or modify it under
2609 the same terms as Perl itself.
2610
2611 =head1 SEE ALSO
2612
2613 L<Image::ExifTool(3pm)|Image::ExifTool>,
2614 L<exiftool(1)>,
2615 L<jpegtran(1)>,
2616 L<Image::Magick(3pm)|Image::Magick>
2617
2618 =cut

Properties

Name Value
svn:eol-style native
svn:executable *
svn:keywords Author Date Id Revision

Send suggestions and bug reports to Sergey Poznyakoff
ViewVC Help
Powered by ViewVC 1.1.20