#!/usr/local/bin/perl -w # # $Id$ # use strict; require 5.004; use Time::localtime; use Image::ExifTool; use Getopt::Long; our $VERSION = "0.15.1"; # the version of this script my $noRotation; # no rotation needed, default is to rotate my $rotate; # define the angle, to rotate on, 90 or 270 my $rotateThumbnail; # define the angle, to rotate on, 90 or 270 my $noRename; # no rename needed, default is to rename to the YYYYmmddHHMMSS.jpg my $noComment; # no comment needed my $namePrefix; # prefix to the filename my $namePrefixOnly; # prefix to the filename (for case when no date in name needed) my $nameSuffix; # suffix to the filename my $extentionToProcess; # the extention of files to work with my $extentionToProcessLowCase; # lowercased extention of files to work with my $usageOutput = 0; # flag to show the usage my $configFile; # config file my $comfile; # file with commentary my $verbose = 0; # verbosity of output my $dir = "."; # we'll work ONLY in current directory # information message sub infomsg { print STDERR @_; } # warning message sub warnmsg { print STDERR "Warning: "; print STDERR @_; } # error message sub errmsg { print STDERR "ERROR: "; print STDERR @_; } # fatal message sub fatalmsg { print STDERR "FATAL: "; print STDERR @_; } # debug message sub dbgmsg { my $level = shift; print STDERR @_ if ($verbose >= $level); } # parse command line sub getopt { my $ver; # need version my $help; # need help my $getopt = GetOptions ( "work-directory|w=s" => \$dir, "ext|e=s" => \$extentionToProcess, "rotate|r=i" => \$rotate, "rotate-thumb=i" => \$rotateThumbnail, "no-rotate" => \$noRotation, "no-rename" => \$noRename, "comment-file=s" => \$comfile, "name-prefix=s" => \$namePrefix, "name-prefix-only" => \$namePrefixOnly, "name-suffix" => \$nameSuffix, "config-file|c=s" => \$configFile, "v+" => \$verbose, "version" => \$ver, "help|?" => \$help); if (defined $help && $help != 0) { usage(); exit 0; } if (defined $ver && $ver != 0) { infomsg ("renrot version $VERSION\n"); exit 0; } if (not defined $extentionToProcess) { errmsg ("Extention of files is required!\n"); exit 1; } if ($getopt == 0) { usage(); exit 1; } # change user's parameter '*.ext' to 'ext' $extentionToProcess =~ s/^\*\.//; dbgmsg (1, "Process with '$extentionToProcess' extention.\n"); } # parse command-line options getopt(); # the default commentary to add to UserComment our $COMMENTARY; our $CiAdrCity; our $CiAdrCtry; our $CiAdrExtadr; our $CiAdrPcode; our $CiAdrRegion; our $CiEmailWork; our $CiTelWork; our $CiUrlWork; our $City; my $etc_rc = "/etc/renrot.rc"; my $usrlocaletc_rc = "/usr/local/etc/renrot.rc"; my $homedir_rc = $ENV{"HOME"} . "/.renrotrc";; if ( defined ( $configFile ) and -f $configFile ) { do $configFile; } else { if ( -f $etc_rc ) { do $etc_rc; } if ( -f $usrlocaletc_rc ) { do $usrlocaletc_rc; } if ( -f $homedir_rc ) { do $homedir_rc; } } my $exifTool = new Image::ExifTool; $exifTool->Options(Binary => 1, Unknown => 1, DateFormat => '%Y%m%d%H%M%S'); my $file; # file in the directory my @files; # array of the sorted filenames to process my @filenames; # array of the filenames to process my $info; # ImageInfo object my $newfilename; # the name file to be renamed to my $orientation = ''; # Orientation tag value my @filenameSplited; # array to hold filename splitted to the parts my $comment; # where to put the commentary to write my @commentar; # array to put the content of the file with commentary my $filecounter = 1; # counter to cat and increment when DateTimeOriginal is absent my $countersuffix; # the suffix to add after timestamp to name the files without DateTimeOriginal tag my $now; # now timestamp to use to name the files without DateTimeOriginal tag my $rotas; # how to rotate my @rotparms = ('' , '-flip horizontal' , '-rotate 180' , '-flip vertical' , '-transpose' , '-rotate 90' , '-transverse' , '-rotate 270'); my %rotangles = ( '90' => '-rotate 90', '270' => '-rotate 270'); my $errorMessage; my $warningMessage; my $suffix = ""; my $prefix = ""; my $ExposureTime = ""; my $WhiteBalance = ""; chdir ($dir) || ( fatalmsg ("Can't enter to $dir!\n"), die ); opendir (DIR, $dir) || ( fatalmsg ("Can't open $dir!\n"), die ); while ( defined ( $file = readdir DIR )) { next if $file =~ /^\.\.?$/; # skip the current directory entry next if ( -d $file ); # is this a directory? $extentionToProcessLowCase = $extentionToProcess; $extentionToProcessLowCase =~ tr/A-Z/a-z/; @filenameSplited = split (/\./,$file); # if we have defined extention files here if ( $filenameSplited[$#filenameSplited] && ( $filenameSplited[$#filenameSplited] eq $extentionToProcess ) ) { push @filenames, $file; } } @files = sort @filenames; foreach $file ( @files ) { print "Processing file: $file...\n"; $info = $exifTool->ImageInfo($file); $newfilename = ""; ################################################################################# # ## analyzing whether and how to rename file # if ( defined $noRename ) { infomsg ("No renaming asked, filename is left untouched.\n"); $newfilename = $file; } else { if ( defined $namePrefix ) { $newfilename = $namePrefix . "."; } if ( defined $nameSuffix ) { if ( defined $info->{"ExposureTime"} ) { $ExposureTime = $info->{"ExposureTime"}; $ExposureTime =~ s/\//by/g; } else { $ExposureTime = 0; } if ( defined $info->{"WhiteBalance"} ) { $WhiteBalance = $info->{"WhiteBalance"}; $WhiteBalance =~ s/[\s()]//g; } else { $WhiteBalance = 0; } $suffix = ".W" . $WhiteBalance . "I" . $info->{"ISO"} . "A" . $info->{"FNumber"} . "E" . $ExposureTime; } if ( defined ( $info->{"DateTimeOriginal"} ) && defined ( $info->{"FileNumber"} ) && !(defined $namePrefixOnly) ) { $newfilename = $newfilename . $info->{"DateTimeOriginal"} . "." . $info->{"FileNumber"} . $suffix . "." . $extentionToProcessLowCase; } elsif ( defined ( $info->{"DateTimeOriginal"} ) && !( defined ( $info->{"FileNumber"} ) ) && !( defined $namePrefixOnly ) ) { $countersuffix = sprintf('%.4d', $filecounter); $newfilename = $newfilename . $info->{"DateTimeOriginal"} . "." . $countersuffix . $suffix . "." . $extentionToProcessLowCase; $filecounter++; } else { # if not exists, than adding XXXX index to the end of timestamp $countersuffix = sprintf('%.4d', $filecounter); if ( defined $namePrefixOnly ) { $prefix = ""; } else { $prefix = now(); $prefix = $prefix . "."; } $newfilename = $newfilename . $prefix . $countersuffix . "." . $suffix . $extentionToProcessLowCase; $filecounter++; } if ( $file ne $newfilename ) { rename ( $file, $newfilename ) || ( fatalmsg ("Unable to rename $file -> $newfilename.\n"), die ); print "Renamed: $file -> $newfilename\n"; } else { warnmsg ("No renaming needed for $newfilename, it looks as needed!\n"); } } ################################################################################################ # ## analyzing whether to rotate # if ( defined $noRotation ) { infomsg ("No rotation asked, file orientation is left untouched.\n"); } elsif ( defined $rotate ) { dbgmsg (2, "We'll deal with: $newfilename and $rotangles{$rotate}.\n"); rotateFile($newfilename, $rotangles{$rotate}); rotateThumbnail($newfilename, $rotangles{$rotate}); } elsif ( defined $rotateThumbnail ) { rotateThumbnail($newfilename, $rotangles{$rotateThumbnail}); } else { if ( defined( $orientation = $exifTool->GetValue("Orientation", 'ValueConv') ) ) { if ( $orientation > 1 ) { $rotas = $rotparms[$orientation - 1]; rotateFile($newfilename, $rotas); rotateThumbnail($newfilename, $rotas); } elsif ( $orientation == 1 ) { infomsg ("No need to rotate, orientation is: Horisontal (normal).\n"); } else { errmsg ("Something wrong, orientation is < 1: $orientation.\n"); } } else { warnmsg ("Orientation tag is absent!\n"); } } ############################### # ## analyzing whether to comment # my $comentedfilename = $newfilename . "_comment"; if ( $comfile ) { if ( open ( COMMENT, "<$comfile" ) ) { @commentar = ; unless ( close ( COMMENT ) ) { errmsg ("$comfile wasn't closed!\n"); } $comment = join ( '', @commentar ); # preparing to write the commentary $exifTool->SetNewValue(Comment => $comment); } } else { infomsg ("No commenting, file commentary is left untouched.\n"); } ###################################### # ## preparing to write the UserComment and some additional tags # if ( defined ( $COMMENTARY ) ) { $exifTool->SetNewValue(UserComment => $COMMENTARY); } if ( defined ( $CiAdrCity ) ) { $exifTool->SetNewValue('CreatorContactInfoCiAdrCity', $CiAdrCity, Group=>'XMP'); } if ( defined ( $CiAdrCtry ) ) { $exifTool->SetNewValue('CreatorContactInfoCiAdrCtry', $CiAdrCtry, Group=>'XMP'); } if ( defined ( $CiAdrExtadr ) ) { $exifTool->SetNewValue('CreatorContactInfoCiAdrExtadr', $CiAdrExtadr, Group=>'XMP'); } if ( defined ( $CiAdrPcode ) ) { $exifTool->SetNewValue('CreatorContactInfoCiAdrPcode', $CiAdrPcode, Group=>'XMP'); } if ( defined ( $CiAdrRegion ) ) { $exifTool->SetNewValue('CreatorContactInfoCiAdrRegion', $CiAdrRegion, Group=>'XMP'); } if ( defined ( $CiEmailWork ) ) { $exifTool->SetNewValue('CreatorContactInfoCiEmailWork', $CiEmailWork, Group=>'XMP'); } if ( defined ( $CiTelWork ) ) { $exifTool->SetNewValue('CreatorContactInfoCiTelWork', $CiTelWork, Group=>'XMP'); } if ( defined ( $CiUrlWork ) ) { $exifTool->SetNewValue('CreatorContactInfoCiUrlWork', $CiUrlWork, Group=>'XMP'); } if ( defined ( $City ) ) { $exifTool->SetNewValue('City', $City, Group=>'XMP'); } # writing the changes to the EXIFs my $res = $exifTool->WriteInfo( $newfilename, $comentedfilename); if ( $res == 1 ) { dbgmsg (2, "Writing to $comentedfilename ok.\n"); rename ( $comentedfilename, $newfilename ) || ( fatalmsg ("$comentedfilename -> $newfilename\n"), die ); } elsif ( $res == 2 ) { warnmsg ("No changes between $newfilename and $comentedfilename. No EXIF was written.\n"); unlink $comentedfilename; } else { my $errorMessage = $exifTool->GetValue('Error'); my $warningMessage = $exifTool->GetValue('Warning'); if ( defined $errorMessage ) { errmsg ("ExifTool: $errorMessage\n"); } if ( defined $warningMessage ) { warnmsg ("ExifTool: $warningMessage\n"); } } print "\n"; } closedir(DIR); exit 0; ######################################################################################## # # now() returns timestamp in form YYYYmmddHHMMSS # sub now { my $date = (localtime); my $result = sprintf("%04d%02d%02d%02d%02d%02d", $$date[5] + 1900, $$date[4], $$date[3], $$date[2], $$date[1], $$date[0]); return $result; } ######################################################################################## # # rotating the image and it's thumbnail, rewriting Orientation tag and the commentary # to the result file # sub rotateFile { # $_[0] - file name to transform with jpegtran # $_[1] - the switches for jpegtran to transform the image my $oldfile = $_[0]; # original name my $origfile = $_[0] . "_orig"; # backup original name my $newfile = $_[0] . "_rotated"; # temporay name to store rotated file # jpegtran the image my $cmd = "jpegtran -copy all " . $_[1] . " -outfile " . $newfile . " " . $oldfile; dbgmsg (3, "Rotate: $cmd\n"); system $cmd || ( fatalmsg ("System $cmd failed: $?\n"), die ); rename ( $oldfile, $origfile ) || ( fatalmsg ("$oldfile -> $origfile\n"), die ); rename ( $newfile, $oldfile ) || ( fatalmsg ("$newfile -> $oldfile\n"), die ); # preparing to write Orientation tag to the just rotated file my $exifAfterRot = new Image::ExifTool; $exifAfterRot->Options(Binary => 1); my $exifedfile = $oldfile . "_exifed"; # the file name, exifed file to be put to $exifAfterRot->SetNewValue("Orientation", 1, Type => 'ValueConv'); # writing the changes to the EXIFs my $res = $exifAfterRot->WriteInfo($oldfile, $exifedfile); if ( $res == 1 ) { dbgmsg (2, "Writing to $exifedfile ok.\n"); rename ( $exifedfile, $oldfile ) || ( fatalmsg ("$exifedfile -> $oldfile\n"), die ); } elsif ( $res == 2 ) { infomsg ("No changes for $exifedfile.\n"); } else { my $errorMessage = $exifAfterRot->GetValue('Error'); my $warningMessage = $exifAfterRot->GetValue('Warning'); if ( defined $errorMessage ) { errmsg ("ExifTool: $errorMessage\n"); } if ( defined $warningMessage ) { warnmsg ("ExifTool: $warningMessage\n"); } } $oldfile = $origfile = $newfile = ""; } ######################################################################################## # # rotate thubnail only, where the file was rotated but thumbnail was left untouched # sub rotateThumbnail { # $_[0] - file, which thumbnale to transform with jpegtran # $_[1] - the switch for jpegtran to rotate the thumbnail if ( defined ${$$info{ThumbnailImage}} ) { my $fileToThumbnail = $_[0]; # original name # extracting the thumbnail image my $ThumbnailOriginal = $fileToThumbnail . "_thumborig"; unless ( open ( OLDTHUMBNAIL, ">$ThumbnailOriginal" ) ) { errmsg ("$ThumbnailOriginal wasn't opened!\n"); } print OLDTHUMBNAIL ${$$info{ThumbnailImage}}; unless ( close ( OLDTHUMBNAIL ) ) { warnmsg ("$ThumbnailOriginal wasn't closed!\n"); } # rotating the thumbnail my $ThumbnailOriginalRotated = $ThumbnailOriginal . "_rotated"; my $cmd = "jpegtran -copy all " . $_[1] . " -outfile " . $ThumbnailOriginalRotated . " " . $ThumbnailOriginal; dbgmsg (3, "Rotate thmb: $cmd\n"); system $cmd || ( fatalmsg ("System $cmd failed: $?\n"), die ); # preparing to write the just rotated thumbnail, back to the file unless ( open ( THUMBNAILROTATED, "<$ThumbnailOriginalRotated" ) ) { errmsg ("$ThumbnailOriginalRotated wasn't opened!\n"); } binmode THUMBNAILROTATED; my ( @rthumb ) = ; my $thethumb = join ('',@rthumb); unless ( close ( THUMBNAILROTATED ) ) { errmsg ("$ThumbnailOriginalRotated wasn't closed!\n"); } # preparing to write thumbnale to the just rotated file my $exifThumbnailed = new Image::ExifTool; $exifThumbnailed->Options(Binary => 1); my $fileThumbnailed = $fileToThumbnail . "_thumb"; # the file name, thumbnailed file to be put to $exifThumbnailed->SetNewValue("ThumbnailImage", $thethumb, Type => 'ValueConv'); # writing the changes to the EXIFs my $res = $exifThumbnailed->WriteInfo($fileToThumbnail, $fileThumbnailed); if ( $res == 1 ) { dbgmsg (2, "Writing to $fileThumbnailed ok.\n"); rename ( $fileThumbnailed, $fileToThumbnail ) || ( fatalmsg ("$fileThumbnailed -> $fileToThumbnail\n"), die ); } elsif ( $res == 2 ) { infomsg ("No changes for $fileThumbnailed.\n"); } else { my $errorMessage = $exifThumbnailed->GetValue('Error'); my $warningMessage = $exifThumbnailed->GetValue('Warning'); if ( defined $errorMessage ) { errmsg ("ExifTool: $errorMessage\n"); } if ( defined $warningMessage ) { warnmsg ("ExifTool: $warningMessage\n"); } } unlink ($ThumbnailOriginalRotated) || ( fatalmsg ("While killing $ThumbnailOriginalRotated.\n"), die ); unlink ($ThumbnailOriginal) || ( fatalmsg ("While killing $ThumbnailOriginal.\n"), die ); } else { warnmsg ("No thumbnail found.\n"); } } ######################################################################################## # # usage() - the instruction how to use the script # sub usage { infomsg ( "Usage: renrot [-h|--help] [--no-rotate] [--no-rename] [--comment-file FILE] [--version] [--name-prefix PREFIX] [--work-directory DIR] <--ext EXTENTION> -h | --help this very page -c | --config-file use this config file (see manual for details) --work-directory specify working directory --ext extention of files to be processed, JPG, jpg, JPEG e.t.c. --no-rotate no rotation needed, default is to rotate --rotate <90|270> define the angle, to rotate file and the thumbnail on, 90 or 270. it's for the files where no Orientation tag is present and set right way --rotate-thumb <90|270> rotate thumbnail ONLY. 90 or 270 degree it's for the files which were rotated, but thumbnail wasn't --no-rename no rename needed, default is to rename to the YYYYmmddHHMMSS.jpg --name-prefix prefix to the filename --name-prefix-only prefix to the filename when no date in the name needed --name-suffix suffix to the filename, the values of aperture, exsposure time, iso and white balance are added to the filename as suffix, see manual --comment-file file with commentary --version the version of this script "); } __END__ =head1 NAME renrot - rename and rotate images according EXIF data =head1 SYNOPSIS renrot [OPTIONS] =head1 DESCRIPTION B is intended to work ONLY in current directory. You can change the behaviour by specify B<--work-directory> option. It renames files according the DateTimeOriginal EXIF tag if it's exists than if it isn't, according the FileModifyDate EXIF tag if it's exists than if it isn't, the name will be given according the current timestamp and will look as YYYYmmddHHMMSS-XXXXX.jpg where XXXXX is whether file ID, if exists in EXIF (as for Canon) or incrementable suffix to the name YYYYmmddHHMMSS. It rotates file and it's thumbnail, accordingly Orientation EXIF tag. If the tag is absent or miss set, than the script allows to rotate the file as well as it's thumbnail, After all, the script can put the commentary to: - Commentary tag if commentary file provided with --comment-file option - UserComment tag if $COMMENTARY variable set in config file Personal data could be specified via XMP tags defined in config file, look CONFIG section. =over 5 =head1 OPTIONS =over 5 =item B<-h> or B<--help> the help =item B<-c> or B<--config-file> I location of the config file =item B<--work-directory> I define the working directory =item B<--ext> I extention of files to be processed, JPG, jpg, JPEG, CRW, crw e.t.c. =item B<--rotate> I<90> or I<270> define the angle, to rotate file and the thumbnail on, 90 or 270 it's for the files where no Orientation tag is set right way =item B<--rotate-thumb> I<90> or I<270> rotate thumbnail ONLY. 90 or 270 degree. it's for the files which were rotated, but thumbnail wasn't =item B<--no-rotate> no rotation needed, default is to rotate =item B<--no-rename> no rename needed, default is to rename to the YYYYmmddHHMMSS.jpg =item B<--comment-file> I file with commentary =item B<--version> the version of this script =item B<--name-prefix> I prefix to the filename, if defined, filenames looks like: myprefix-YYYYmmddHHMMSS-XXXXX.jpg =item B<--name-prefix-only> prefix to the filename when no date in the filename whished, if defined, filenames looks like: PREFIX-XXXXX.jpg works with B<--name-prefix> =item B<--name-suffix> suffix to the filename, the value of tags: FNumber, CameraISO, WhiteBalance and ExposureTime, if defined, filenames looks like: 20050813072758.a8-e1by1760-i400-wbDaylight.crw 20050813072814.a6.3-e1by1244-i200-wbClowdy.jpg where I stays for FNumber, I for ExposureTime, I for CameraISO and I for WhiteBalance =head1 CONFIG Config file could be used to set some variables. Config file is just perl code. B looks for B<--config-file> option firstly, than it looks for file named renrot.rc in system directories such as /etc and /usr/local/etc. And finally it looks for file named .renrotrc in home directory of the user. User defined config file can be given via B<--config-file> option. These tags could be set via config file: =item B anything you'd wanna put as commentary =item B City =item B street =item B extended address =item B zip code =item B region =item B email =item B phone =item B URL =item B city =head1 FILES config file placement (with high priority first) =item B<~/.renrotrc> =item B =item B =head1 AUTHOR Copyright 2005-2006, Zeus Panchenko =head1 SEE ALSO L,L =cut