Picture-of-the-day helper script

As you may have noticed, I'm currently trying to shoot one photo each day and post it here and on twitter. Until now it was fun, but copying and resizing the image was getting boring. And boring things are to be automated!

The Task

Each day, I have to do the following steps:

  • Take some photos and select one
  • Rename the file from the ugly name my camera generates to YYYY-MM-DD.jpg
  • Copy that photo to some folder, so I can later still access the original pictures
  • Resize the photo to a smaller version (800 px width), which is enough for my website (especially as I still keep the originals)
  • Move the image file into the right place in my local blog checkout
  • Create a text file, in the format needed by my blogging engine, Blio
  • Open a text editor so I can enter the title and some witty comments
  • Add & commit the new files, and push the repo
  • Everything else will happen on my server and will be explained in another post...

The Code

#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Imager;
use Path::Class;
use File::Copy;
use DateTime;
use File::HomeDir;

my $raw = file( $ARGV[0] );
die "No such file $ARGV[0]" unless -e $raw;
my $not_scheduled = 1 if $ARGV[1];

my $home     = dir( File::HomeDir->my_home );
my $blio_src = $home->subdir(qw(privat domm.plix.at src potd));
my $now      = DateTime->now;
$now->add( days => 1 ) unless $not_scheduled;
my $basename   = $now->ymd('-');
my $target_img = $blio_src->file( $basename . '.jpg' );
my $target_txt = $blio_src->file( $basename . '.txt' );
if ( -e $target_img ) {
    say "target $basename.jpg already exists, aborting";
    exit;
}

my $image = Imager->new;
$image->read( file => $raw );
my $scaled = $image->scale( xpixels => 800 );
$scaled->write( file => $target_img );

move( $raw,
    $home->subdir(qw(media fotos 2015 potd))->file( $basename . '.jpg' ) );

my $publish_date = $basename . 'T10:00:00';
$target_txt->spew(
    <<EOBLIO
title: 
date: $publish_date
converter: textile
template: potd.tt

EOBLIO
);
system("vim $target_txt");

chdir( $target_img->parent );
system( join( ' ', 'git add ', $target_img, $target_txt ) );
system("git commit -m 'potd $basename'");
system('git push');

This is a rather simple and straight-forward, old-school Perl solution to my problem.

I declare a bunch of modules I'm going to use:

#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Imager;
use Path::Class;
use File::Copy;
use DateTime;
use File::HomeDir;

I do some very basic command line parsing:

my $raw = file( $ARGV[0] );
die "No such file $ARGV[0]" unless -e $raw;
my $not_scheduled = 1 if $ARGV[1];

I could have used Getopt::Long or similar, but it's easy enough to just pop off the filename of the selected image from ARGV. $not_scheduled is a flag I can set if I do not want the blog post to be scheduled for the next day, but published immediately.

Now I define a bunch of variables I'm going to need:

my $home     = dir( File::HomeDir->my_home );
my $blio_src = $home->subdir(qw(privat domm.plix.at src potd));
my $now      = DateTime->now;
$now->add( days => 1 ) unless $not_scheduled;
my $basename   = $now->ymd('-');
my $target_img = $blio_src->file( $basename . '.jpg' );
my $target_txt = $blio_src->file( $basename . '.txt' );
if ( -e $target_img ) {
    say "target $basename.jpg already exists, aborting";
    exit;
}

File::HomeDir is very handy to figure out my home-dir. (I could have hardcoded it to /home/domm/). $blio_src defined the location where I want the image to be copied to. I use DateTime to figure out the current date and add a day unless I don't want to schedule the post. (Yes, this can result in a "bug" if I run the script after midnight and forget to add the not-scheduled-flag). I then use the very nice ymd method on the DateTime object to get something like 2015-01-14. This $basename is then used to generate the name of the image and text file. If the image file already exists, I abort, because I don't want to overwrite my old images. (ok, they are in git, but still...)

The next step is to resize the image to 800 pixels, which I do using Imager.

my $image = Imager->new;
$image->read( file => $raw );
my $scaled = $image->scale( xpixels => 800 );
$scaled->write( file => $target_img );

Instead of writing the image somewhere and then copy it to the right location (as listed in my initial spec), I just tell Imager to write the resized image to the final location. (I'm afraid this is the smartest part of this script...)

The next line moves the original image to my backup location

move( $raw,
    $home->subdir(qw(media fotos 2015 potd))->file( $basename . '.jpg' ) );

Now I create my blog post (which is a plain text file using some header-like fields):

my $publish_date = $basename . 'T10:00:00';
$target_txt->spew(
    <<EOBLIO
title: 
date: $publish_date
converter: textile
template: potd.tt

EOBLIO
);
system("vim $target_txt");

After preparing the data (no title, because I'll enter the title and some comments by hand; date set to the timestamp, at 10:00), I use spew (of Path::Class) to write the file. And then I just call vim, which opens an editor, so I can enter the title etc.

The last step is to commit the new files and push them:

chdir( $target_img->parent );
system( join( ' ', 'git add ', $target_img, $target_txt ) );
system("git commit -m 'potd $basename'");
system('git push');

I could have probably use some git wrapper, but just calling system is good enough for this simple set of commands.

And we're done!

Time saved

It took me a bit less than hour to write that script. The manual steps took me ~3 minutes. This year has 350 days left, so I'm going to save 350 * 3 - 60 = 990 minutes. Which means I can watch the upcoming episodes of The Walking Dead and Brooklyn Nine-Nine for "free", and still have some time left. Yay!

Next Steps

  • On my server, I've a cronjob running that pulls the repo from time to time and republished my website. This could probably be made a bit smarter
  • I also want to automatically post the new picture to twitter. I plan to use (or re-use) vti's publishr, which he handily announced yesterday. But currently I cannot create a twitter app (which I need to get access-tokens etc), because twitter does not recognize any of the mobile phone numbers I have available (all of which are anonymous prepaid cards, so maybe that's the problem?), but requires your account to be linked to a mobile phone to set up apps. Fckrs!

You can expect more blog posts as soon as I've solved this issues.

And for those of you who managed to read this far, here's a picture of my code!