aboutsummaryrefslogblamecommitdiff
path: root/cpphoto/cpphoto
blob: f5b862a1d9f79a826d727fbfa24bd1e5e7c6d343 (plain) (tree)



















































































































































































































































































































































                                                                                     
#!/usr/bin/perl
#

use warnings;
use strict;
use Cwd qw(abs_path getcwd);
use Digest::MD5 'md5_hex';
use File::Basename;
use File::Copy;
use File::Glob qw(bsd_glob);
use File::Path qw'mkpath';
use Getopt::Long;
use Image::ExifTool q(:Public);
use Pod::Usage;

my $help      = 0;
my $dryrun    = 0;
my $recursive = 0;
my $clobber   = 0;
my $man       = 0;
my $whose     = '';
GetOptions('dry-run|n'   => \$dryrun,
           'recursive|r' => \$recursive,
           'clobber|C'   => \$clobber,
           'help|h'      => \$help,
           'man'         => \$man) or pod2usage(2);
pod2usage(1) if $help;
pod2usage(-exitval => 0, -verbose => 2) if $man;

my $photo_dir = '/external/pictures';
die '/external not mounted?' unless -d $photo_dir;

my @m_abbr = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
my ($month, $year) = (localtime())[4..5];
$month += 1; $year += 1900;

my @idirs = @ARGV;
unless (@idirs) {
  print "Using .\n";
  push @idirs, getcwd();
}

my @files;
foreach my $dir (@idirs) {
  unless (-d $dir) {
    print "$dir is not a directory.\n";
    next;
  }
  push @files, getfiles(abs_path($dir));
}

my @missing;

foreach my $file (sort(@files)) {
  eval {
    copy_photo($file);
  };
  if ($@) {
    push @missing, "$file: $@";
  }
}

if (@missing) {
  print "Unable to copy:\n" . join("\n", map { "* $_" } sort(@missing)) . "\n"
}

exit 0;


sub getfiles {
  my $top = shift;
  my @dirs;
  my @files;
  opendir(my $dh, $top) or die "Failed to open $_: $!\n";
  while (readdir($dh)) {
    next if /^\.(\.)?/;
    my $e = "$top/$_";
    if (-d $e) {
      print "Adding $e\n";
      push @files, getfiles($e) if $recursive;
    } elsif (-f $e and $e =~ m/\.jpe?g$/i) {
      push @files, $e;
    }
  }
  closedir($dh);
  return @files;
}

sub copy_photo {
  my $p = $_[0];
  my ($Y, $M, $D, $h, $m, $s) = dtg($p);

  my $ext     =  lc($p =~ m/(\.\w+)$/ ? $1 : '');
  my $destdir = sprintf("%s/%04d/%02d_%s", $photo_dir, $Y, $M, $m_abbr[$M - 1]);
  my $meddir  = "$destdir/med";
  my $base    = sprintf("p%04d%02d%02d_%02d%02d%02d", $Y, $M, $D, $h, $m, $s);
  my $dest    = "$destdir/$base";

  if ($dryrun) {
    print "mkdir $destdir\n" unless -d $destdir;
    print "mkdir $meddir\n" unless -d $meddir;
  } else {
    mkpath $destdir unless -d $destdir;
    mkpath $meddir unless -d $meddir;
  }

  $dest = disambiguate($p, $dest, $ext) if -f "$dest$ext";

  my $outfile = "$dest$ext";
  my $medfile = "$meddir/${base}_med$ext";

  for (my $count = 0; $count < 100; $count++) {
    # Unlikely ever to need more than two extra digits
    last unless -f $medfile;
    $medfile = sprintf("$meddir/${base}_med_%02d$ext", $count++);
  }

  unless (-e $outfile) {
    print "$p => $outfile\n";
    copy($p, $outfile) unless $dryrun or -e $outfile;
    unless ($outfile =~ m/\.avi$/ or $dryrun) {
      system("/usr/bin/convert -resize '800x600' $outfile $medfile");
    }
  }
}

# Extract the date-time group from the EXIF data, returning it as a
# list.  Return a guess if there is no EXIF.
sub dtg {
  my $p = shift;
  foreach my $tag (qw|CreateDate FileCreateDate FileModifyDate|) {
    my $exif = ImageInfo($p, $tag);
    if (defined($exif->{$tag}) &&
        $exif->{$tag} =~ m/^(\d\d\d\d):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/) {
      return ($1, $2, $3, $4, $5, $6);  # Y,M,D,H,M,S
    }
  }
  return guess_dtg($p);
}

sub guess_dtg {
  my $p = shift;


  # # Most common filename format:
  # #   p[0-c]DDnnn.jpg  -- month (in hex), day, serial
  # # 
  # if ($p =~ m|/p([0-9a-c])(\d\d)([^.]+)\.?.*$|) {
  #   my ($M, $D, $serial) = (hex("0x$1"), $2, $3);
  #   my $
  # }


  # if ($p =~ m|/(p([0-9a-c])[^.]+)\.(.+)$|i) {
  #   ($basename, $m, $ext) = ($1, hex('0x' . $2), $3);
  #   $cp_year = ($m > $month) ? $year - 1 : $year;
  # } else {
  #   $p =~ m|/(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)-(\d\d)\.(.+)$|;
  #   ($cp_year, $ext, $m) = ($3 + 2000, $7, $1);
  #   $basename = "p$cp_year$m$2_$4$5$6";
  # }

  # XXX: If all else fails, keep a one-up, checking and incrementing.

  die "Can't guess DTG for $p";
}


sub disambiguate {
  my ($source, $dest, $ext) = @_;
  open(my $s, "<$source") or die "Can't read $source: $!";
  binmode($s);
  my $ctx = Digest::MD5->new;
  $ctx->addfile($s);
  my $smd = $ctx->hexdigest;
  close($s);
  my ($count, $extra) = (0, '');
  unless ($clobber) {
    while (open(my $f, "<$dest$extra$ext")) {
      my $ctx = Digest::MD5->new;
      binmode($f);
      $ctx->addfile($f);
      my $md = $ctx->hexdigest;
      close($f);
      print "S/F: ".basename($source).':'.basename("$dest$extra$ext").": $smd/$md\n";
      last if $md eq $smd;
      $extra = sprintf("_%02d", $count++);
    }
  }
  return "$dest$extra";
}




##############################################################################
##############################################################################
# -- original code --

sub copydir {
  print "copydir: $_[0]\n"; exit;
  my $dcim = 'DCIM';

  opendir(my $pd, $_[0]) or die "opendir $_[0]:$!";

  foreach my $subdir (readdir($pd)) {
    next if $subdir =~ m/^\.\.?$/;
    my $camera = "/mnt/camera/$dcim/$subdir";
    print "camera => $camera\n";

    foreach my $p (bsd_glob("$camera/*")) {
      my ($basename, $ext, $cp_year, $m);
      my @exif_dtg = exif_dtg($p);

      if (@exif_dtg) {
        my ($y, $mn, $d, $h, $M, $s) = @exif_dtg;
        my ($_b, $_e) = ('', '');
        if ($p =~ m|/p...([^.]+)\.(.+)$|) {
          ($_b, $_e) = ($1, $2);
        } else {
          $p =~ m|/([^/]+)\.([^./]+)$|;
          ($_b, $_e) = ($1, $2);
        }

        ($cp_year, $basename, $ext, $m) = ($y, "p$y$mn${d}_$h$M${s}_$_b", $_e, $mn);
      } else {
        if ($p =~ m|/(p([0-9a-c])[^.]+)\.(.+)$|) {
          ($basename, $m, $ext) = ($1, hex('0x' . $2), $3);
          $cp_year = ($m > $month) ? $year - 1 : $year;
        } else {
          $p =~ m|/(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)-(\d\d)\.(.+)$|;
          ($cp_year, $ext, $m) = ($3 + 2000, $7, $1);
          $basename = "p$cp_year$m$2_$4$5$6";
        }
      }

      my $outdir = "$photo_dir/$cp_year/" . sprintf("%02d_%s", $m, $m_abbr[$m - 1]);
      my $meddir = "$outdir/med";

      if ($dryrun) {
        print "mkdir $outdir\n" unless -d $outdir;
        print "mkdir $meddir\n" unless -d $meddir;
      } else {
        mkpath $outdir unless -d $outdir;
        mkpath $meddir unless -d $meddir;
      }

      my $outfile = "$outdir/${basename}_$whose.$ext";
      my $old_outfile = "$outdir/${basename}.$ext";

      if (-e $outfile || -e $old_outfile) {
        print "Skipping $p\n";
        next;
      }

      my $medfile = "$meddir/${basename}_${whose}_med.$ext";

      print "$p => $outfile\n";
      copy($p, $outfile) unless $dryrun;

      unless ($outfile =~ m/\.avi$/ or $dryrun) {
        system("/usr/bin/convert -resize '800x600' $outfile $medfile");
      }
    }
  }

  closedir($pd);
}


sub find_camera_dirs {
my $whose = lc substr($ARGV[0], 0, 1);
#my $camera_o = '/mnt/camera/dcim/100olymp';           # Olympus
#my $camera_p = '/mnt/camera/dcim/100_pana';           # Panasonic
#my $camera_p = '/mnt/camera/dcim/101_pana';           # Panasonic
#my $camera_lg = '/mnt/lg840g/Pictures/Camera Album';  # LG 840G


#my $camera = undef;

opendir(my $c, '/mnt/camera') or die "opendir camera: $!";
my ($dcim) = grep { /^dcim$/i } readdir($c);
closedir($c);

die "Camera not mounted" unless defined $dcim;

#$camera = $camera_o if -d $camera_o;
#$camera = $camera_p if -d $camera_p;
#$camera = $camera_lg if -d $camera_lg;
#die "Camera not mounted\n" unless defined $camera and -d $camera;

  return ();
}

# -- original code --
##############################################################################
##############################################################################


__END__

=head1 CPPHOTO

cpphoto -- Copy photos into the correct directories on /external

=head1 SYNOPSIS

cpphoto [options] [directory ...]

 Options:
   --help          help message
   --man           documentation
   --dry-run,-n    don't copy, just print what would be done
   --recursive,-r  copy photos from entire tree

=head1 OPTIONS

=over 8

=item B<--help>



=item B<--man>



=item B<--dry-run>



=item B<--recursive>



=back

=head1 DESCRIPTION

=cut