Copy and timestamp digital photos

I love photography, but I love digital photography even more. Given that I am not a very good photographer, that I like to take many variations on the same picture, and that I like instant feedback, digital cameras have been a huge blessing to me.

My first digital camera was a Fuji FinePix 4900Z which came with a rudimentary but very useful image copying program. This program allowed me to specify a renaming scheme for the images I was copying from the camera to the computer hard drive. As each image was being copied, it would be renamed with a timestamp of the form YYYY_MM_DD_HHMMSScc where cc is a two character extension to distinguish between images taken within the same second.

I loved this feature. I never had to contend with DSCxxxx style names. I could look at the name of a file, and immediately recognize the shoot it came from. It was a great help in organizing etc etc.

When I got my Canon Powershot S1 IS, I was very disappointed with the bundled software. ZoomBrowser was slow, create thumbnail files in all the directories, and it lacked this copying scheme. To top it all off, the camera put images in various subfolders according to the settings with which they were taken, so using the Fuji software with it was tedious.

So, I wrote the short script given here, uninstalled ZoomBrowser, and never looked back. The script is not very involved, or very general, but it is short and easily modifiable.

Enter Perl

I am running ActivePerl on Windows XP. The installation program sets environment variables so that, if a script is in the PATH, I can invoke it from the command line just by typing its name. The script takes up to arguments. The first is required, and specifies the top-level source directory to copy from. The script will go through all of the subdirectories under this directory, and copies all files matching the media file specification used. The second argument can be thought of as an album name under which the organize the photos. If it is omitted, the images are copied into a directory named after the current year and month.

The script places this album subdirectory in %HOME%\My Pictures. All of this is customizable by editing the code: I wrote the bare minimum I needed.

Please note that while I provide this script in the hopes that it will be useful to you, I make NO GUARANTEES OR WARRANTIES OF ANY KIND. The code is provided subject to the same terms as Perl itself. For more information, please refer to the Perl Artistic License.


use strict;
use warnings;

use File::Basename;
use File::Find::Rule;
use File::Spec::Functions qw'canonpath catfile';
use File::stat;

my $src = shift;

die "Source directory not specified\n" unless defined $src ;
die "$src is not a directory\n" unless -d $src;

die "HOME environment variable not set\n" unless defined $ENV{HOME};
use constant MYPICS => catfile $ENV{HOME}, 'My Pictures';

my $album = shift;
    my ($mon, $year) = (localtime time)[4 .. 5];
    $year += 1900;

    my $year_dir = catfile MYPICS, sprintf('%4.4d', $year);
    -d $year_dir
        or mkdir $year_dir
        or die "Cannot create $year_dir: $!\n";

    if(defined $album) {
        $album = catfile $year_dir, $album;
    } else {
        $album = catfile(
            sprintf('%4.4d_%2.2d', $year, $mon + 1),

unless(-d $album) {
    mkdir $album or die "Cannot create $album: $!\n";

my @ext = (

my @pics = File::Find::Rule->file()
                           ->name( @ext )
                           ->in( $src );

for my $pic (@pics) {
    my $ext = lc (fileparse $pic, @ext )[2];

    my ($sec, $min, $hour, $mday, $mon, $year)
        = (localtime stat($pic)->mtime)[0 .. 5];

    my $basename = sprintf(
        $year + 1900, $mon + 1, $mday, $hour, $min, $sec

    my $t = 'aa';
    my $dest;
    do {
        $dest = catfile($album, $basename.$t.$ext);
    } while(-e $dest);

    $dest = canonpath($dest);
    $pic  = canonpath($pic);
    print  qq{"$pic" => "$dest"\n};
    system qq{cmd /c copy "$pic" "$dest"};