#!/usr/bin/perl # UCW Gallery: Symlink photos according to their EXIF timestamps # (c) 2013 Martin Mares use strict; use warnings; use Image::EXIF; use Getopt::Long; if (@ARGV && $ARGV[0] eq '--help') { die <] or: gal date [] Options: -d, --destdir= Create symlinks in specified directory (default: current dir) -n, --dry-run Perform a trial run with no changes made -o, --offset= Adjust timestamps by a given offset ([:[:]]) --suffix= Add suffix to all names -s, --symbolic Create symbolic links (default: hardlinks) AMEN } my $destdir = '.'; my $dry_run = 0; my $offset = 0; my $suffix = ""; my $symbolic = 0; Getopt::Long::Configure('bundling'); GetOptions( 'destdir|d=s' => \$destdir, 'dry-run|n!' => \$dry_run, 'offset|o=s' => \$offset, 'suffix=s' => \$suffix, 'symbolic|s!' => \$symbolic, ) or die "Try gal date --help\n"; my @src = @ARGV; if (!@src) { while () { chomp; push @src, $_; } } if ($offset =~ m{^([+-])?(\d)+(:(\d+)(:(\d+))?)?$}) { $offset = $2 * 3600 + ($4 // 0) * 60 + ($6 // 0); if ($1 eq '-') { $offset = -$offset; } } else { die "Invalid offset: $offset\n"; } foreach my $f (@src) { my $e = new Image::EXIF($f); my $i = $e->get_all_info(); if ($e->error) { print STDERR "EXIF error on $f: ", $e->error, "\n"; next; } # print STDERR Dumper($i), "\n"; my $d = $i->{'image'}->{'Image Created'}; if (!defined $d) { print STDERR "No date for $f\n"; next; } my ($ty, $tm, $td, $tH, $tM, $tS) = ($d =~ m{^(\d{4}):(\d{2}):(\d{2}) (\d{2}):(\d{2}):(\d{2})$}) or die "EXIF date parse error: $d\n"; my $t = 3600*$tH + 60*$tM + $tS; $t = int($t + $offset); $tH = int($t / 3600); $tM = int(($t % 3600) / 60); $tS = $t % 60; my $retry = 0; my $dest = ""; do { $dest = sprintf("%s/%04d-%02d-%02d-%02d:%02d:%02d%s%s.jpg", $destdir, $ty, $tm, $td, $tH, $tM, $tS, $suffix, $retry ? sprintf("-%d", $retry) : ""); $retry++; } while (-f $dest); print "$f $dest\n"; unless ($dry_run) { if ($symbolic) { symlink $f, $dest or die "Cannot symlink $f to $dest: $!\n"; } else { link $f, $dest or die "Cannot hardlink $f to $dest: $!\n"; } } } STDOUT->autoflush(1);