renrot-1.2.0/000755 001751 000024 00000000000 12154670231 013151 5ustar00zeusstaff000000 000000 renrot-1.2.0/MANIFEST000644 001751 000024 00000002040 12154670231 014276 0ustar00zeusstaff000000 000000 AUTHORS ChangeLog Makefile.PL MANIFEST This file NEWS User friendly change log README README.russian Small description for russians users README.Windows Installation instructions for windows users renrot RenRot script renrot.spec RPM spec file TODO etc/colors.conf Colorization scheme etc/copyright.tag Text to put to Copyright tag etc/renrot.conf Sample of configuration file etc/tags.conf Strings to fill appropriate tags lib/Image/RenRot/TimeUtil.pm lib/Image/RenRot/Util.pm lib/Image/RenRot/Config.pm lib/Image/RenRot/Logging.pm lib/Image/RenRot/FileUtil.pm sample/dotkeywords Sample of a .keywords file sample/dotrank Sample of a .rank file META.yml Module meta-data (added by MakeMaker) renrot-1.2.0/NEWS000644 001751 000024 00000025674 12154667731 013701 0ustar00zeusstaff000000 000000 * 1.2.0 * The option --keywordize was renamed to --keywords. The behaviour of --dry-run option is fixed (there are bugs #539346 in Debian and #322623 in Ubuntu). The command line framework now understands a new kind of the option string. * 1.1.0 * The current post release contains two small fixes. One of them is for a documentation formatting. Hence a manual page looks better and more understandable. Another fix is addressed to the configuration file parser. So, a speed of parsing is increased a little bit. * 1.1 * The dying is less informative now, when verbosity level set to zero. All messages which are not related to the 'info' or 'process' category will be put to the standard error stream. * 1.1rc3 * The option --aggr-virtual-directory was removed, you should use --aggr-directory instead. Now the '#' (hash) symbol could be used at the begin of given color value in a command line. The tree for an aggregation is created automatically. Documentation has been updated accordingly. * 1.1rc2 * Serveral bugs were fixed in the aggregation framework. The hardcoded path separator symbols are removed. This should bring proper path interpretation on other than Unix operating systems. The virtual aggregation is worked now for delta aggregation mode as well. In according to the last changes the documentation has been updated. Additionally a small optimization was done in the contact sheet generator. * 1.1rc1 * There is major improvement of the existent functionality. In this release the thumbnail generation has been added (see --generate-thumb option). An aggregation could be performed with absolute path now, but on the same file system. A thumbnail image for the contact sheet generator could be now in any supported format by ImageMagick. The plain documentation was expanded especially in contact sheet generator part. The sample directory has been created with the dotrank and dotkeywords files. Additionally a few bugs were fixed. The option --no-ren-rot-tag-mtm was renamed to the --no-renrot. Also the debug messages are prepended by a calling function name. * 1.0 * A major upgrade was done. The contact sheet generator has been implemented. This new feature requires the Image::Magick Perl module at run-time. In addition to the contact sheet generator framework, the rank functionality has been added. See the applied manual to get details for using it. The configuration parameter "use color" is turned off by default. * 0.25 * Several bugs were fixed after the last release. The "tag" target in the Makefile works properly now. The bug with clearing all EXIF tags (when --no-backup option is given) was possibly fixed as well. The configuration parameter "use color" has been added with the default value set to "yes". However, the --color option was dropped and should not be used anyway. The processing message look has been tuned: now it shows "(m of n)" files processed. * 0.24 * The colorization for output has been implemented. The options --use-color and --color were added. A new configuration file with a user's color scheme called colors.conf would be placed in the /etc/renrot or in a similar directory. * 0.23 * The additional template sequences %C, %O, and %o had been added. They represent the original filename counter, base part, and full copy of the original filename respectively. A new tag, RenRotFileNameOriginal, is written after the first pass by renrot. On the Win32 platform, the home path is taken from the environment variable USERPROFILE instead of HOME. The IPC support for rotating thumbnails has been added. The output messages of the usage function have been reformatted. * 0.22 * A new option --no-tags has been added. Use it when you don't want to write EXIF tags. The 'no'-prefixed aliases to the 'no-' options were added: --norename, --norotate, and --notags. The main functionality has been expanded with the keywordizer procedure. See the applied manual to get details for using it. * 0.21.1 * The new template parameters %n and %e have been added, representing the original name and the extension of the given file, respectively. A new --backup option has been added, and --no-backup should be used if you don't want to backup the original files before rotating. Additionally, the online (-h) documentation has been reduced to approach 80 characters wide. The bug with counter in has been fixed. * 0.21 * The new one-letter aliases had been added (see -n, -a, and -t in the manual). The aggregation directory argument is checked for a level of path (the current and multilevel directory isn't supported now). The --dry-run option now works on the almost general functions. The old style tag options is no longer supported. The tags documentation has been expanded. Additionally, the CONFIG section has been partially rewritten. * 0.21rc3 * The issue where the parsed data of the configuration files was not used was fixed. The RESTRICTIONS and BUGS sections have been expanded with the description of the discovered problem on FreeBSD 6 with Perl 5.8.7/5.8.8. * 0.21rc2 * The include directive for the configuration file has been added. The configuration file is split into the main part and the tag definitions. The new configuration files are relocated to the their own directory on a filesystem. The configuration keywords were expanded (a new TagFile directive was added). The comment file configuration variable is deprecated now. Two issues in the tag parser were fixed. The virtualization of an aggregation has been implemented. The documentation has been expanded according to above changes. A detailed description of the TAGS section was written. * 0.21rc1 * A new tag parser and option style are represented in this version (the old style of the tag prefixed options still works, but is deprecated). The EXIF data for a rotated file is rewritten (used ExifTool rather than jpegtran). The ExifTool requirement was updated due to a new implementation of WriteInfo(). The documentation has been expanded with a new part in README.Windows, BUGS, and TAGS sections have been added to the manual. * 0.20 * The deprecated options (--aggr, --aggr-dumb, and the "delta" keyword in the --aggr-template) and their configuration variables were removed. The old style of the configuration file is not supported. An extension is no longer used for the file set. A new "trim" variable has been added to the configuration file (represented by --trim in the command line). The script writes its own XMP tags to the RenRot called group. The default aggregation directory value has been changed, and the dot delimeter is added to the directory name before a counter. The documentation has been expanded according to the last changes. * 0.20rc3 * The --aggr-dumb option and related configuration variable are deprecated (use --dry-run instead). The --aggr switcher and 'delta' keyword in the --aggr-template option are deprecated as well (use --aggr-mode='none', 'delta', or 'template'). The aggregation mode can be defined via the configuration file. The start and step counter values can be derived by the --counter-start and --counter-step command line options. A new option --counter-fixed-field provides a fixed field when a counter is used in a template. * 0.20rc2 * A new style of configuration file was described in README. This style is used by default. Some useful comments were added to the sample configuration file. The TODO file was cleaned up. The check for configuration file presence has been restored. * 0.20rc1 * This release presents a new style of the configuration file. The new option, --user-comment, allows the UserComment tag to be set from the command line. The --aggr-dir option alias has been removed. The 180 degree may now be used with the --rotate-angle or --rotate-thumb options, and their parameters are checked for correctness. Special symbols in filenames should no longer cause problems. The --work-directory options should work properly now. The documentation was expanded with an explanation of Orientation tag rotation. * 0.19.3 * The bug which occurred when rotating filenames containing special symbols has been fixed. The empty --extension argument is no longer available. A small optimization has been implemented. No attempt is made to process absent files. Smart Orientation tag rotation has been implemented for the rotation process. * 0.19.2 * The --rotate and --ext option aliases have been removed, and -aggr-dir has been moved to deprecated (use --aggr-directory instead). A small optimization in the aggregation process function has been implemented. Simplified counter size calculation has been added. The hard-coded 'jpg' extension has been changed to a lowercased value of the --extension command line option. The '.ext' form of extension is recognized. The documentation has been expanded with NEWS (a user-friendly changelog). * 0.19.1 * Several bugs were fixed. The thumbnail image did not rotate correctly. The --file option is obsolete because it's useless (command line arguments without switchers are also interpreted as files). Directories in aggregation mode use counter size as well. A new option --exclude provides an exclude list of files that will not be processed by the script. The documentation has been expanded with a small description in Russian. * 0.19 * Aggregation was implemented (moving files to directories according date/time patterns). Dynamic counter size (in decimal digits) was implemented. No puts a single letter for %E, %F, %I, or %W when the tag is absent. Now you may operate with a fileset instead of the whole directory. An mtime setting was added to the configuration file and switched on by default (use --no-mtime to negate this). --rotate and --ext options are deprecated. The FileModifyDate tag is written when the DateTimeOriginal tag is absent. A software tag with versions of tools is written to the passed file. * 0.16.1 * When a new file name is generated and a file is present on the filesystem with same name, the last would be lost in previous version. This bug has been fixed. The spelling in the documentation was corrected. * 0.16 * The new template ideology was implemented for image file naming. The user may choose any configuration of certain parameters set for a generated file name. A new option was added for setting the mtime file attribute equal to the the DateTimeOriginal tag or the current timestamp when the last is invalid. The documentation was expanded and partialy rewritten according to these enhancements. * 0.15.1 * A --work-directory option was added. The output procedure has been rewritten and more debugging information is posted to stderr instead of stdout. Values in the configuration file are quoted by single quotes instead of double (due to special symbol issues such as "@" in email). * 0.15 * This release adds caching filenames for the working directory to avoid processing the processed files (processed files could be rescanned while processing a large number files in the directory). The ISO tag has been chosen instead of CameraISO, since not all cameras support the latter. * 0.14.2 * First published release. renrot-1.2.0/renrot.spec000644 001751 000024 00000007062 12154670016 015344 0ustar00zeusstaff000000 000000 %define rcver %{nil} %define dotrc %{nil} Name: renrot Version: 1.2.0 Release: 3%{?dotrc}%{?dist} License: Artistic 2.0 Group: Applications/Multimedia Summary: A program to rename and rotate files according to EXIF tags URL: http://puszcza.gnu.org.ua/projects/renrot/ Source0: ftp://download.gnu.org.ua/pub/release/renrot/%{name}-%{version}%{?rcver}.tar.gz BuildArch: noarch BuildRequires: perl(ExtUtils::MakeMaker) BuildRequires: perl(Getopt::Long) >= 2.34 BuildRequires: perl(Image::ExifTool) >= 5.72 Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version)) Requires: perl(Image::Magick) Requires: /usr/bin/jpegtran %description Renrot renames files according the DateTimeOriginal and FileModifyDate EXIF tags, if they exist. Otherwise, the name will be set according to the current timestamp. Additionally, it rotates files and their thumbnails, accordingly Orientation EXIF tag. The script can also put commentary into the Commentary and UserComment tags. Personal details can be specified via XMP tags defined in a configuration file. %prep %setup -q -n %{name}-%{version}%{?rcver} %build %{__perl} Makefile.PL INSTALLDIRS=vendor make %install make pure_install PERL_INSTALL_ROOT=$RPM_BUILD_ROOT # Fix renrot permissions chmod 755 $RPM_BUILD_ROOT%{_bindir}/renrot # install sample configuration files mkdir -p $RPM_BUILD_ROOT%{_sysconfdir}/%{name} install -m644 etc/colors.conf $RPM_BUILD_ROOT%{_sysconfdir}/%{name} install -m644 etc/copyright.tag $RPM_BUILD_ROOT%{_sysconfdir}/%{name} install -m644 etc/renrot.conf $RPM_BUILD_ROOT%{_sysconfdir}/%{name} install -m644 etc/tags.conf $RPM_BUILD_ROOT%{_sysconfdir}/%{name} # Remove some unwanted files find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} \; find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} \; %triggerin -- renrot < 0.21-0.2.rc2 if [ -f %{_sysconfdir}/renrot.rc ]; then /bin/mkdir -p %{_sysconfdir}/%{name} /bin/mv -fb %{_sysconfdir}/renrot.rc %{_sysconfdir}/%{name}/renrot.conf fi %files %doc AUTHORS ChangeLog NEWS README TODO %lang(ru) %doc README.russian %{_bindir}/renrot %{_mandir}/man1/*.1* %dir %{_sysconfdir}/%{name} %config(noreplace) %{_sysconfdir}/%{name}/colors.conf %config(noreplace) %{_sysconfdir}/%{name}/copyright.tag %config(noreplace) %{_sysconfdir}/%{name}/renrot.conf %config(noreplace) %{_sysconfdir}/%{name}/tags.conf %{perl_vendorlib}/Image/ %changelog * Sun Jul 15 2012 Andy Shevchenko - explicitly require perl-Image-Magick * Mon Jun 20 2011 Petr Sabata - 1.1-3 - Perl mass rebuild - Dropping now obsolete Buildroot and defattr - Commenting Requires(hint) out since fedpkg refuses to work with it * Thu Jul 01 2010 Adam Tkac - 1.1-2 - Require /usr/bin/jpegtran instead of libjpeg; compatible with both libjpeg and libjpeg-turbo * Mon Oct 06 2008 Andy Shevchenko - 1.1-0.3.rc3 - update to 1.1rc3 - change License to Artistic 2.0 accordingly to mainstream - update URLs - require (optional) Image::Magick * Tue Sep 04 2007 Andy Shevchenko 0.25-3.1 - Fix License tag - Add BuildRequires: perl(ExtUtils::MakeMaker) * Tue Aug 22 2006 Andy Shevchenko - add colors.conf * Wed Jun 07 2006 Andy Shevchenko - relocate configuration to %_sysconfdir/%name * Sat Jun 03 2006 Andy Shevchenko 0.20-2 - remove BR: perl - fix renrot permissions * Mon May 15 2006 Andy Shevchenko - install rc-file * Tue Apr 18 2006 Andy Shevchenko - initial package renrot-1.2.0/ChangeLog000644 001751 000024 00000057423 12154667731 014751 0ustar00zeusstaff000000 000000 $Log$ Revision 1.182 2007/07/22 17:48:03 zeus renrot URL added to the watermark of contact sheet generated. Revision 1.181 2007/07/22 17:23:21 zeus version number changed. gradient fill added to the thmbgen(). watermark added to the top left corner of each contact sheet file. Revision 1.180 2007/07/22 09:56:38 zeus written own contact sheet files numeration engine, instead of ImageMagick native engine. Revision 1.179 2007/07/06 19:59:55 zeus version changed. Revision 1.178 2007/07/04 20:05:41 zeus check for availability of Image::Magick was added. EXIF writing to the montage files was added. some debug and error messages were added. Revision 1.177 2007/06/28 12:18:31 zeus thmgen() - thumbnail stub generator function added. If the image has no thumbnailimage, than thmbgen() generates stub image to place instead of the absent one. Revision 1.176 2007/06/25 18:55:51 zeus Default behaviour changed to --no-use-color. contactSheetGenerator() added, POD for it added. Revision 1.175 2007/01/13 16:01:46 andy Split our tag writting to separate function. Revision 1.174 2006/12/24 11:54:26 zeus RenRotFileNameOriginal cleanin bug fixed. The tag writing moved from renameFile() to renRotProcess(). Revision 1.173 2006/12/24 09:21:02 zeus RenRotFileNameOriginal tag related bug report added to TODO. Revision 1.172 2006/10/29 17:03:08 andy Changing/fixing/adding URLs across README. Revision 1.171 2006/10/12 09:53:35 zeus TODO edited on account of plugins. Revision 1.170 2006/10/09 15:16:46 andy Get file list from file (--sub-fileset). Revision 1.169 2006/10/09 12:35:16 andy Modify TODO according to conference.osdn.org.ua 2006 visitor wishes. Revision 1.168 2006/10/09 12:25:01 andy sync with 0.25 Revision 1.167 2006/10/06 11:50:36 andy Possible fix bug with clearing EXIFs when --no-backup is given Revision 1.166 2006/09/09 20:23:43 zeus Now "use color = Yes" is default. Revision 1.165 2006/09/09 19:14:51 zeus Processing message look tunned, now it shows (m of n) file processed. Revision 1.164 2006/09/05 05:41:11 andy Move main() to the end of file. Refactoring: configOptions -> cfgOpts. Revision 1.163 2006/09/05 05:07:47 andy Remove --color. Add 'use color' to configuration file. Revision 1.162 2006/09/02 18:29:20 andy Optimize hash parser. Revision 1.161 2006/08/22 10:16:07 andy Sync with 0.23 Revision 1.160 2006/08/10 09:28:50 zeus --include-file option implementation task written to TODO file. Revision 1.159 2006/08/09 07:29:31 zeus original file name counter template sequence moved to the "%C" and the base part of the original file name to "%O". code optimization around the template sequences. Revision 1.158 2006/08/08 11:15:27 zeus colorization for output implemented, the option --test-use-color Revision 1.157 2006/08/08 08:41:31 zeus Template sequence "%O" was added. Revision 1.156 2006/08/03 21:23:26 zeus parseConfig() code optimized. Revision 1.155 2006/08/03 20:27:33 zeus now win32 platform is considered in parseConfig(), the variable USERPROFILE instead of HOME. Revision 1.154 2006/08/03 10:26:25 zeus Template sequence "%o" was added. Revision 1.153 2006/08/03 10:09:55 zeus Tag RenRotFileNameOriginal writing was added. Revision 1.152 2006/07/21 07:02:39 zeus POD for --sub-fileset edited. Revision 1.151 2006/07/20 06:26:50 zeus --sub-fileset option is implemented. Revision 1.150 2006/07/18 18:36:52 andy --test-speed -> --test-use-ipc (and 'test speed' -> 'test use ipc'). Split rotateThumbnail() to its own, rotateByJpegtran(), and thumbWriter(). Revision 1.149 2006/07/18 13:58:05 zeus option --test-speed for thumbnail rotate, via pipe, speeding added. Revision 1.148 2006/07/16 16:59:44 andy Release as 0.22. Revision 1.147 2006/07/16 10:02:12 andy Remove fatal case when keywords file isn't exist. Fix return value in the getFileDataLines() and keywordizer(). Now skip empty keywords, and remove tail \r if occurs. Add 'no'-prefixed aliases to exist 'no-' options: --norename, --norotate. New option --no-tags (with alias --notags) switches tags writting. Revision 1.146 2006/07/13 13:07:39 zeus Tags initialization moved from tagWriter() to the renRotProcess(). Revision 1.145 2006/07/13 11:41:10 andy TODO cleanup. --keyword-file -> keywords-file. Minor optimization in keywordizer code was added. Revision 1.144 2006/07/13 10:08:31 zeus option --keywords-replace is added. Revision 1.143 2006/07/13 09:44:44 zeus Bugfix, tag Keywords was accumulated in the loop rather than written once. Revision 1.142 2006/07/08 05:04:24 andy Optimize keywordizer code. Should be checked. Revision 1.141 2006/07/07 11:43:25 zeus keywordizer() added, managed via CLI and config file options --keywordize and --keyword-file. Revision 1.140 2006/07/05 11:43:26 andy Release as 0.21.1. Revision 1.139 2006/07/05 06:20:54 andy Implement --backup option. Small cutting of usage() messages. Revision 1.138 2006/06/26 09:09:20 zeus %n and %e template patters've being implemented. Revision 1.137 2006/06/21 19:36:51 zeus TODO edited (%n,%e & virtual renaming) Revision 1.136 2006/06/19 11:19:08 zeus online documentation (option -h) tailored. Revision 1.135 2006/06/14 20:40:19 zeus in renameFile(), $newname look changed on opposite in case of it's existance. Revision 1.134 2006/06/12 08:03:01 andy Release as 0.21. Revision 1.133 2006/06/12 07:42:14 andy Check for directory tree in aggregation arguments. Now it isn't possible. Remove support of old style tag options. Revision 1.132 2006/06/11 14:05:38 andy Use $dryRun in the rotateOrient(), renameFile(), mtimeSet(), and tagWriter(). Revision 1.131 2006/06/10 06:51:13 zeus tag GPSAltitudeRef added to etc/tags.conf Revision 1.130 2006/06/09 18:26:54 andy Add the one-letter aliases to --name-template, --aggr-template, and --tag options. Change preamble in CONFIG section for drop possible misunderstanding. Add two tags to TAGS section of manual. Revision 1.129 2006/06/09 08:21:07 andy Fix unusing included files. Add 'include' description to POD. Revision 1.128 2006/06/09 07:44:18 zeus TagFile config option added to the POD. Revision 1.127 2006/06/08 17:34:31 andy Fix configuration file parser (parsed data wasn't used). Release as 0.21rc3. Revision 1.126 2006/06/08 14:08:09 zeus Information concerning the perl 5.8.7 and 5.8.8 behaviour on FreeBSD 6, while processing large amount of files, was added to the POD and README. Revision 1.125 2006/06/08 06:49:48 andy Release as 0.21rc2. Revision 1.124 2006/06/08 06:14:54 andy Implement include configuration directive. Split configuration file to main and tags. Revision 1.123 2006/06/07 13:23:54 andy relocate configuration to /etc/renrot/. Revision 1.122 2006/06/06 11:34:38 andy Use getFileData() in rotateThumb(). Revision 1.121 2006/06/06 11:17:07 zeus Virtualization of aggregation has been implemented. POD, README, renrot.rc are edited. Revision 1.120 2006/06/06 08:51:58 andy TagFile configuration option was added. Any tag could be filled by file content. "comment file" configuration variable are deprecated by new TagFile. Minor changes at TAGS section. Revision 1.119 2006/06/05 21:29:58 andy Fix tag configuration option parser (no collision with tagNNN, where NNN - numberF). Revision 1.118 2006/06/05 14:23:34 zeus POD edited. Detailed section TAGS description added. Revision 1.117 2006/06/05 06:34:31 andy Fix logic for empty given tags (now they should be removed from EXIF). Revision 1.116 2006/06/04 21:02:50 andy Implemented new tag parser and option style. Make old style of tag prefixed options work, but it deprecated since now. Release as 0.21rc1. Revision 1.115 2006/06/04 15:02:59 andy Added INSTALLATION ON PDA section to README.Windows (still theoretical). TODO has been modified. Fedora Extras Team credit is added to README. New section BUGS in the manual is added. Splited tag option description to separate manual section. Revision 1.114 2006/06/04 08:21:01 andy Configuration file tag variable parser has been added. Fix usage of undefined group in tagParser(). Revision 1.113 2006/06/03 15:25:03 andy Fix spec according Fedora Extras Review. First part of tags filling implementation. Revision 1.112 2006/06/02 13:06:02 andy Update ExifTool requirement due to new implementation of WriteInfo(). Revision 1.111 2006/06/01 22:17:59 zeus exifWriter() idealogy a bit changed. rotateImg() a bit tunned. Revision 1.110 2006/06/01 13:12:03 zeus EXIF data copying code rewritten. Now $exiftool->SetNewValuesFromFile() is used rather than jpegtran -copy all Revision 1.109 2006/06/01 08:21:39 zeus in delta aggregation mode, dir counter now delimited with dot from dir name. Revision 1.108 2006/06/01 07:12:08 andy TODO is cleaned up. Remove underscore symbol from RenRot unique tags. XMP-RenRot -> RenRot. Revision 1.107 2006/05/31 21:08:23 zeus XMP-RenRot namespace was added (tags RenRot_ProcessingTimestamp, RenRot_Version and RenRot_URL). README and POD a bit polished. Revision 1.106 2006/05/31 10:52:23 zeus Description of --counter-fixed-field, --counter-start and --counter-step added to the usage() and POD. Revision 1.105 2006/05/31 06:45:03 andy Refactoring (counterprefixsize -> counterSize, anglesuffix -> angleSuffix, renameFile() -> renameImg()). Split MAIN() to main part, renRotProcess() and renameFile(). No reset incorrect aggregation template, only ignore it with warning and error. Change default aggregation directory value to 'Images'. Add trim variable to the configuration file. Group --mtime/--no-mtime and --trim/--no-trim in manual. Expand CONFIG section by trim and aggregation mode descriptions. Revision 1.104 2006/05/30 21:16:23 zeus the option --trim added Revision 1.103 2006/05/30 18:37:11 andy Remove extension usage when file set is given. Revision 1.102 2006/05/30 06:27:31 andy Remove deprecated code. Revision 1.101 2006/05/29 19:00:01 andy Release as 0.20rc3. Revision 1.100 2006/05/29 14:44:35 zeus bugfix in aggregation mode delta section. Revision 1.99 2006/05/29 06:52:19 andy Deprecate --aggr-dumb and related configuration variable, use --dry-run instead. Revision 1.98 2006/05/28 21:37:34 andy Implement start and step counter values (options --counter-start and --counter-step). Use fixed field or not when counter is printed (--counter-fixed-field, is 'on' by default). Inject aggregation mode option to configuration file and command line. Deprecate --aggr switcher and 'delta' keyword in --aggr-template, use --aggr-mode='none', 'delta' or 'template' instead. Revision 1.97 2006/05/28 20:36:18 zeus bugfix. leading "F" have to be in case of %F but not %i in template2name() Revision 1.96 2006/05/27 07:11:14 andy Release as 0.20rc2 (documentation fix). Revision 1.95 2006/05/26 20:22:38 andy TODO cleanup. Start tag writer as separate task (new option --tag, but still unworked). Revision 1.94 2006/05/25 07:02:51 andy Fix NEWS spelling according to FreshMeat announce. New configuration file style is described in README. Use new configuration file style by default. Add some needed comments to renrot.rc. Revert check for configuration file presence in parseConfig(). Revision 1.93 2006/05/24 21:30:13 andy Release as 0.20rc1. Public testing version. Revision 1.92 2006/05/24 21:12:37 andy Don't require and set available empty extension for file set. Validate angle value for --rotate-angle and --rotate-thumbnail. Don't use re with extension - it cause problem with special symbols in file name. Should be fixed --work-directory parameter handling (BR by Jan Vereecke). Revision 1.91 2006/05/24 03:43:04 andy New configuration file style is imlemented. Switch to new style by --no-old-config (default 'Yes'). Use --user-comment="comment" or "Tag UserComment = 'comment'" to add UserComment tag (formerly COMMENTARY). Now "comment file = '/patch/to/commentary'" in configuration sets commentary filename. Revision 1.90 2006/05/22 18:22:27 andy Implement the configuration file parser. Revision 1.89 2006/05/21 19:01:46 andy Write more explanation of Orientation tag rotation in README and manual. Add new restriction to related section of README. Also fix TODO according last changes. The --aggr-dir option alias is removed. Now 180 degree is approved to use at --rotate-angle or --rotate-thumb options. Revision 1.88 2006/05/21 11:17:21 andy Release as 0.19.3 (bugfix). Revision 1.87 2006/05/20 18:52:27 andy No empty --extension argument. Fix bug when rotating file name contains special symbols. Change ($#var + 1) to scalar(@var) for optimization. No attempt to process absent files. Smart Orientation tag rotation is implemented for rotation process. Revision 1.86 2006/05/19 11:50:16 andy Change URL tag in spec file. Release as 0.19.2. Revision 1.85 2006/05/18 18:57:38 andy NEWS - user friendly change log is added. Revision 1.84 2006/05/18 18:36:27 andy Recognize --extension '.ext' as well. Simplify counter size calculation. Add missed bracket at if ($AggrTemplate eq "delta"). Fix multiple issues with low cased extension variable. Revision 1.83 2006/05/18 17:36:20 andy Deprecate --aggr-dir (use --aggr-directory instead). Remove hardcoded ".jpg" extension. Revision 1.82 2006/05/18 16:18:01 andy Optimize aggregationProcess(). Remove deprecated --rotate and --ext options. Revision 1.81 2006/05/17 17:05:09 andy Implement exclude list (option --exclude ). Release as 0.19.1. Revision 1.80 2006/05/17 12:07:14 andy Unify EXIF writtings to exifWritting(). First attempt to rotation by Orientation tag (new option --only-orientation). Remove unused variables. Revision 1.79 2006/05/15 17:49:06 andy Add TODO and README.russian to %doc section in spec file. Revision 1.78 2006/05/15 13:23:55 andy Add simple russian description as README.russian. Fix bug with thumbnail rotating (patch by zeus). Revision 1.77 2006/05/15 10:46:01 andy Remove --file option. It's overhead. Install renrot.rc to %_sysconfdir in rpm package. Revision 1.76 2006/05/14 19:54:06 andy Use counter size for aggregation directories. Release as 0.19. Revision 1.75 2006/05/14 10:46:23 andy Check if we have files to process is added. Synchronize TODO with last changes. New option --aggr and --no-aggr for switch aggregation process. No put single letter for %E, %F, %I or %W when tags is absent. Revision 1.74 2006/05/13 15:18:08 andy Correct definition of aggregation related variables. Merge files from ARGV and --file option. Optimize file cache generation. Use mathematic definition of counter size (needs to be tested on windows). Use counter size for default template (was four digits independently of files amount). Revision 1.73 2006/05/13 15:03:38 andy Modify debug outputs. Add new debug messages to main() and getOptions(). Revision 1.72 2006/05/13 14:53:22 andy Reformat usage() outputs. Fix internal documentation according to last changes. Revision 1.71 2006/05/13 14:33:33 andy Change --files to --file, --rotate to --rotate-angle and --ext to --extension. Revert back --no-rotate and --no-rename. Exclude single quotas from README examples part due to undesired interpretation in cmd.exe. Also, put note to README.Windows. Add no warranty part to README. Correct low case makeup. Always writting Software tag. Release as 0.18.3. Revision 1.70 2006/05/11 10:11:47 zeus fixing typo in parameter to template2name. adding FileModifyDate writing in DateTimeOriginal tag absence. Revision 1.69 2006/05/10 19:03:34 andy Renaming several internal variables. Use $Software as boolean variable (user would not changed the string). Revision 1.68 2006/05/10 10:58:57 zeus TODO edited. Revision 1.67 2006/05/09 21:51:39 zeus delay aggregation directory counter format changed on "001" rather "1". Revision 1.66 2006/05/09 20:52:24 zeus the bug with --aggr-template 'delta' fixed (the situation when directory wasn't possible to create (it was bug, the attept to create 'dir/file.ext' rather than 'dir')). Revision 1.65 2006/05/09 12:22:25 zeus so called "delta" aggregation mode had been added. new options added: --aggr-delta, --aggr-dir and --aggr-dumb/--noaggr-dumb. TODO and renrot.rc are edited. Revision 1.64 2006/05/08 15:14:05 zeus implemented work with separate files via --files and -- options. rewritten code concerning the "no mtime" option, according the GetOptions features. syntaxis changed to the GetOptions (no-rotate -> norotate e.t.c.). AggrTemplate option added to the config file. documentation edited according the changes. TODO, AUTHORS edited. Revision 1.63 2006/05/06 21:14:13 zeus names and versions of the programs used to process the file now is written to the tag Software. dynamic length file name counter format is implemented. TODO edited, rc edited. Revision 1.62 2006/05/06 15:17:04 andy Expand TODO semantics. Revision 1.61 2006/05/06 14:51:04 andy Use --no-mtime as opposite to --mtime. Add $setMtime to config file. (Needs to be checked). Minor changes in the documentation. Revision 1.60 2006/05/06 10:00:36 zeus bug fixed, when --no-rename requested it was rewriting XMP tags, now it'd fixed. TODO edited. Revision 1.59 2006/05/06 07:32:14 andy Minor changes of documentation. Removing trailing spaces. Restructure TODO. Add -q option for suppressing process messages. Release 0.16.2. Revision 1.58 2006/05/05 21:31:38 zeus TODO item added. Revision 1.57 2006/05/03 21:59:52 zeus file aggregation via option --aggr-template has been implemented. control code for file name originality has been moved from template2name() to main(). README edited. Revision 1.56 2006/05/02 19:08:59 zeus item about aggregation is added to TODO. minor language ixes in README. Revision 1.55 2006/05/02 09:07:02 andy Wrote RESTRICTIONS section in README. Revision 1.54 2006/05/01 10:22:08 andy Bugfix release 0.16.1. Revision 1.53 2006/05/01 08:01:05 andy Minor fixes in README. Fix bug in rename algorithm when file with generated filename already exists. Add some words about licensing into manual. Revision 1.52 2006/05/01 06:39:21 andy Fix many typos (found by aspell). Add new TODO item. Revision 1.51 2006/04/30 20:37:49 zeus README is tailoded, WHY section is added. City tag is removed from core and rc file. Revision 1.50 2006/04/30 14:12:10 andy Fix stylistic typos and add some explanations of project naming to README. Revision 1.49 2006/04/30 11:35:07 andy Expand documentation accordingly to last code changes (template ideology, mtime option, and so on). Release as 0.16. Revision 1.48 2006/04/28 09:53:50 zeus getTimestamp conditions reviewed. commentaries added where were missed. Revision 1.47 2006/04/27 22:21:28 andy Use regular expression instead of multiple substr(). After our verbosity switch on exiftool's. Revision 1.46 2006/04/27 21:58:52 andy Split template2name() to a two additional functions: getTimestamp() and getUnixTime(). Minor fixes in timeValidator(). New mtime implementation. Revision 1.45 2006/04/27 17:55:26 andy Revert =back keyword to necessary places. Rewrote main description in pod part. Revision 1.44 2006/04/27 17:22:42 andy Remove obsoleted examples from README. Remove mtime code (now is not working). Add template sequences description to rc-file. Revision 1.43 2006/04/27 16:39:06 andy Fix several bugs (script does not run). Inject functions name to lead to argument of dbgmsg(). Probe to use another timestamp algorithm in template2name(). Revision 1.42 2006/04/27 14:31:22 andy Fix timestamp for invalid DateTimeOriginal tag (new function timeValidator()). Rewrote parts of template engine. Set default value for $anglesuffix. Sort in alphabetical order template hash and related documentation. Revision 1.41 2006/04/27 09:53:50 zeus mtime seting according the DateTimeOriginal tag implemented. possibility to add some EXIF details, like FNumber, ISO e.t.c. to the filename is added Revision 1.40 2006/04/27 07:23:14 andy Fix template bug (last symbol analysing). Revision 1.39 2006/04/26 20:36:06 zeus Name template engine tunning. Added suffix for the name in case of the rotation. thanks to Alex Zasypkin added to THANKS section of README. Revision 1.38 2006/04/26 18:35:01 andy Formating inline documentation. First implementation of template for file naming. Revision 1.37 2006/04/26 15:19:58 andy README for windows users. Linguistiq fix for README. Revision 1.36 2006/04/24 07:47:35 andy Minor documentation fix. Revision 1.35 2006/04/23 17:04:36 andy Release 0.15.1. Revision 1.34 2006/04/23 11:57:45 andy Switch to *msg() procedures instead of print "smth.". Print is used only for progress indicator. Revision 1.33 2006/04/23 08:09:35 andy Removing trailing spaces. Revision 1.32 2006/04/23 08:06:55 andy Change die "smth." to ( fatalmsg(), die ). Revision 1.31 2006/04/22 20:26:17 andy Real use --work-directory option. Fix several typos. Don't use double quotes in config file (special symbol issue such as '@' in email). Revision 1.30 2006/04/22 19:22:40 andy Use errmsg() instead of print "ERROR smth." Revision 1.29 2006/04/22 19:16:01 andy New functions: *msg - expand print functionality. Revision 1.28 2006/04/22 18:16:59 andy Add --work-directory option for more functionality. Revision 1.27 2006/04/21 12:30:55 andy Release 0.15. Revision 1.26 2006/04/21 11:16:00 andy Fix DEPENDENCIES section. Fix Requires in spec. Revision 1.25 2006/04/20 13:33:01 zeus README is edited, URL for DEPENDENCIES are added. Revision 1.24 2006/04/20 08:24:23 zeus ISO tag choosen instead of CameraISO, since not all cameras has the last one. Revision 1.23 2006/04/19 20:02:02 zeus README section WHAT IS IT edited. Revision 1.22 2006/04/19 16:24:59 andy Wrote several examples to README. Revision 1.21 2006/04/19 13:23:54 andy Use sprintf() instead of multiple concatenation. Revision 1.20 2006/04/19 12:24:58 andy WHAT IS IT? and GETTING sections were added to README. Change YYYYmmddHHMMS to YYYYmmddHHMMSS in the documentation and comments. Fix description in spec file according to freshmeat.net. Revision 1.19 2006/04/19 08:55:44 zeus code for file names caching for the directory was reingenered, to avoid the processing of the processed files ( processed files could be rescanned while processing of a big number files in the directory ) Revision 1.18 2006/04/19 07:08:07 zeus TODO is added. Revision 1.17 2006/04/18 20:10:26 zeus Documentation edited. Revision 1.16 2006/04/18 13:12:40 andy Start THANKS part in README. Revision 1.15 2006/04/18 11:32:33 andy Fix BuildRequires in specfile. Revision 1.14 2006/04/18 10:34:53 andy Added missed file to MANIFEST. Rerelease as 0.14.2. Revision 1.13 2006/04/18 10:22:12 andy Added LICENSING part to README. Added renrot.spec for RPM-based linux distributions. Bump version (0.14.1). Revision 1.12 2006/04/17 12:57:47 zeus README edited. Revision 1.11 2006/02/17 20:35:12 zeus The bug with --name-prefix-only fixed (it was defined as string in getopt() function) and the extra points in the name are removed. Revision 1.10 2006/01/10 12:16:07 zeus Documentation tailored a bit, the info concerning the jpegtran URL is added. Revision 1.9 2005/11/19 22:53:31 andy Append new files to MANIFEST. Revision 1.8 2005/11/19 09:45:56 zeus Minor script output formating. Revision 1.7 2005/11/18 14:19:26 andy Fix documentation part according to last code changes. Revision 1.6 2005/11/18 14:05:19 andy Use our syntax for $VERSION. Requires perl >= 5.6.0. Revision 1.5 2005/11/18 13:25:18 zeus Config file overlaping implemented. Now if not -c, than the order of reading the configs is such: /etc, /usr/local/etc and finaly HOME. Revision 1.4 2005/11/18 12:54:58 andy Add long option --config-file as alias to -c. Revision 1.3 2005/11/17 12:02:45 zeus Config file support added. Now hardcoded personal details are moved to config file, option -c added. Fixed minor bug when EXIFs are identical in new and old files, renrot wasn't removing temporaty copy. Revision 1.2 2005/10/17 21:34:52 zeus Id keyword is added to renrot file. Revision 1.1 2005/10/17 13:39:38 zeus ChangeLog file is added. Its the very begining. renrot-1.2.0/README.russian000644 001751 000024 00000003244 12154667731 015532 0ustar00zeusstaff000000 000000 О ПРОГРАММЕ RenRot переименовывает файлы в соответствие с EXIF-тегами DateTimeOriginal и FileModifyDate, если они присутствуют, иначе, имя устанавливается в соответствие с текущими датой и временем. Дополнительно изображение восстанавливается в ориентации по тегу Orientation. Скрипт также может добавлять комментарии в теги Commentary и UserComment. Возможна установка персональной информации через XMP-теги, определённые в конфигурационном файле. Также с версии 0.16.2 внедрена функция агрегации, подразумевающая перенесение файлов из рабочего каталога в подкаталоги согласно шаблону, определяемому из частей даты и времени создания фото. В командной строке можно задать набор файлов, вместо целого каталога. Установка атрибута mtime включена по умолчанию. Более подробно о программе можно узнать из англоязычной документации README и renrot(1). ВОПРОСЫ Возникшие замечания, вопросы и предложения, пожалуйста, направляйте авторам, адреса которых указаны в файле AUTHORS. renrot-1.2.0/sample/000755 001751 000024 00000000000 12154670231 014432 5ustar00zeusstaff000000 000000 renrot-1.2.0/README000644 001751 000024 00000022305 12154667731 014046 0ustar00zeusstaff000000 000000 WHAT IT IS ---- -- -- RenRot is a program to rename and lossless rotate (for now only JPEG format) files according to their EXIF tags values. To prevent incorrect associations, some explanation is needed here. The name of project is short form of 'REName and ROTate' and no other interpretation will be used. RenRot is intended to work with files of --extension extension containing EXIF date and can do two things with them - rename and rotate. It runs in batch mode in current or set with --work-directory directory as well as selective mode for separate files given as arguments in command line. New template ideology was implemented. It includes flexibility in file name construction. In version 0.16 and later the previous behaviour still present with default NameTemplate. The template can contain different data, from direct name to EXIF data (the date, id or shooting details such as WhiteBalance, ISO, e.t.c.). For further information, please, see applied manual. RenRot rotates file and its thumbnail, according to EXIF tag Orientation. If the tag is absent or miss set, than the script allows to rotate the file as well as its thumbnail "by hands". Furthermore, the script put a comment to: - Comment tag from comment file; - UserComment tag from configuration variable or command line option. Personal data could be specified via XMP tags defined in configuration file. Starting from the version 0.21rc1 RenRot can write any EXIF tag given by command line option or configuration file. In addition RenRot can aggregate all files in directories according the given date/time pattern template, set with --aggr-template. WHY RenRot --- ------ Several projects like RenRot are available in the net, but why to choose namely RenRot? Because: - it does just what it would do - renames and rotates, nothing more than that; - it is pure CLI with all it's advantage (no need KDE or any other monster to run); - it uses Image::ExifTool (the best open tool to work with EXIF data) and libjpeg6 (the best open tool to operate JPEG format files, to correctly rotate both, the entire file and the thumbnail inside it); - it has very much flex file naming and aggregation template engines; - it uses original algorithm of smart Orientation tag rotation; - it works in batch mode. Although not all of these 100% truth now due to implementation of the Contact Sheet Generator framework RenRot is worth to try. GETTING ------- RenRot's home page is the https://puszcza.gnu.org.ua/projects/renrot/. You can download script package from the following sites: ftp://download.gnu.org.ua/pub/release/renrot/ (home) ftp://ftp.dn.farlep.net/pub/misc/renrot/ (mirror) http://www.smile.org.ua/~andy/prj/renrot/ (mirror) Repositories: Fedora and any of its mirrors (use yum install renrot) Debian and any of its mirrors (use apt-get install renrot) Ubuntu (the same as for Debian) To get notifications about new releases you could subscribe on the Freshmeat page: http://freshmeat.net/projects/renrot/ RUNNING ------- After installation process was done renrot is being running by typing its name in console as usual. The next several examples provide general applications of the script: rename each file according to the given template renrot --name-template="01.%c.%Y%m%d%H%M%S.%E%F%W%I" --extension JPG rename each file according to the given template and aggregate according the date renrot --name-template="%y%m%d%H%M%S.%i" --aggr-mode="template" --aggr-template="%Y%m%d" *.JPG aggregate files by yymmddHHMM renrot --aggr-mode="template" --aggr-template="%y%m%d%H%M" --extension jpg rotate each file and their thumbnail by 90CW in specified directory renrot --rotate-angle 90 --work-directory="/tmp/images" --extension jpg rotate thumbnails, included to EXIF, for each file by 270CW (same as 90CCW) renrot --rotate-thumb 270 --extension jpg rotate given files by Orientation tag (no real rotation will be done) renrot --no-rename --mtime --rotate-angle=90 --only-orientation *.JPEG fix file mtime according to its EXIF tags or current time stamp, when tags are invalid renrot --no-rotate --no-rename --mtime --extension jpeg leave mtime untouched for couple of files renrot --no-mtime *.jpg RESTRICTIONS AND BUGS ------------ --- ---- RenRot has some restrictions and known bugs at runtime. 1. Script handles a whole directory without recursion and only with one extension of files. 2. Algorithm treats each file with given extension as the image. Otherwise, file will be renamed to the current time stamp when --no-rename option is omitted. 3. Rename operation is not permited between different partitions due to hard link technology used (this will be avoided in the future). 4. Rotation process is available only on JPEG files. 5. It seems that for Perl v.5.8.7 and 5.8.8, at least on FreeBSD 6 the bug, which cause crash of the renrot, exists. In case when total amount of the files size to process is bigger than RAM amount, the renrot falls with error: Out of memory during "large" request for XXXX bytes ... This doesn't occure for Perl with external malloc implementation used (f.e. Perl 5.6.1). Starting from version 1.0 RenRot has workaround for this. 6. Still no way to set the tags with the same name but located in the different groups. 7. The colorization is started after the configuration files were parsed. While that is not happened the messages will be colorized by default color scheme. 8. The colorization is not working under Windows platform. All bug reports are welcome to our bugzilla which is obtained here: http://puszcza.gnu.org.ua/bugs/?group=renrot DEBUGGING --------- For advanced users and developers renrot provides debug interface. Amount of -v options in a command line defines level of verbosity. So script differs the nine levels of debugging such as: from 1 till 4 - internal levels from 5 till 9 - equal to 1-5 levels for ExifTool plus maximum verbosity for renrot First word after DEBUG prefix in each message means function where the dbgmsg() is called. INSTALLATION ------------ You can install RenRot to make it available for use by other users by typing the following: perl Makefile.PL PREFIX=/usr/local make make install PREFIX=/usr/local Notes: i) You need root access for the last step above. ii) Some Perl installations (like the standard OSX installation) may not contain the necessary files to complete the first step above. But no worries: You can install script manually by moving 'renrot' to any directory in your current PATH. iii) You may choose any desired PREFIX by changing /usr/local at above sample. RenRot is shipped with the built-in spec file and rpm package. In RPM-based distribution you may install package by next way: rpmbuild -tb renrot-.tar.gz rpm -ivh /usr/src/redhat/RPMS/noarch/renrot--.noarch.rpm DEPENDENCIES ------------ Requires Perl version 5.6.0 or later. No other special libraries are required, except: Image::ExifTool http://www.sno.phy.queensu.ca/~phil/exiftool/ Getopt::Long cpan:Getopt::Long (usually, it's included in the perl distribution) jpegtran (libjpeg6) http://www.ijg.org/ Contact Sheet Generator framework is depended to Image::Magick module at run-time. The module is still optional to whole script. LICENSING --------- RenRot is an Open Source project distributed under Artistic 2.0 license You can find it here http://www.perlfoundation.org/artistic_license_2_0 THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. For more information, please, consult with the full license text placed on http://opensource.org/licenses/. THANKS AND CREDITS ------ --- ------- People ------ Phil Harvey (http://www.sno.phy.queensu.ca/~phil/exiftool/) for his valued comments and suggestions. Alex Zasypkin for his help with documentation writing. Vyacheslav Garonin for his idea concerning the virtual aggregation. Sergey Poznyakoff for help summary and manual style, English and code fixes. Anthony Thyssen for his help with ImageMagick/PerlMagick related questions. Slaven Rezic for his help with perl5.8.x "Out of memory ... sbrk()" on FreeBSD-6.x Teams ----- ASPLinux company (http://www.asplinux.ru) has included RenRot to their distribution. Fedora Team for including RenRot to its repository. Debian maintainers team for including RenRot to the pool. ARE YOU WANT SOMETHING TO SAY? --- --- ---- --------- -- ---- If you have notes, propositions, found bugs or something else do not hesitate to contact us! renrot-1.2.0/Makefile.PL000644 001751 000024 00000001614 12154667731 015140 0ustar00zeusstaff000000 000000 use ExtUtils::MakeMaker; # See lib/ExtUtils/MakeMaker.pm for details of how to influence # the contents of the Makefile that is written. WriteMakefile( 'NAME' => 'renrot', 'AUTHOR' => 'Zeus Panchenko ', 'VERSION_FROM' => 'renrot', # finds $VERSION 'PREREQ_PM' => { 'Image::ExifTool' => 5.72, 'Getopt::Long' => 2.34 }, 'PREREQ_FATAL' => 'true', 'EXE_FILES' => [ 'renrot' ], 'dist' => { COMPRESS => 'gzip', SUFFIX => 'gz' } ); package MY; sub postamble { my $postamble = << 'END'; # Build rpm rpm : tardist rpmbuild -ta $(DISTVNAME).tar.$(SUFFIX) $(RM_F) $(DISTVNAME).tar.$(SUFFIX) srpm : tardist rpmbuild -ts $(DISTVNAME).tar.$(SUFFIX) $(RM_F) $(DISTVNAME).tar.$(SUFFIX) # CVS tagging tag : $(PERLRUN) -e 'use POSIX qw(strftime); $$date = strftime("%Y%m%d%H%M%S", localtime()); system("cvs tag $(NAME)_$$date");' END return $postamble; } renrot-1.2.0/renrot000755 001751 000024 00000243121 12154670156 014421 0ustar00zeusstaff000000 000000 #!/usr/bin/env perl # # vim: ts=2 sw=2 et : # # Analyze crash in third-party modules # (Recipe has been borrowed from http://ilya-dogolazky.livejournal.com/15061.html) BEGIN { $| = 1; $SIG{__DIE__} = sub { use Carp; confess $_[0] . 'Aborted' } } use strict; use warnings; use diagnostics; require 5.006; use Pod::Usage; use Image::ExifTool; use Getopt::Long; use File::Spec; use File::Basename; # add our 'lib' directory to the include list my $exeDir; BEGIN { $exeDir = ($0 =~ /(.*)[\\\/]/) ? $1 : '.'; # get exe directory unshift @INC, "$exeDir/lib"; # add lib directory at start of include path } use Image::RenRot::Util; use Image::RenRot::Logging; use Image::RenRot::Config; use Image::RenRot::TimeUtil; use Image::RenRot::FileUtil; # perl 5.8.x memory overflow workaround (perl5.8.1-8 which uses it's own malloc) my $mem_trouble_stub = " "x4_000_000; undef $mem_trouble_stub; # Versioning my @svnrevision = split(/ /, '$Revision: 625 $'); our $VERSION = '1.2.0'; our $REVISION = defined $svnrevision[1] ? $svnrevision[1] : '0'; my $maxVerbosity = 4; # our max verbosity level (internal) my $homeURL = 'http://puszcza.gnu.org.ua/projects/renrot/'; # homepage of the project ######################################################################################## # # The global definition for a new XMP namespace. The # %Image::ExifTool::UserDefined::RenRot defines XMP-RenRot tags which could be # added to mark the file as processed with RenRot. # if (%Image::ExifTool::UserDefined::RenRot) { die_renrot("Won't redefine Image::ExifTool::UserDefined::RenRot.\n"); } %Image::ExifTool::UserDefined::RenRot = ( GROUPS => { 0 => 'XMP', 1 => 'RenRot', 2 => 'Image' }, NAMESPACE => [ 'RenRot' => $homeURL ], WRITABLE => 'string', RenRotFileNameOriginal => { }, RenRotProcessingTimestamp => { }, RenRotVersion => { }, RenRotURL => { }, ); # The %Image::ExifTool::UserDefined hash defines new tags to be added to # existing tables. %Image::ExifTool::UserDefined = ( # new XMP namespaces must be added to the Main XMP table 'Image::ExifTool::XMP::Main' => { RenRot => { SubDirectory => { TagTable => 'Image::ExifTool::UserDefined::RenRot', }, }, }, ); ######################################################################################## # # Parsed configuration file in hash # Note: unfortunately we still need manual adaptation in getOptions() # my $co_aggr = { # aggregation time delta in seconds (file with delta > $delta is placed in new DIR) 'delta' => { type => 'i', default => 900 }, # counterless directory name for "delta" type aggregation 'directory' => { type => 's', default => 'Images' }, # aggregation framework 'enabled' => { type => '!', default => 0}, # define aggregation mode, possible values are: none, delta or template 'mode' => { type => 's', default => 'none' }, # template for the files aggregation taken from CLI 'template' => { type => 's', default => '%Y%m%d' }, # flag to do links instead real file moving while aggregation 'virtual' => { type => '!', default => 0 }, }; my $co_cs = { # background, look ImageMagick documentation for montage options 'background' => { type => 's', default => 'FFF' }, # bordercolor, look ImageMagick documentation for montage options 'bordercolor' => { type => 's', default => 'DDD' }, # tmp directory for the montage operations 'dir' => { type => 's', default => 'Contact.Sheet' }, # contact sheet generation 'enabled' => { type => '!', default => 0 }, # file name for the montage 'file' => { type => 's', default => 'cs-%c.jpg' }, # fill, look ImageMagick documentation for montage options 'fill' => { type => 's', default => '000' }, # font, look ImageMagick documentation for montage options 'font' => { type => 's', default => 'Helvetica' }, # frame, look ImageMagick documentation for montage options 'frame' => { type => 's', default => '3' }, # label, look ImageMagick documentation for montage options 'label' => { type => 's', default => '%t' }, # mattecolor, look ImageMagick documentation for montage options 'mattecolor' => { type => 's', default => 'CCC' }, # pointsize, look ImageMagick documentation for montage options 'pointsize' => { type => 's', default => '11' }, # ranking 'rank' => { type => '!', default => 0 }, # file with images ranks 'rank file' => { type => 's', default => '.rank' }, # shadow, look ImageMagick documentation for montage options 'shadow' => { type => '!', default => 1 }, # is $extToProcess files are the thumbnails? 'thm' => { type => '!', default => 0 }, # font, look ImageMagick documentation for montage options 'thm font' => { type => 's', default => 'Helvetica' }, # fill, look ImageMagick documentation for montage options 'thm fill' => { type => 's', default => 'gray34' }, # thumbnail background gradient from 'thm grad fr' => { type => 's', default => 'FFFFFF' }, # thumbnail background gradient to 'thm grad to' => { type => 's', default => '909090' }, # thumbnail text 'thm text' => { type => 's', default => "thumbnail\n\nNA" }, # tile in the montage 'tile' => { type => 's', default => '7x5' }, # title for the montage 'title' => { type => 's', default => 'Default Contact Sheet Title' }, }; my $co_g = { 'generate thumbnail size' => { type => 's', default => '160x120' }, # mtime taken from CLI 'mtime' => { type => '!', default => 1 }, # template for the filename taken from CLI 'name template' => { type => 's', default => '%Y%m%d%H%M%S' }, # jpegtran -trim 'trim' => { type => '!', default => 1 }, # colorized output 'use color' => { type => '!', default => 0 }, # rotate thumbnail via pipe 'use ipc' => { type => '!', default => 0 }, }; my $co_kw = { # whether or not to fill Keywords tag 'enabled' => { type => '!', default => 0 }, # file with keyword set 'file' => { type => 's', default => '.keywords' }, # whether to add keywords to the existent ones or replace them 'replace' => { type => '!', default => 0 }, }; my $config_opts = { 'aggregation' => $co_aggr, 'contact sheet' => $co_cs, 'general' => $co_g, 'keywords' => $co_kw, }; ######################################################################################## # # Command line options # my $backup = 1; # make or not a backup of the original files my $comfile; # file with commentary my $configFile; # configuration file my $countFF = 1; # use fixed field for counter my $countStart = 1; # Start value for counter my $countStep = 1; # Step for counter my $dryRun = 0; # show what would have been happened my @excludeList = (); # files that will be excluded from list my $extToProcess = ''; # the extension of files to work with my $gen_thm = 0; # to not to generate ThumbnailImage if empty my $noRename = 0; # no rename needed, default is to rename to the YYYYmmddHHMMSS.ext my $noRotation = 0; # no rotation needed, default is to rotate my $noTags = 0; # no tags writing needed my $noRenRoTagMtm = 0; # no rename, no rotate, no tag and no mtime have to be done my $orientTag = 0; # rotate by changing Orientation tag (no real rotation) my $quiet = 0; # suppressing messages my $rotateAngle; # define the angle to rotate on 90, 180 or 270 my $rotateThumbnail; # define the angle to rotate on 90, 180 or 270 my $subFileSet = ''; # subset of files to process, given in file rather than in commandline my %tagsFromCli = (); # tags are got from CLI my @tag_to_name = (); # tags for name building, theirs values to be used (in theory any EXIF) my $userComment; # text to put into UserComment tag my $verbose = 0; # verbosity of output my $workDir = './'; # we'll work ONLY in current directory my $isThereIM = 0; # is there Image::Magick package? ######################################################################################## # # Tags hash for injecting to EXIFs when renaming # my %tags = ( 'RenRotProcessingTimestamp' => { value => now(), group => 'RenRot', }, 'RenRotVersion' => { value => $VERSION . " (r" . $REVISION . ")", group => 'RenRot', }, 'RenRotURL' => { value => $homeURL, group => 'RenRot', }, ); # define tags for filling ######################################################################################## # # Global variables (internal) # my @rotparms = ( '', '-flip horizontal', '-rotate 180', '-flip vertical', '-transpose', '-rotate 90', '-transverse', '-rotate 270', ); # jpegtran options array to rotate the file my @angles = ( '', 'fh', '180cw', 'fv', 'tp', '90cw', 'tv', '270cw', ); # the array of suffixes to add to the newfilename after rotating my %rotangles = ( '90' => '-rotate 90', '180' => '-rotate 180', '270' => '-rotate 270' ); # array of options to rotate file "by hands" my %rotorient = ( 1 => 0, 6 => 90, 3 => 180, 8 => 270, ); my %rotorientrev = reverse %rotorient; my @multOpts = ( 'color', 'include', 'tag', 'tagfile', ); my @files = (); # array of the sorted filenames to process my %filenameshash = (); # hash for old file names ######################################################################################## ### GETTINNG OPTIONS FROM CLI ### ######################################################################################## ######################################################################################## # Usage : getOptions(...) # Purpose : parses command line arguments # Returns : nothing # Parameters : many of them, look bellow # Throws : no exceptions # Comments : none # See Also : Getopt::Long GetOptions() sub getOptions { my @tmpTags = (); # For 'aggregation' my $s_aggr_opts; my %h_aggr_opts; # For 'contact sheet' my $s_cs_opts; my %h_cs_opts; # For 'general' my %h_g_opts; # For 'keywords' my $s_kw_opts; my %h_kw_opts; my $ll_opts = { 'aggregation' => { 's' => \$s_aggr_opts, 'h' => \%h_aggr_opts }, 'contact sheet' => { 's' => \$s_cs_opts, 'h' => \%h_cs_opts }, 'keywords' => { 's' => \$s_kw_opts, 'h' => \%h_kw_opts }, }; GetOptions ( # AGGREGATION "aggr-opts=s" => \$s_aggr_opts, "aggr-delta=i" => \$h_aggr_opts{'delta'}, "aggr-directory=s" => \$h_aggr_opts{'directory'}, "aggr-mode=s" => \$h_aggr_opts{'mode'}, "aggr-template|a=s" => \$h_aggr_opts{'template'}, "aggr-virtual!" => \$h_aggr_opts{'virtual'}, # CONTACT SHEET "contact-sheet-opts|cs-opts=s" => \$s_cs_opts, "contact-sheet-bg|cs-bg=s" => \$h_cs_opts{'background'}, "contact-sheet-bd|cs-bd=s" => \$h_cs_opts{'bordercolor'}, "contact-sheet-dir|cs-dir=s" => \$h_cs_opts{'dir'}, "contact-sheet|cs!" => \$h_cs_opts{'enabled'}, "contact-sheet-file|cs-file=s" => \$h_cs_opts{'file'}, "contact-sheet-fl|cs-fl=s" => \$h_cs_opts{'fill'}, "contact-sheet-fn|cs-fn=s" => \$h_cs_opts{'font'}, "contact-sheet-fr|cs-fr=s" => \$h_cs_opts{'frame'}, "contact-sheet-lb|cs-lb=s" => \$h_cs_opts{'label'}, "contact-sheet-mt|cs-mt=s" => \$h_cs_opts{'mattecolor'}, "contact-sheet-pntsz|cs-pntsz=i" => \$h_cs_opts{'pointsize'}, "contact-sheet-rank|cs-rank!" => \$h_cs_opts{'rank'}, "contact-sheet-rank-file|cs-rank-file=s" => \$h_cs_opts{'rank file'}, "contact-sheet-shadow|cs-shadow" => \$h_cs_opts{'shadow'}, "contact-sheet-thm|cs-thm" => \$h_cs_opts{'thm'}, "contact-sheet-thm-fl|cs-thm-fl=s" => \$h_cs_opts{'thm fill'}, "contact-sheet-thm-fn|cs-thm-fn=s" => \$h_cs_opts{'thm font'}, "contact-sheet-thm-grfr|cs-thm-grfr=s" => \$h_cs_opts{'thm grad fr'}, "contact-sheet-thm-grto|cs-thm-grto=s" => \$h_cs_opts{'thm grad to'}, "contact-sheet-thm-text|cs-thm-text=s" => \$h_cs_opts{'thm text'}, "contact-sheet-tile|cs-tile=s" => \$h_cs_opts{'tile'}, "contact-sheet-title|cs-title=s" => \$h_cs_opts{'title'}, # GENERAL "mtime!" => \$h_g_opts{'mtime'}, "name-template|n=s" => \$h_g_opts{'name template'}, "trim!" => \$h_g_opts{'trim'}, "use-color!" => \$h_g_opts{'use color'}, "use-ipc!" => \$h_g_opts{'use ipc'}, # KEYWORDIZER "kw-opts=s" => \$s_kw_opts, "keywords!" => \$h_kw_opts{'enabled'}, "keywords-file|k=s" => \$h_kw_opts{'file'}, "keywords-replace!" => \$h_kw_opts{'replace'}, # RENAMIMG "counter-fixed-field!" => \$countFF, "counter-start=i" => \$countStart, "counter-step=i" => \$countStep, "no-rename|norename" => \$noRename, # ROTATIMG "no-rotate|norotate" => \$noRotation, "only-orientation" => \$orientTag, "rotate-angle|r=i" => \$rotateAngle, "rotate-thumb=i" => \$rotateThumbnail, # TAG WRITER "comment-file=s" => \$comfile, "no-tags|notags" => \$noTags, "tag|t=s" => \@tmpTags, "user-comment=s" => \$userComment, # OTHERS "backup!" => \$backup, "config-file|c=s" => \$configFile, "dry-run" => \$dryRun, "exclude=s" => \@excludeList, "extension|e=s" => \$extToProcess, "generate-thumb|g" => \$gen_thm, "help|?" => sub { usage(0, 2) }, "no-renrot|nochg" => \$noRenRoTagMtm, "quiet|q" => \$quiet, "sub-fileset=s" => \$subFileSet, "v+" => \$verbose, "version" => sub { usage(0, 0) }, "work-directory|d=s" => \$workDir, ) or usage(1, 1); # Set the verbosity first Image::RenRot::Logging->set(Verbose => $quiet ? -1 : $verbose); my $fileCount = scalar(@ARGV); foreach my $key (keys %$ll_opts) { # Parse long list option if (defined ${$ll_opts->{$key}{'s'}}) { update_cfg_value($config_opts->{$key}{'enabled'}, 1); parse2hash(${$ll_opts->{$key}{'s'}}, $config_opts, $key); } # Override long list option by set of standalone ones before any other action assign2hash($config_opts, $key, $ll_opts->{$key}{'h'}); # Debug result ldbg3opts($config_opts, $key); } # For 'general' section assign2hash($config_opts, 'general', \%h_g_opts); ldbg3opts($config_opts, 'general'); ldbg3("--backup: ", bool2str($backup)); ldbg3("--comment-file: $comfile") if (defined $comfile); ldbg3("--config-file: $configFile") if (defined $configFile); ldbg3("--counter-start: $countStart", " --counter-step: $countStep", " --counter-fixed-field: ", bool2str($countFF)); ldbg3("--dry-run: ", bool2str($dryRun)) if (defined $dryRun); ldbg3("--exclude:\n", join("\n", @excludeList)) if (scalar(@excludeList) > 0); ldbg3("--extension: '$extToProcess'"); ldbg3("--generate-thumb: ", bool2str($gen_thm)); ldbg3("--no-rename: ", bool2str($noRename), " --no-rotate: ", bool2str($noRotation), " --no-tags: ", bool2str($noTags), " --no-renrot: ", bool2str($noRenRoTagMtm)); ldbg3("--only-orientation: ", bool2str($orientTag)); ldbg3("--rotate-angle: $rotateAngle") if (defined $rotateAngle); ldbg3("--rotate-thumb: $rotateThumbnail") if (defined $rotateThumbnail); ldbg3("--sub-fileset: $subFileSet") if ($subFileSet ne ""); ldbg3("--tag:\n", join("\n", @tmpTags)) if (scalar(@tmpTags) > 0); ldbg3("--work-directory: $workDir"); ldbg3("ARGV:\n", join("\n", @ARGV)) if ($fileCount > 0); if ($extToProcess eq "" and ($fileCount == 0) and $subFileSet eq "") { fatalmsg ("Extension of files is required!\n"); exit 1; } if ($extToProcess ne "" and ($fileCount != 0)) { warnmsg ("Extension of files will be ignored!\n"); } if ($noRenRoTagMtm != 0) { $noRename = $noRotation = $noTags = 1; update_cfg_value($config_opts->{'general'}{'mtime'}, 0); } # is there ImageMagick? if ($isThereIM == 1) { dbgmsg (1, "We have Image::Magick package and could proceed with --contact-sheet related functionality.\n"); } elsif (get_cfg_value($co_cs, 'enabled') == 1) { errmsg ("To use --contact-sheet related functionality you need Image::Magick package!\n", "Contact Sheet generation disabled.\n"); } elsif ($gen_thm != 0) { errmsg ("To use --generate-thumb functionality you need Image::Magick package!\n", "ThumbnailImage generation is disabled.\n"); } # preparing Software tag according the usage or not of ImageMagick if ($isThereIM == 1) { my ($imName, $imVersion) = (split(/ /, Image::Magick->new()->Get('version')))[0,1]; $tags{'Software'}{'value'} = sprintf("RenRot v%s (r%s), ExifTool v%s, %s v%s", $VERSION, $REVISION, $Image::ExifTool::VERSION, $imName, $imVersion); } else { $tags{'Software'}{'value'} = sprintf("ExifTool v%s, RenRot v%s (r%s)", $Image::ExifTool::VERSION, $VERSION, $REVISION); } $tags{'Software'}{'group'} = 'EXIF'; # Change user's parameter '*.ext' or 'ext' to '.ext' $extToProcess =~ s/^\*?\.?/\./ if ($fileCount == 0); dbgmsg (1, "Process with '$extToProcess' extension.\n"); # Convert multiple tag parameters to tags hash foreach my $tagStr (@tmpTags) { my %tag = str2hash($tagStr); map { $tagsFromCli{$_} = $tag{$_} } keys %tag; } } ######################################################################################## # # parseConfig() parses user's or standart configuration files to hash # sub parseConfig { my $file = shift; my $hash = shift; my @sections = keys %$hash; Image::RenRot::Config->init(Sections => \@sections, MultiOptions => \@multOpts); return Image::RenRot::Config->parsefile($file, $hash) if (defined $file); my $home = $ENV{"HOME"}; my @homeRC = (); $home = $ENV{"USERPROFILE"} if (not defined $home); if (defined $home and $home ne "") { push (@homeRC, File::Spec->catfile($home, ".renrotrc")); push (@homeRC, File::Spec->catfile($home, ".renrot", ".renrotrc")); push (@homeRC, File::Spec->catfile($home, ".renrot", "renrot.conf")); } else { warnmsg ("User's home environment variable isn't defined or empty!\n"); } my @rcFiles = ( "/etc/renrot.rc", "/etc/renrot/renrot.rc", "/etc/renrot/renrot.conf", "/usr/local/etc/renrot.rc", "/usr/local/etc/renrot/renrot.rc", "/usr/local/etc/renrot/renrot.conf", @homeRC, ); map { Image::RenRot::Config->parsefile($_, $hash) } @rcFiles; } ######################################################################################## # # switchColor() switches to user defined color scheme # sub switchColor { my $colors = (); # Parse configuration file color set while (my ($cKey, $v) = each %$config_opts) { next if ($cKey !~ m/^color#\d+#\d+$/); # skip not a color my ($ck, $ch) = str2hash($v, 'reset'); $colors->{$ck} = $ch; } Image::RenRot::Logging->set(Color => $colors, UseColor => get_cfg_value($co_g, 'use color')); dbgmsg (1, "Switch to user defined color scheme.\n"); } ######################################################################################## # Usage : keywordizer($keywords_file) # Purpose : validates keywords # Returns : [array of strings] @results with CR and LF symbols removed # Parameters : [string] $keywords_file # Throws : no exceptions # Comments : none # See Also : N/A sub keywordizer { my $file = shift; # keywords file return if (not (-R $file and -f $file and -T $file)); dbgmsg (2, "Reading keywords from file: $file\n"); my @result = (); my @keywordArr = Image::RenRot::FileUtil->getFileDataLines($file); for (my $i = 0; $i < scalar(@keywordArr); $i++) { $keywordArr[$i] =~ s/\r?\n$//; # remove CR and LF symbols push (@result, trim($keywordArr[$i])) if ($keywordArr[$i] !~ m/^\s*$/); } return @result; } ######################################################################################## # Usage : renRotProcess($exif_tool_obj, $counter_size) # Purpose : renames and rotates given file set # Returns : nothing # Parameters : $exif_tool_obj [ref] # : $counter_size [num] file name counter digit capacity (number of digits in # : counter. for tens it is 2, for thousands - 4, e.t.c.) # Throws : no exceptions # Comments : none # See Also : N/A sub renRotProcess { my $exifToolObj = shift; my $counterSize = shift; my $fileCounter = $countStart;# file counter my $newFileName; # the name file to be renamed to my $info; # ImageInfo object my @keywordArr = (); # array for keywords if (get_cfg_value($co_kw, 'enabled') != 0) { @keywordArr = keywordizer (get_cfg_value($co_kw, 'file')); errmsg ("Keywords file doesn't exist!\n") if (not -e get_cfg_value($co_kw, 'file')); } if (scalar(@keywordArr) > 0) { dbgmsg (2, "Keywords count: ", scalar(@keywordArr), "\n"); if (get_cfg_value($co_kw, 'replace') != 0) { $exifToolObj->SetNewValue(Keywords => \@keywordArr); } else { $exifToolObj->SetNewValue(Keywords => \@keywordArr, AddValue => 1); } } # Convert trim boolean value to string my $trimStr = get_cfg_value($co_g, 'trim') ? '-trim' : ''; dbgmsg (1, "Trim string: '$trimStr'\n"); dbgmsg (1, "Initializing tags...\n"); foreach my $key (sort (keys %tags)) { $exifToolObj->SetNewValue($key, $tags{$key}{value}, Group => $tags{$key}{group}); } procmsg ("RENAMING / ROTATING\n"); procmsg ("===================\n"); my $file_num = scalar(@files); my $file_rem = 0; foreach my $file (@files) { $file_rem++; procmsg ("Processing file: ($file_rem of $file_num) $file...\n"); # Setup defaults $info = $exifToolObj->ImageInfo($file); # analyzing whether to rotate my $angleSuffix = rotateFile($exifToolObj, $info, $file, $trimStr); # generating absent ThumbnailImage tag from the original image if (($isThereIM == 1) and ($gen_thm != 0) and not defined ${$$info{ThumbnailImage}}) { thm_gen_orig($file, 1); } # analyzing whether and how to rename file $newFileName = renameFile($exifToolObj, $info, $file, $fileCounter, $counterSize, $angleSuffix); # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file saveOurHdrs($exifToolObj, $info, $file); # Writing tags. tagWriter($exifToolObj, $newFileName) if ($noTags == 0); # seting mtime for the file if been asked for mtimeSet($exifToolObj, $info, $newFileName); procmsg ("\n"); $fileCounter += $countStep; } } ######################################################################################## # Usage : saveOurHdrs($exif_tool_obj, $info_obj, $file) # Purpose : saves native RenRot defined tags to file EXIF # Returns : nothing # Parameters : $exif_tool_obj [ref] # : $info_obj [ref] # : $file [str] # Throws : no exceptions # Comments : none # See Also : N/A sub saveOurHdrs { my $exifToolObj = shift; my $infoObj = shift; my $file = shift; my $fileNameOriginal = $exifToolObj->GetValue("RenRotFileNameOriginal"); if (not defined $fileNameOriginal) { $tags{'RenRotFileNameOriginal'} = {value => $file, group => 'RenRot'}; dbgmsg (2, "Set RenRotFileNameOriginal to $file.\n"); } else { $tags{'RenRotFileNameOriginal'} = { value => $infoObj->{"RenRotFileNameOriginal"}, group => 'RenRot' }; dbgmsg (2, "RenRotFileNameOriginal: $fileNameOriginal.\n"); } $exifToolObj->SetNewValue("RenRotFileNameOriginal", $tags{'RenRotFileNameOriginal'}{value}, Group => $tags{'RenRotFileNameOriginal'}{group}); } ######################################################################################## # # rotateFile() rotates file and its thumbnail if needed, changes Orientation tag # sub rotateFile { my $exifToolObj = shift; my $infoObj = shift; my $file = shift; my $trimStr = shift; my $orientation = $exifToolObj->GetValue("Orientation", 'ValueConv'); my $angleSuffix = "0cw"; if ($noRotation != 0) { dbgmsg (2, "No rotation asked, file orientation is left untouched.\n"); } elsif (defined $rotateAngle) { dbgmsg (2, "We'll deal with: $file and $rotangles{$rotateAngle}.\n"); if ($orientTag != 0) { rotateOrient($exifToolObj, $file, $orientation); } else { rotateImg($file, $rotangles{$rotateAngle}, $trimStr); rotateThumbnail($infoObj, $file, $rotangles{$rotateAngle}, $trimStr); } $angleSuffix = $rotateAngle . "cw"; } elsif (defined $rotateThumbnail) { rotateThumbnail($infoObj, $file, $rotangles{$rotateThumbnail}, $trimStr); } else { if (defined $orientation) { if ($orientation > 1) { rotateImg($file, $rotparms[$orientation - 1], $trimStr); rotateThumbnail($infoObj, $file, $rotparms[$orientation - 1], $trimStr); $angleSuffix = $angles[$orientation - 1]; } elsif ($orientation == 1) { dbgmsg (2, "No need to rotate, orientation is: Horizontal (normal).\n"); } else { errmsg ("Something wrong, orientation low than 1: $orientation.\n"); } } else { warnmsg ("Orientation tag is absent!\n"); } } return $angleSuffix; } ######################################################################################## # # renameFile() renames file according to user request and EXIF data # sub renameFile { my $exifToolObj = shift; my $infoObj = shift; my $file = shift; my $fileCounter = shift; my $counterSize = shift; my $angleSuffix = shift; my $newFileName; my $unixTime = get_unix_time(getTimestamp($exifToolObj, $infoObj)); if ($noRename != 0) { dbgmsg (2, "No renaming asked, filename is left untouched.\n"); $newFileName = $file; $filenameshash{$newFileName} = $unixTime; } else { $newFileName = template2name ( $exifToolObj, $infoObj, get_cfg_value($co_g, 'name template'), $fileCounter, $file, $counterSize, $angleSuffix ); my $ext = "." . (splitext($file))[1]; $newFileName .= "." . sprintf($counterSize, $fileCounter) if ($filenameshash{$newFileName . $ext}); $newFileName .= $ext; $filenameshash{$newFileName} = $unixTime; if ($file ne $newFileName) { if (-f $newFileName) { die_renrot("File $newFileName already exists!\n"); } if ($dryRun == 0) { rename ($file, $newFileName) || die_renrot("Unable to rename $file -> $newFileName.\n"); } procmsg ("Renamed: $file -> $newFileName\n"); } else { warnmsg ("No renaming needed for $newFileName, it looks as needed!\n"); } } return $newFileName; } ######################################################################################## # # mtimeSet() sets mtime for the given file # sub mtimeSet { my $exifToolObj = shift; my $infoObj = shift; my $file = shift; if (get_cfg_value($co_g, 'mtime') != 0) { my $mTime = get_unix_time(getTimestamp($exifToolObj, $infoObj)); if ($dryRun == 0) { utime $mTime, $mTime, $file; } else { procmsg ("Setting mtime.\n"); } dbgmsg (2, "Changing mtime for $file OK.\n"); } } ######################################################################################## # # tagWriter() writes couple of tags defined via configuration file and command line # sub tagWriter { my $exifToolObj = shift; my $file = shift; # writing the changes to the EXIFs if ($dryRun == 0) { exifWriter($exifToolObj, $file); } else { procmsg ("Writing user defined EXIF tags to $file.\n"); } } ######################################################################################## # # exifWriter() applies EXIF info, set by SetNewValue, to the image taken from # the file, and writes the result to the same file. # sub exifWriter { my $exifToolObject = shift; my $fileRes = shift; # the file the *image* taken from my $result = $exifToolObject->WriteInfo($fileRes); if ($result == 1) { dbgmsg (2, "Writing to $fileRes seems to be OK.\n"); } elsif ($result == 2) { warnmsg ("No EXIF difference. No EXIF was written.\n"); } else { my $errorMessage = $exifToolObject->GetValue('Error'); my $warningMessage = $exifToolObject->GetValue('Warning'); if (defined $errorMessage) { errmsg ("ExifTool: $errorMessage\n"); } if (defined $warningMessage) { warnmsg ("ExifTool: $warningMessage\n"); } } return $result; } ######################################################################################## ### AGGREGATION FRAMEWORK ### ######################################################################################## ######################################################################################## # # aggregationProcess() aggregates files to separate directories by request # sub aggregationProcess { return if (get_cfg_value($co_aggr, 'mode') eq "none"); my $exifToolObj = shift; my $counterSize = shift; my $file; my $info; my $BaseDir = get_cfg_value($co_aggr, 'directory'); my $NewDir; my $file_num = scalar(keys(%filenameshash)); my $file_rem = 0; procmsg ("AGGREGATION\n"); procmsg ("===========\n"); makedir($BaseDir) if ($dryRun == 0); if (get_cfg_value($co_aggr, 'mode') eq "template") { dbgmsg (1, "Template: ", get_cfg_value($co_aggr, 'template'), "\n"); my $fileCounter = $countStart; foreach $file (sort (keys %filenameshash)) { $file_rem++; $info = $exifToolObj->ImageInfo($file); $NewDir = template2name ( $exifToolObj, $info, get_cfg_value($co_aggr, 'template'), $fileCounter, $file, $counterSize, "0cw" ); $NewDir = File::Spec->catdir($BaseDir, $NewDir); aggregateFile($file, $NewDir) if ($dryRun == 0); procmsg ("Aggregate: ($file_rem of $file_num) $file -> $NewDir\n", "\n"); $fileCounter += $countStep; } } elsif (get_cfg_value($co_aggr, 'mode') eq "delta") { my $DirCounter = 1; my $timestampPrev; my $filePrev; my $filetmp; foreach $file (sort (keys %filenameshash)) { $filetmp = $file; $file_rem++; if ($DirCounter == 1) { $timestampPrev = $filenameshash{$filetmp}; $filePrev = $filetmp; $NewDir = $BaseDir . "." . sprintf($counterSize, $DirCounter); $DirCounter++; aggregateFile($file, $NewDir) if ($dryRun == 0); } else { # Check for new direcroty creation if (($filenameshash{$filetmp} - $timestampPrev) > get_cfg_value($co_aggr, 'delta')) { $NewDir = $BaseDir . "." . sprintf($counterSize, $DirCounter); $DirCounter++; } aggregateFile($file, $NewDir) if ($dryRun == 0); $timestampPrev = $filenameshash{$filetmp}; } procmsg ("Aggregate: ($file_rem of $file_num) $file -> $NewDir\n", "\n"); } } else { errmsg ("Aggregation mode ", get_cfg_value($co_aggr, 'mode'), " isn't implemented!\n"); } } ######################################################################################## # Usage : contactSheetGenerator(); # Purpose : builds contact sheet(s) # Returns : none # Parameters : $exifTool ref - # Throws : no exceptions # Comments : requires --ext; --counter-start is valid # See Also : template2name() sub contactSheetGenerator { return if (not get_cfg_value($co_cs, 'enabled')); use File::Copy; my $exifToolObj = shift; my $workdir = get_cfg_value($co_cs, 'dir'); my $file; my $info; my $infothm; my $ThumbnailOriginal; my $width; my $height; my $size = 0; my @thumbnailes = (); my @thumbnailes_sorted = (); my $orientation; my $filefull; my $ranks; # reference to the hash of files ranks # ranks file processing if (get_cfg_value($co_cs, 'rank') == 1 and -f get_cfg_value($co_cs, 'rank file')) { $ranks = Image::RenRot::FileUtil->getFileDatLns(get_cfg_value($co_cs, 'rank file')); if ($ranks != 0) { dbgmsg (3, "Ranks successfully processed.\n"); } else { errmsg ("Ranks processing failed.\n"); } } makedir($workdir) if ($dryRun == 0); procmsg ("CONTACT SHEET GENERATION\n"); procmsg ("========================\n"); # no sort since it'll be sorted below foreach $file (keys %filenameshash) { $info = $exifToolObj->ImageInfo($file); $orientation = $exifToolObj->GetValue("Orientation", 'ValueConv'); $filefull = $file; if (get_cfg_value($co_cs, 'thm') != 0 and defined $orientation) { if ($orientation > 1) { $filefull = rot_thm_cs ($file, $rotorient{$orientation}, $workdir); } elsif ($orientation == 1) { # We need this since rotated imgage will be at $workdir, but others are # in current $ThumbnailOriginal = File::Spec->catfile($workdir, $file); if ($dryRun == 0) { copy ($file, $ThumbnailOriginal) || die_renrot("copy failed: $!"); } $filefull = $ThumbnailOriginal; } } elsif (get_cfg_value($co_cs, 'thm') == 0) { if (defined ${$$info{ThumbnailImage}}) { $ThumbnailOriginal = File::Spec->catfile($workdir, $file); if ($dryRun == 0) { open (OLDTHUMBNAIL, ">$ThumbnailOriginal") || die_renrot("$ThumbnailOriginal wasn't opened!\n"); binmode OLDTHUMBNAIL; print OLDTHUMBNAIL ${$$info{ThumbnailImage}}; unless (close (OLDTHUMBNAIL)) { warnmsg ("$ThumbnailOriginal wasn't closed!\n"); } } if (not defined $orientation) { $orientation = $exifToolObj->GetValue("Rotation", 'ValueConv'); } if (defined $orientation and $orientation > 1) { $filefull = rot_thm_cs ($ThumbnailOriginal, $orientation, File::Spec->curdir()); } else { $filefull = $ThumbnailOriginal; } } else { my $ffwf = File::Spec->catfile($workdir, $file); if ($gen_thm == 0) { warnmsg ("$filefull has no ThumbnailImage tag. Stub thumbnail image will be used.\n"); my $thmbstubjpg = File::Spec->catfile($workdir, "thmbstub.jpg"); thm_gen_stub($thmbstubjpg) if (not -f $thmbstubjpg); copy ($thmbstubjpg, $ffwf) if ($dryRun == 0); } else { warnmsg ("$filefull has no ThumbnailImage tag. Thumbnail image will be generated.\n"); move (thm_gen_orig($file, 0), $ffwf) if ($dryRun == 0); } $filefull = $ffwf; } } if ($dryRun == 0) { $infothm = $exifToolObj->ImageInfo($filefull); $width = $exifToolObj->GetValue("ImageWidth"); if ($width > $size) { $size = $width; } $height = $exifToolObj->GetValue("ImageHeight"); if ($height > $size) { $size = $height; } } push (@thumbnailes, $filefull); } @thumbnailes_sorted = sort {$a cmp $b} @thumbnailes; # here it's iteration by tile my ($tileX, $tileY) = split ("x", get_cfg_value($co_cs, 'tile')); my $tileMul = $tileX * $tileY; my $csIterationNumber = scalar @thumbnailes_sorted; my $csFullIterations = int($csIterationNumber/$tileMul); my $csRest = $csIterationNumber % $tileMul; my $fileCounter = $countStart; my $csIteration = 0; my $csIterator = 0; my $csIter = 0; my $readres; my $image; my $writeres; my $montage; my $montagename; my $infoMontage; my @left_up_row = ("RenRot v$VERSION (r$REVISION)", $homeURL); my $substrFile; my $readIndex; my $counter_size; if ($csFullIterations != 0) { $counter_size = "%." . length($csFullIterations * $countStep + $countStart) . "d"; dbgmsg (1, "Counter size: $size (CS files num: $csFullIterations)\n"); } else { $counter_size = "%d"; } if ($csFullIterations > 0) { # we need this for the case when $csRest == 0 and $csIterationNumber > $tileMul if (not $csRest) { --$csFullIterations; } for (; $csIterator < $csFullIterations; $csIterator++, $fileCounter += $countStep) { $image = Image::Magick->new; $montagename = template2name ( $exifToolObj, $info, get_cfg_value($co_cs, 'file'), $fileCounter, "stub", $counter_size, "none" ); for ($readIndex = 0, $csIter = $csIterator * $tileMul; $csIter < ($csIterator * $tileMul + $tileMul); $csIter++, $readIndex++) { $readres = $image->Read($thumbnailes_sorted[$csIter]); if (not $readres) { dbgmsg (4, "$thumbnailes_sorted[$csIter] was successfully read.\n"); } else { errmsg ("Image::Magick error: $readres\n"); } # ranking $substrFile = substr($thumbnailes_sorted[$csIter], length(get_cfg_value($co_cs, 'dir')) + 1); if (defined $ranks->{$substrFile}->[1]) { dbgmsg (4, "$substrFile mattecolor is \"$ranks->{$substrFile}->[1]\"\n"); $image->[$readIndex]->Set(mattecolor => $ranks->{$substrFile}->[1]); } else { dbgmsg (4, "$substrFile mattecolor is undefined, using \"", get_cfg_value($co_cs, 'mattecolor'), "\" from config\n"); $image->[$readIndex]->Set(mattecolor => normalize_color(get_cfg_value($co_cs, 'mattecolor'))); } } dbgmsg (1, "$csIterator montage is started, wait a bit please...\n"); $montage = $image->Montage(background => normalize_color(get_cfg_value($co_cs, 'background')), bordercolor => normalize_color(get_cfg_value($co_cs, 'bordercolor')), font => get_cfg_value($co_cs, 'font'), fill => normalize_color(get_cfg_value($co_cs, 'fill')), label => get_cfg_value($co_cs, 'label'), frame => get_cfg_value($co_cs, 'frame'), geometry => $size . "x" . $size . "+4+4", pointsize => get_cfg_value($co_cs, 'pointsize'), shadow => get_cfg_value($co_cs, 'shadow'), title => get_cfg_value($co_cs, 'title'), tile => get_cfg_value($co_cs, 'tile'), stroke => 'none', ); if (not ref($montage)) { errmsg ("Image::Magick error: $montage\n"); } else { dbgmsg (1, "$csIterator montage've finished successfully.\n"); } $montage->Set ( filename => 'jpg:' . $montagename, quality => 95, interlace => 'Partition', gravity => 'Center', stroke => 'none', ); $montage->Annotate ( font => get_cfg_value($co_cs, 'font'), pointsize => 9, x => 5, y => 13, fill => 'lightgray', text => $left_up_row[0], ); $montage->Annotate ( font => get_cfg_value($co_cs, 'font'), pointsize => 9, x => 5, y => 23, fill => 'lightgray', text => $left_up_row[1], ); $writeres = $montage->Write(); if (not $writeres) { dbgmsg (1, "Successfully written $montagename file.\n"); } else { errmsg ("Image::Magick error: $writeres\n"); } $infoMontage = $exifToolObj->ImageInfo($montagename); # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file saveOurHdrs($exifToolObj, $infoMontage, $montagename); # Writing tags. tagWriter($exifToolObj, $montagename); # Writing ThumbnailImage tag with generated thumbnail thm_gen_orig($montagename, 1); undef $image; } } $image = Image::Magick->new; $montagename = template2name ( $exifToolObj, $info, get_cfg_value($co_cs, 'file'), $fileCounter, "stub", $counter_size, "none" ); for ($readIndex = 0, $csIteration = $csIterator * $tileMul; $csIteration < $csIterationNumber; $csIteration++, $readIndex++) { $readres = $image->Read($thumbnailes_sorted[$csIteration]); if (not $readres) { dbgmsg (4, "$thumbnailes_sorted[$csIteration] was successfully red.\n"); } else { errmsg ("Image::Magick error: $readres\n"); } # ranking $substrFile = substr($thumbnailes_sorted[$csIteration], length(get_cfg_value($co_cs, 'dir')) + 1); if (defined $ranks->{$substrFile}->[1] and length($ranks->{$substrFile}->[1]) > 1) { dbgmsg (4, "[last] $substrFile mattecolor is \"$ranks->{$substrFile}->[1]\"\n"); $image->[$readIndex]->Set(mattecolor => $ranks->{$substrFile}->[1]) if ($dryRun == 0); } else { dbgmsg (4, "[last] $substrFile mattecolor is undefined, using \"", get_cfg_value($co_cs, 'mattecolor'), "\" from config.\n"); $image->[$readIndex]->Set(mattecolor => normalize_color(get_cfg_value($co_cs, 'mattecolor'))) if ($dryRun == 0); } } dbgmsg (1, "Final, $csIterator montage is started, wait a bit please...\n"); # the final invocation of ->Montage() method for the the rest of files didn't fit # previous loop ->Montage() calls when $csIterationNumber < $tileX * $tileY $montage = $image->Montage ( background => normalize_color(get_cfg_value($co_cs, 'background')), bordercolor => normalize_color(get_cfg_value($co_cs, 'bordercolor')), font => get_cfg_value($co_cs, 'font'), fill => normalize_color(get_cfg_value($co_cs, 'fill')), label => get_cfg_value($co_cs, 'label'), frame => get_cfg_value($co_cs, 'frame'), geometry => $size . "x" . $size . "+4+4", pointsize => get_cfg_value($co_cs, 'pointsize'), shadow => get_cfg_value($co_cs, 'shadow'), title => get_cfg_value($co_cs, 'title'), tile => get_cfg_value($co_cs, 'tile'), stroke => 'none', ); if (not ref($montage)) { errmsg ("Image::Magick error: $montage\n"); } else { dbgmsg (1, "Montage've finished successfully.\n"); } if ($dryRun == 0) { $montage->Set ( filename => 'jpg:' . $montagename, quality => 95, interlace => 'Partition', gravity => 'Center', stroke => 'none', ); $montage->Annotate ( font => get_cfg_value($co_cs, 'font'), pointsize => 9, x => 5, y => 13, fill => 'lightgray', text => $left_up_row[0], ); $montage->Annotate ( font => get_cfg_value($co_cs, 'font'), pointsize => 9, x => 5, y => 23, fill => 'lightgray', text => $left_up_row[1], ); $writeres = $montage->Write(); } if (not $writeres) { dbgmsg (1, "Successfully written $montagename file.\n"); } else { errmsg ("Image::Magick error: $writeres\n"); } undef $image; $infoMontage = $exifToolObj->ImageInfo($montagename); # to save RenRotFileNameOriginal tag we have to rewrite it each time we anyhow prosess file saveOurHdrs($exifToolObj, $infoMontage, $montagename); # Writing tags. tagWriter($exifToolObj, $montagename); # Writing ThumbnailImage tag with generated thumbnail thm_gen_orig($montagename, 1); if (-d $workdir) { chdir $workdir; unlink <*>; chdir ".."; } rmdir $workdir; procmsg ("\n"); } ######################################################################################## # Usage : thm_gen_stub($thm_name); # Purpose : thumbnail stub generator # Returns : none # Parameters : $thm_name str - thumbnail image pathname # Throws : no exceptions # Comments : none # See Also : contactSheetGenerator(); sub thm_gen_stub { my $thm_name = shift; my $size = get_cfg_value($co_g, 'generate thumbnail size'); my $thmb = Image::Magick->new; $thmb->Set(size => $size, filename => $thm_name, quality => 95, interlace => 'Partition'); my $gf = normalize_color(get_cfg_value($co_cs, 'thm grad fr')); my $gt = normalize_color(get_cfg_value($co_cs, 'thm grad to')); $thmb->ReadImage("gradient:$gf-$gt"); $thmb->Annotate( pointsize => 25, fill => normalize_color(get_cfg_value($co_cs, 'thm fill')), font => get_cfg_value($co_cs, 'thm font'), text => get_cfg_value($co_cs, 'thm text'), gravity => 'Center', ); if ($dryRun == 0) { my $thmbnum = $thmb->Write(); if ($thmbnum) { errmsg ("$thmbnum\n"); undef $thmb; return; } } undef $thmb; procmsg ("Stub thumbnail image has been created.\n"); } ######################################################################################## # Usage : thm_gen_orig($file, $unlink); # Purpose : generate thumbnail image from the original file and write it # : to the ThumbnailImage tag # Returns : thumbnail file name if unlink not asked # Parameters : $file str - original image name # : $unlink bin - 0 not to kill generated thumbnail # : 1 to kill generated thumbnail # Throws : no exceptions # Comments : none # See Also : n/a sub thm_gen_orig { my $file = shift; my $unlink = shift; my $thm_name = "thm_" . $file; my $size = get_cfg_value($co_g, 'generate thumbnail size'); my $thmb = Image::Magick->new; $thmb->Set(size => $size, filename => $thm_name, quality => 95, interlace => 'Partition'); $thmb->ReadImage($file); $thmb->Thumbnail(geometry => $size); if ($dryRun == 0) { my $thmbnum = $thmb->Write(); if ($thmbnum) { errmsg ("$thmbnum\n"); undef $thmb; return; } } undef $thmb; procmsg ("ThumbnailImage was generated for $file\n"); if ($unlink) { dbgmsg (3, "Attempt to write ThumbnailImage tag to $file\n"); thumbWriter($file, Image::RenRot::FileUtil->getFileData($thm_name)); unlink $thm_name; return; } else { return $thm_name; } } ######################################################################################## # Usage : rot_thm_cs($base_orig, $angle, $workdir); # Purpose : rotates thumbnails for contact sheet and put it to the workdir # Returns : none # Parameters : $base_orig str - pathless filename of the rotated file # : $angle num - rotation angle # : $workdir str - the work directory thumbnails kept in # Throws : no exceptions # Comments : works only with JPEGs # See Also : contactSheetGenerator(); sub rot_thm_cs { my $base_orig = shift; my $angle = shift; my $workdir = shift; my $image_to_rotate = Image::Magick->new; my $result = File::Spec->catfile($workdir, $base_orig); $image_to_rotate->Read("JPEG:" . $base_orig); $image_to_rotate->Rotate(degrees => $angle); $image_to_rotate->Write(filename => "JPEG:" . $result, quality => 95, compression => 'JPEG2000'); undef $image_to_rotate; return $result; } ######################################################################################## # Usage : normalize_color($color); # Purpose : validates given string as color suitable for ImageMagick # Returns : none # Parameters : $color str - color # Throws : no exceptions # Comments : none # See Also : convert -list color sub normalize_color { my $color = shift; if ($color =~ /^#?([[:xdigit:]]{3})$/ or $color =~ /^#?([[:xdigit:]]{6})$/) { return "#" . $1; } return $color; } ######################################################################################## # Usage : aggregateFile($file, $NewDir); # Purpose : moves or links file to the aggregation directory # Returns : none # Parameters : $file [str] - name of file to be moved # : $new_dir [str] - name of dir the file to be moved to # Throws : no exceptions # Comments : none # See Also : N/A sub aggregateFile { my $file = shift; my $new_dir = shift; my $base_name = basename($file); makedir($new_dir); if (get_cfg_value($co_aggr, 'virtual') == 0) { my $newname = File::Spec->catfile($new_dir, $base_name); rename ($file, $newname) || die_renrot("While moving $file -> $newname, perhaps a different file system is using\n"); dbgmsg (3, "$file moved to $newname\n"); } else { my $newfile = File::Spec->catfile(File::Spec->abs2rel($new_dir), $base_name); if (not -l $newfile) { # Make relative path to original file from new directory my $oldfile = File::Spec->catfile(File::Spec->abs2rel(File::Spec->curdir(), $new_dir), $file); symlink ($oldfile, $newfile) || die_renrot("While linking $oldfile -> $newfile\n"); dbgmsg (3, "Following link was created $newfile -> $oldfile\n"); } else { warnmsg ("Link $newfile already exists.\n"); } } } ######################################################################################## # # getTimestamp() returns EXIF timestamp in form YYYYmmddHHMMSS if exists, otherwise # it returns Image::RenRot::TimeUtil->now() # sub getTimestamp { my $exifToolObj = shift; my $infoObj = shift; my $timestamp; if (defined $infoObj->{"DateTimeOriginal"} and not time_validator($infoObj->{"DateTimeOriginal"})) { $timestamp = $infoObj->{"DateTimeOriginal"}; } elsif (defined $infoObj->{"FileModifyDate"} and not time_validator($infoObj->{"FileModifyDate"})) { $timestamp = $infoObj->{"FileModifyDate"}; } else { $timestamp = now(); $exifToolObj->SetNewValue('FileModifyDate', $timestamp, Group => 'File'); warnmsg ("EXIF timestamp isn't correct, using current time!\n"); } return $timestamp; } ######################################################################################## # # rotateOrient() rotates image by changing Orientation tag. No real rotation # will be made. # sub rotateOrient { my $exifToolObj = shift; my $fileOrient = shift; my $orientation = shift; dbgmsg (4, "Original Orientation: $orientation\n"); my $angleTmp = $rotorient{$orientation}; if (not defined $angleTmp) { errmsg ("Operation not permited for mirror type orientation.\n"); return; } $angleTmp += $rotateAngle; $angleTmp -= 360 if ($angleTmp >= 360); $orientation = $rotorientrev{$angleTmp}; dbgmsg (4, "New Orientation: $orientation\n"); $exifToolObj->SetNewValue("Orientation", $orientation, Type => 'ValueConv'); if ($dryRun == 0) { exifWriter($exifToolObj, $fileOrient); } else { procmsg ("Rotating Orientation tag value.\n"); } } ######################################################################################## # # rotateImg() rotates the image file by given angle # sub rotateImg { my $oldfile = shift; # original name to transform with jpegtran my $origfile = $oldfile . "_orig"; # backup original name my $newfile = $oldfile . "_rotated"; # temporay name to store rotated file my @addon = @_; # the switches for jpegtran to transform the image # jpegtran the image my $cmd = "jpegtran -copy none @addon -outfile \"$newfile\" \"$oldfile\""; dbgmsg (3, "$cmd\n"); if ($dryRun == 0) { system $cmd || die_renrot("System $cmd failed: $?\n"); } # preparing to write tags to the just rotated file my $exifAfterRot = new Image::ExifTool; $exifAfterRot->Options(Binary => 1); $exifAfterRot->SetNewValuesFromFile($oldfile, '*:*'); $exifAfterRot->SetNewValue("Orientation", 1, Type => 'ValueConv'); if ($dryRun == 0) { if ($backup != 0) { rename ($oldfile, $origfile) || die_renrot("$oldfile -> $origfile\n"); } rename ($newfile, $oldfile) || die_renrot("$newfile -> $oldfile\n"); } procmsg ("$oldfile was rotated\n"); # writing the changes to the EXIFs exifWriter($exifAfterRot, $oldfile) if ($dryRun == 0); } ######################################################################################## # # thumbWriter() writes binary data as thumbnail to given file # sub thumbWriter { my $file = shift; my $thethumb = shift; # preparing to write thumbnale to the just rotated file my $exifThumbnailed = new Image::ExifTool; $exifThumbnailed->Options(Binary => 1); $exifThumbnailed->SetNewValue("ThumbnailImage", $thethumb, Type => 'ValueConv'); # writing the changes to the EXIFs procmsg ("Writing thumbnail to $file...\n"); exifWriter($exifThumbnailed, $file) if ($dryRun == 0); } ######################################################################################## # # rotateThumbnail() rotates thumbnail only, where the file was rotated but # thumbnail was left untouched # sub rotateThumbnail { my $infoObj = shift; my $file = shift; # file, which thumbnale to transform with jpegtran my @addon = @_; # the switches for jpegtran to rotate the thumbnail if (not defined ${$$infoObj{ThumbnailImage}}) { warnmsg ("No thumbnail found.\n"); return; } my $origThumb = ${$$infoObj{ThumbnailImage}}; if (get_cfg_value($co_g, 'use ipc') == 0) { # extracting the thumbnail image my $ThumbnailOriginal = $file . "_thumborig"; unless (open (OLDTHUMBNAIL, ">$ThumbnailOriginal")) { errmsg ("$ThumbnailOriginal wasn't opened!\n"); return; } binmode OLDTHUMBNAIL; print OLDTHUMBNAIL $origThumb; unless (close (OLDTHUMBNAIL)) { warnmsg ("$ThumbnailOriginal wasn't closed!\n"); } # rotating the thumbnail my $ThumbnailOriginalRotated = $ThumbnailOriginal . "_rotated"; my $cmd = "jpegtran -copy none @addon -outfile \"$ThumbnailOriginalRotated\" \"$ThumbnailOriginal\""; dbgmsg (3, "$cmd\n"); if ($dryRun == 0) { system $cmd || die_renrot("System $cmd failed: $?\n"); } # write the just rotated thumbnail back to file thumbWriter($file, Image::RenRot::FileUtil->getFileData($ThumbnailOriginalRotated)); if ($dryRun == 0) { unlink ($ThumbnailOriginalRotated) || die_renrot("While killing $ThumbnailOriginalRotated.\n"); } unlink ($ThumbnailOriginal) || die_renrot("While killing $ThumbnailOriginal.\n"); } else { my $cmd = "jpegtran -copy none @addon"; dbgmsg (3, "$cmd\n"); # write the just rotated thumbnail back to file thumbWriter($file, Image::RenRot::FileUtil->piper($origThumb, $cmd)); } } ######################################################################################## # # usage() prints the instructions how to use the script # sub usage { my $exitcode = shift; my $v = shift; if ($v == 0) { infomsg ("RenRot version $VERSION (r$REVISION)\n"); } elsif ($v == 1) { infomsg ( "Usage: renrot <--extension EXTENSION> [--quiet] [--no-rotate] [--no-rename] [--name-template TPL] [--comment-file FILE] [--work-directory DIR] [[--] FILE1 FILE2 ...] Options: -c, --config-file configuration file to use -d, --work-directory set working directory -e, --extension extension of files to process: JPG, jpeg, ... Renaming options: -n, --name-template filename template (see manual for details) Rotating options: -r, --rotate-angle angle to rotate files and thumbnails by 90, 180, 270 --rotate-thumb rotate only thumbnails by 90, 180, 270 --mtime (*) set file mtime according to DateTimeOriginal tag Misc options: --dry-run do nothing, only print would have been done --use-ipc (*) rotate thumbnail via pipe, rather than via file -v increment debugging level by 1 -h, --help display this help and exit --version output version and exit (*) The options marked with this sign do not take arguments and can be negated, i.e. prefixed by 'no'. E.g. '--mtime' sets file mtime value, while '--nomtime' or '--no-mtime' disables setting it. Consult the documentation for a full list of options. "); } elsif ($v == 2) { pod2usage(-verbose => 2); } exit $exitcode; } ######################################################################################## # Usage : $name = template2name(...); # Purpose : builds file name according to the template # Returns : name as string # Parameters : $exifToolObj [ref] - # : $infoObj [ref] - # : $template [str] - the template to be used # : $fileNo [num] - counter for %c # : $fileName [str] - file name for %n and %e # : $counterSize [num] - the size of the counter for format (like "01", "0012") # : $angleSuffix [str] - suffix to add to the end of the rotated files # Throws : no exceptions # Comments : none # See Also : n/a sub template2name { my $exifToolObj = shift; my $infoObj = shift; my $template = shift; my $fileNo = shift; my $fileName = shift; my $counterSize = shift; my $angleSuffix = shift; if (not defined $template) { die_renrot("Template isn't given!\n"); } my $timestamp = getTimestamp($exifToolObj, $infoObj); my @tm = ($timestamp =~ m/(\d\d(\d\d))(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/); dbgmsg (4, "tm: @tm\n"); my $ExposureTime = ""; my $FileNumber = 'NA'; my $FNumber = ""; my $ISO = ""; my $WhiteBalance = ""; my $fileNameOriginal = ""; my $fileNameOriginalCounter = ""; # we can not use 0 as default value if (defined $infoObj->{"FileNumber"}) { $FileNumber = $infoObj->{"FileNumber"}; } if (defined $infoObj->{"ExposureTime"}) { $ExposureTime = $infoObj->{"ExposureTime"}; $ExposureTime =~ s/\//by/g; } if (defined $infoObj->{"FNumber"}) { $FNumber = $infoObj->{"FNumber"}; } if (defined $infoObj->{"ISO"}) { $ISO = $infoObj->{"ISO"}; } if (defined $infoObj->{"WhiteBalance"}) { $WhiteBalance = $infoObj->{"WhiteBalance"}; $WhiteBalance =~ s/[\s()]//g; } if (defined $infoObj->{"RenRotFileNameOriginal"}) { $fileNameOriginal = $infoObj->{"RenRotFileNameOriginal"}; } else { # Temporary stub to make renrot remember original file name for the formats # ExifTool writing to doesn't support. # Reported by Neil Hooey $fileNameOriginal = $infoObj->{"FileName"}; } # file name starts with letters and ends with digits if ($fileNameOriginal =~ m/^[[:alpha:]\-_]*(\d+)(\.[^\.]+)?$/) { $fileNameOriginalCounter = $1; } my ($base, $ext) = splitext($fileName); # file name %n and extension %e my @templatearea = split (//, $template); my %templatehash = ( '%' => "%", '#' => "#", 'a' => $angleSuffix, 'C' => $fileNameOriginalCounter, 'c' => sprintf($counterSize, $fileNo), 'd' => $tm[3], 'E' => $ExposureTime, 'e' => $ext, 'F' => $FNumber, 'H' => $tm[4], 'I' => $ISO, 'i' => $FileNumber, 'M' => $tm[5], 'm' => $tm[2], 'n' => $base, 'O' => (splitext($fileNameOriginal))[0], 'o' => $fileNameOriginal, 'S' => $tm[6], 'W' => $WhiteBalance, 'Y' => $tm[0], 'y' => $tm[1], ); my $thename = ""; my $substroffset = 0; my $substrchar; my $tag_to_name_val = ""; my $tag_to_name = ""; dbgmsg (4, "'$template' (length: ", scalar(@templatearea), ")\n"); while ($substroffset < scalar(@templatearea)) { $substrchar = $templatearea[$substroffset++]; if ($substrchar eq "%" and $substroffset < scalar(@templatearea)) { $substrchar = $templatearea[$substroffset++]; if (defined $templatehash{$substrchar}) { $thename .= $templatehash{$substrchar}; } } elsif ($substrchar eq "#" and $substroffset < scalar(@templatearea)) { $substrchar = $templatearea[$substroffset++]; until ($substrchar eq "#") { $tag_to_name .= $substrchar; $substrchar = $templatearea[$substroffset++]; } if (defined $infoObj->{$tag_to_name}) { $tag_to_name_val = $infoObj->{$tag_to_name}; dbgmsg (2, "Tag '$tag_to_name' = $tag_to_name_val\n"); $tag_to_name_val =~ s/[\s()]//g; $tag_to_name_val =~ s/[\/\\:;\'\"]/_/g; $thename .= $tag_to_name_val; } $tag_to_name_val = $tag_to_name = ""; } else { $thename .= $substrchar; } } return $thename; } ######################################################################################## # # MAIN() renames and rotates given files # # Check for modules $isThereIM = 1 if (defined loadpkg("Image::Magick")); getOptions(); parseConfig($configFile, $config_opts); switchColor(); # redefining options set in configuration file with set via CLI ones Image::RenRot::Config->apply($config_opts); dbgmsg (1, "Show what would have been happened (no real actions).\n") if ($dryRun != 0); # Validate aggregation mode possible values my $aggr_mode = get_cfg_value($co_aggr, 'mode'); if (not grep (/^$aggr_mode$/, ('none', 'delta', 'template'))) { warnmsg ("Aggregation mode isn't correct!\n"); } # Calculate ExifTool's verbosity my $exiftoolVerbose = ($verbose > $maxVerbosity) ? ($verbose - $maxVerbosity) : 0; # ExifTool object configuration my $exifTool = new Image::ExifTool; $exifTool->Options(Binary => 1, Unknown => 1, DateFormat => '%Y%m%d%H%M%S', Verbose => $exiftoolVerbose); chdir ($workDir) || die_renrot("Can't enter to $workDir!\n"); if ($subFileSet eq "") { # All things in ARGV will be treated as file names to process @files = @ARGV; } else { dbgmsg (2, "Reading file names for processing from file: $subFileSet\n"); @files = Image::RenRot::FileUtil->getFileDataLines($subFileSet); chomp(@files); } # if no file is given if (scalar(@files) == 0) { my $fileMask = "*" . $extToProcess; @files = grep { -f } glob ($fileMask); } # independently of @files initialization doing this my @filenames = (); foreach my $file (@files) { next if (not -f $file); # skip absent file or not a file next if (grep {/^$file$/} @excludeList); # skip excluded file push (@filenames, $file); } # No file to process? if (scalar(@filenames) == 0) { fatalmsg ("No files to process!\n"); exit 1; } # Parse configuration file tag set foreach my $cKey (keys %$config_opts) { next if ($cKey !~ m/^tag(file)?#\d+#\d+$/); # skip not a tag or tagfile my %tag = str2hash($config_opts->{$cKey}); foreach my $key (keys %tag) { $tags{$key} = $tag{$key}; if ($cKey =~ m/^tagfile/) { dbgmsg (4, "Read data from '$tags{$key}{value}' for '$key'\n"); $tags{$key}{value} = Image::RenRot::FileUtil->getFileData($tags{$key}{value}); } } } # Put command line arguments to appropriate tags $tags{'Comment'} = {value => Image::RenRot::FileUtil->getFileData($comfile)} if (defined $comfile); $tags{'UserComment'} = {value => $userComment} if (defined $userComment); # Merge tags from configuration file with command line arguments map { $tags{$_} = $tagsFromCli{$_} } keys %tagsFromCli; # Print parsed tags at debug level my @dbgTags = (); foreach my $key (sort (keys %tags)) { my $group = defined $tags{$key}{group} ? $tags{$key}{group} : ""; my $value = defined $tags{$key}{value} ? $tags{$key}{value} : ""; push (@dbgTags, "$key [$group] = $value"); } dbgmsg (4, "Tags:\n", join("\n", @dbgTags), "\n") if (scalar(@dbgTags) > 0); # Validate angle value if ((defined $rotateAngle and not grep(/^$rotateAngle$/, keys %rotangles)) or (defined $rotateThumbnail and not grep(/^$rotateThumbnail$/, keys %rotangles))) { fatalmsg ("Angle should be 90, 180 or 270!\n"); exit 1; } @files = sort @filenames; dbgmsg (4, "Pushed files(", scalar(@files), "):\n", join("\n", @files), "\n"); # Preparing the variable, which contains the format of the counter output my $counterSize; if ($countFF != 0) { my $size = length((scalar(@filenames) - 1) * $countStep + $countStart); $counterSize = "%." . $size . "d"; dbgmsg (1, "Counter size: $size (amount files in cache: ", scalar(@filenames), ")\n"); } else { $counterSize = "%d"; } renRotProcess($exifTool, $counterSize); contactSheetGenerator($exifTool) if ($isThereIM); aggregationProcess($exifTool, $counterSize); __END__ =head1 NAME renrot - rename and rotate images according EXIF data =head1 SYNOPSIS renrot [OPTIONS] [[B<-->] FILE1 FILE2 ...] =head1 DESCRIPTION B is intended to work with a set of files containing EXIF data and can do two things to them -- rename and rotate. A set of files can be given either explicitly or using the B<--extension> option, which select the files with the given suffix. B operates on files in current working directory, unless given the B<--work-directory> option, which changes this default. B renames input files using a flexible name template (which, among others, uses DateTimeOriginal and FileModifyDate EXIF tags, if they exist, otherwise names the file according to the current timestamp). Further, B can aggregate files according to the shooting time period or to a given template. Additionally, it rotates files and their thumbnails, as per Orientation EXIF tag. If that tag is absent, the program allows to set rotation parameters using B<--rotate-angle> and B<--rotate-thumb> command line options. This is currently implemented only for JPEG format. The program can also place commentaries into the following locations: =over - Commentary tag from file (see B<--comment-file> option) - UserComment tag from configuration variable (see L section) =back Personal details may be specified via XMP tags defined in a configuration file, see L section. In addition, B can aggregate all files in different directories, according to a given date/time pattern template, set with B<--aggr-template>. =head1 OPTIONS =over =item B<-c> or B<--config-file> F Path to the configuration file. =item B<-d> or B<--work-directory> F Define the working directory. =item B<--exclude> F Specify files to exclude. Wildcards are not allowed. If a set of files is given, there must be as many occurrences of this option as there are files in the set. =item B<--sub-fileset> F Get names of files to operate upon from F. The file must contain a file name per line. This option is useful when you need to process only a set of X from Y files in the directory. If specified, the rest of files given in the command line is ignored. =item B<-e> or B<--extension> I Process the files with given I (JPG, jpeg, CRW, crw, etc). Depending on the operating system, the extension search might or might not be case-sensitive. =item B<--mtime>, B<--no-mtime> Defines whether to set the file's mtime, using DateTimeOriginal tag value. Use B<--no-mtime> to set it to current time stamp after processing. =item B<--no-renrot> or B<--nochg> Do not rename, rotate, tag and mtime images. It saves files from any changes while allows to do aggregation, contact sheet generation e.t.c. =item B<--use-color>, B<--no-use-color> Colorize output. This does NOT work under Windows. =item B<--dry-run> Do not do anything, only print would have been done. =item B<-g> or B<--generate-thumb> Generation and writing ThumbnailImage tag. The original value of the ThumbnailImage tag remains intact. To rewrite it you need to delete it first (look exiftool examples). =item B<--use-ipc>, B<--no-use-ipc> Rotate thumbnails using pipe, rather than files. This does NOT work under Windows. =item B<-v> Increase debugging level by 1. Debugging levels from 1 to 4 are internal levels, the levels from 5 till 9 are equivalent to levels 1-5 levels ExifTool with the maximum verbosity for B. =item B<-?> or B<--help> Display short usage summary and exit. =item B<--version> Output version information and exit. =back =head1 B =over =item B<--aggr-mode> I Run aggregation process in given I. Possible values are: none, delta or template. =item B<--aggr-delta> I Aggregation time delta, in seconds. Files with DateTimeOriginal and ones of the previous file delta, greater than B<--aggr-delta> are placed in the directories, with the names are constructed by concatenating the value of the B<--aggr-directory> option and the directory name counter. =item B<--aggr-directory> F Aggregation directory name prefix (default is I), have to be on the same file system (or on the file system which supports symbolic links in case of virtual aggregation), relative to the current working directory or an absolute path. =item B<-a> or B<--aggr-template> I section. =item B<--aggr-virtual>, B<--no-aggr-virtual> Defines virtualization for existent aggregation modes. The main effect of B<--aggr-virtual> is that any files to be aggregated remain untouched in their places, and relative symbolic links pointing to them are stored in the directory tree created. Use B<--no-aggr-virtual> to prevent virtualization. =back =head1 B =over =item B<--contact-sheet>, B<--no-contact-sheet> or B<--cs>, B<--no-cs> Create the contact sheet. Currently it works with ThumbnailImage EXIFs and the files defined as thumbnails (see the option B<--contact-sheet-thm>, below) =item B<--contact-sheet-file> or B<--cs-file> F Base file name for montage files. =item B<--contact-sheet-dir> or B<--cs-dir> F Temporary directory for montage (created in the begining and deleted at the end of the process) =item B<--contact-sheet-thm> or B<--cs-thm> Files for the montage are already thumbnails =back Options bellow are native ImageMagic montage options look ImageMagick documentation for montage options: I and I Note please, for I use RGB triplets only like I<000> for the I or I for the I. =over =item B<--contact-sheet-tile> or B<--cs-tile> I Tile MxN (IM: -tile) =item B<--contact-sheet-title> or B<--cs-title> I Set the title of the contact sheet (IM: -title). =item B<--contact-sheet-bg> or B<--cs-bg> I Background color (IM: -background). =item B<--contact-sheet-bd> or B<--cs-bd> I Border color (IM: -bordercolor). =item B<--contact-sheet-mt> or B<--cs-mt> I Frame color (IM: -mattecolor). =item B<--contact-sheet-fn> or B<--cs-fn> I Render text with this font (IM: -font). =item B<--contact-sheet-fl> or B<--cs-fl> I Color to fill the text (IM: -fill). =item B<--contact-sheet-lb> or B<--cs-lb> I Assign a label to an image (IM: -label). =item B<--contact-sheet-fr> or B<--cs-fr> I Surround image with an ornamental border in N pixels (IM: -frame). =item B<--contact-sheet-pntsz> or B<--cs-pntsz> I Font point size (IM: -pointsize). =item B<--contact-sheet-shadow> or B<--cs-shadow> Set the shadow beneath a tile to simulate depth (IM: -shadow). =item B<--contact-sheet-thm-fl> or B<--cs-thm-fl> I Color to fill the text in generated thumbnail. =item B<--contact-sheet-thm-fn> or B<--cs-thm-fn> I Render the generated thumbnail text with this font (IM: -font). =item B<--contact-sheet-thm-grfr> or B<--cs-thm-grfr> I Generated thumbnail background gradient COLOR-from =item B<--contact-sheet-thm-grto> or B<--cs-thm-grto> I Generated thumbnail background gradient COLOR-to =item B<--contact-sheet-thm-text> or B<--cs-thm-text> I Generated thumbnail text =item B<--contact-sheet-rank> or B<--cs-rank> Run ranking process according to the ranks defined with B<--contact-sheet-rank-file> The result is the colored frames of the thumbnails of contact sheets. =item B<--contact-sheet-rank-file> or B<--cs-rank-file> Path to the file with ranks. Its format is a "file rankcolor" per line. Filename separated from the color by space or tabulation. =over 01.file.jpg red 02.JPG CornflowerBlue 03.jpg aquamarine 04.file.JPG green =back Only the files found in the file will be ranked. =back =head1 B =over =item B<--keywords>, B<--no-keywords> Whether to fill Keywords tag. Default is to not. Be careful, since with this option enabled, the existing keywords are rewriten. The keywords are taken from F<.keywords> file or file specified with option B<--keywords-file>. =item B<-k> or B<--keywords-file> F Path to the file with keywords. Its format is a keyword per line. The CR and LF symbols are removed. Empty (only whitespace) lines are ignored. Any leading and trailing whitespace is removed. For example, the line C< _Test_ CRLF> is read as C<_Test_>. =item B<--keywords-replace>, B<--no-keywords-replace> Replace existing Keywords tag list rather than add new values to it. Default is not to replace. =back =head1 B =over =item B<-n> or B<--name-template> I section. Interpreted sequences are: =over B<%%> a literal % B<%#> a literal # B<%C> Numeric part of the original file name. Implemented for the sake of cameras, that do not supply FileNumber EXIF tag (currently all makes, except I). Such cameras generate file names starting with letters and ended with digits. No other symbols are allowed in file names, except C<->, C<.> and C<_>. B<%c> Ordinal number of file in the processed file set (see also B<--counter-fixed-field> option). B<%d> Day of month (01-31). B<%E> The value of ExposureTime tag, if defined. B<%e> Old file extension B<%F> The value of FNumber tag, if defined. B<%H> Hour (00-23). B<%I> The value of ISO tag, if defined. B<%i> FileNumber tag if exists (otherwise, it is replaced by string C). B<%M> Minute (00-59). B<%m> Month (01-12). B<%n> Previous filename (the one before B started processing). B<%O> Base part of the original filename (see B<%o>). In other words, the first part from the beginning to the last dot character. B<%o> The name file had before it was processed by B for the first time. If the file was processed only once, the tag RenRotFileNameOriginal is set to the original file name. B<%S> Second (00-59) B<%W> The value of WhiteBalance tag, if defined. B<%Y> Year with the century (1900, 1901, and so on) B<%y> Year without a century (00..99) You can use value of any EXIF tag to be included as name part. To do that you need to embrace tag name with sign B<"#">, while building name template (see L). Be careful, since any binary EXIF (like ThumbnaiImage) can produce totally unexpected results. =back =item B<--no-rename> Do not rename files (default is to rename them to YYYYmmddHHMMSS.ext) =item B<--counter-fixed-field>, B<--no-counter-fixed-field> Set fixed length for file counter, used in file name templates (see B<%c>). It is enabled by default. Use B<--no-counter-fixed-field> to undo its effect. =item B<--counter-start> I Initial value for the file counter (default is I<1>) =item B<--counter-step> I Step to increment file counter with (default is I<1>) =back =head1 B =over =item B<-r> or B<--rotate-angle> I Define the angle to rotate files and thumbnails. Allowed values for I are 90, 180 or 270. It is useful for files not having Orientation tag. =item B<--rotate-thumb> I Rotate only thumbnails. Allowed values for I are 90, 180 or 270 degrees. Use if the files which were already rotated, but their thumbnails were not. =item B<--only-orientation> Rotate by changing the value of Orientation tag, no real rotation will be made. The sequence of values to rotate an image from normal (0 degrees) by 90 degrees clockwise is: 0 -> 90 -> 180 -> 270 -> 0. It means. set Orientation tag to 90cw after the first rotation, and increase that value by 90 each time the rotation is applied. For 270cw the rotation algorithm uses the reverted sequence. Rotation by 180cw triggers values in two pairs: 0 <-> 180 and 90 <-> 270. This option cannot be applied to mirror values of Orientation tag. =item B<--trim>, B<--no-trim> Pass the C<-trim> option to L, to trim if needed. By default, trimming is enabled. Use B<--no-trim> to disable it. =item B<--no-rotate> Do not rotate images (default is to rotate according to EXIF data). =back =head1 B =over =item B<--comment-file> F File with commentaries. It is a low priority alias to I. =item B<--user-comment> I A low priority alias to I<--tag UserComment: STRING> =item B<-t> or B<--tag> I See the section L, for the detailed description =item B<--no-tags> No user's defined tags will be written. =back =head1 B