|
|
- #!/usr/bin/env perl
-
- =pod
-
- =head1 NAME
-
- digikam-extract - find files by tag in a DigiKam 4 database
-
- =head1 SYNOPSIS
-
- # List photos tagged "mom-album-2020":
- digikam-extract --tag mom-album-2020
-
- # Copy tagged items to current dir, renamed by date:
- digikam-extract --tag mom-album-2020 --copy
-
- # Same, but falling back to file mtime for anything with a year of 1980:
- digikam-extract --tag mom-album-2020 --ctime-fallback 1980 --copy
-
- =head1 DESCRIPTION
-
- A silly hack for tagging photos in DigiKam and then copying the tagged stuff
- into a separate directory and renaming the files according to their creation
- date. If I need this workflow again, I may generalize this, but it's probably
- worth noting that the database schema is effectively an undocumented API and
- could easily get yanked out from under this code at any time.
-
- =head1 OPTIONS
-
- =over 4
-
- =item B<--tag> [tagname]
-
- Specify a tag to search for. Required.
-
- =item B<--copy> [path]
-
- Copy files to destination path. Defaults to working directory.
-
- =item B<--dest> [path]
-
- Specify a destination path.
-
- =item B<--ctime-fallback> [year]
-
- Specify a year which probably means that the camera's date wasn't set, and fall
- back to whatever DigiKam's db thinks the modification time is instead.
-
- =item B<--dbfile> [path]
-
- Specify location of DigiKam4 database file.
-
- =item B<--date-format> "format"
-
- Specify date format for target filenames. See strftime(3) for date format.
-
- Defaults to C<%Y-%m-%d-%H%M%S>.
-
- =item B<--root> [path]
-
- Specify a gallery path. Must correspond to a root album folder defined in
- Collections Settings in DigiKam. No trailing slash.
-
- =item B<--help>
-
- Print help and exit.
-
- =back
-
- =head1 AUTHOR
-
- Brennen Bearnes
-
- =cut
-
- use warnings;
- use strict;
-
- use Cwd;
- use DBI;
- use Data::Dumper;
- use File::Basename;
- use File::Spec;
- use Getopt::Long;
- use Pod::Usage;
- use SQL::Abstract;
- use Time::Piece;
- use Time::Seconds;
- use File::Copy;
- use File::HomeDir;
-
- my $HOME = File::HomeDir->my_home;
-
- # Handle options, including help generated from the POD above.
- # TODO: This should use digikam config to extract current path to db
- # and roots should be extracted from the database, most likely.
- my $copy = 0;
- my $tag;
- my $dest = getcwd();
- my $ctime_fallback;
- my $date_format = '%Y-%m-%d-%H%M%S';
- my $dbfile = $HOME . "/digikam-db/digikam4.db";
- my $root = $HOME . '/workspace/photos';
-
- GetOptions(
- 'copy' => \$copy,
- 'dest=s' => \$dest,
- 'tag=s' => \$tag,
- 'root=s' => \$root,
- 'dbfile=s' => \$dbfile,
- 'ctime-fallback=s' => \$ctime_fallback,
- 'date-format' => \$date_format,
- 'help' => sub { pod2usage(-verbose => 2) },
- ) or pod2usage(-verbose => 1, );
-
- # Handle a couple of likely failure modes:
- unless (defined $tag) {
- print "Please specify a tag name with --tag\n";
- exit(2);
- }
-
- foreach my $path ($dest, $root) {
- die("$path is not a directory")
- unless -d $path;
- }
-
- my $query = q{
- SELECT i.id, ii.creationDate, i.modificationDate, ar.specificPath,
- a.relativePath, i.name AS image_name, t.name AS tag_name
- FROM Tags t JOIN ImageTags it ON it.tagid = t.id
- JOIN Images i ON it.imageid = i.id
- JOIN Albums a ON i.album = a.id
- JOIN AlbumRoots ar ON ar.id = a.albumRoot
- JOIN ImageInformation ii ON ii.imageid = i.id
- WHERE
- t.name = ? AND ar.specificPath = ?
- ;
- };
-
- my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile", "", "");
- my @bind = ($tag, $root);
-
- my $sth = $dbh->prepare($query);
- $sth->execute(@bind);
-
- unless ($copy) {
- print "Dry run. Would copy:\n\n"
- }
-
- my %sources;
- while (my $data = $sth->fetchrow_hashref()) {
- my $cdate = Time::Piece->strptime($data->{creationDate}, "%Y-%m-%dT%T");
- my $mdate = Time::Piece->strptime($data->{modificationDate}, "%Y-%m-%dT%T");
-
- my $date = $cdate->strftime($date_format);
- if (defined $ctime_fallback) {
- # If we got a year to fall back to the mtime for, then do that -
- # so like if a bunch of stuff has 1980 because the clock wasn't
- # set right on the camera...
- if ($ctime_fallback eq $cdate->strftime('%Y')) {
- $date = $mdate->strftime($date_format);
- }
- }
-
- # print "$date\t";
- my $source = $root . $data->{'relativePath'} . "/" . $data->{image_name};
- my $dest = "$dest/$date-$data->{image_name}";
-
- if (defined $sources{$dest}) {
- die("Collision: $source and $sources{$dest} would collide, making no copies");
- } else {
- $sources{$dest} = $source;
- }
- }
-
- foreach my $dest (keys %sources) {
- my $source = $sources{$dest};
- print "$source -> $dest\n";
- next unless $copy;
-
- if (-e $dest) {
- print "Destination already exists: $dest\n";
- } else {
- copy($source, $dest);
- }
- }
-
- unless ($copy) {
- print "\nAdd --copy to invocation to copy to $dest\n";
- }
|