Snapshot/upload utility

Earlier this week, I wrote about my search to improve and streamline my way of taking screenshots for the blog. I mentioned a program I was testing, SnapNDrag, which improves on the Mac-standard screenshot by allowing you to name the snapshot file, resize it, and drag it directly to the place you want it saved. My plan was to create a folder with a folder action that would automatically upload any files dragged into it. I found an Automator action that does this, but ultimately decided to abandon this approach.

The problem was with SnapNDrag’s resizing feature. It limits you to just three scaling factors (75%, 50% and 25%) and doesn’t let you resize the snapshot to a particular width or height. These limitations led me to create my own snapshot utility that uses some Mac-specific command-line tools, a Perl script, and Quicksilver.

My snapshot utility works like this:

My work is minimal and I get four things:

  1. a snapshot,
  2. a resizing of the snapshot,
  3. a copy of the snapshot on my blog’s server, and
  4. the URL ready to paste into my post.

Here’s the code that does it.

#!/usr/bin/perl

use Net::FTP;
use Image::Info qw(image_info dim);
use Getopt::Std;

# Parameters
$host = "myblogsite.com";
$baseurl = "http://www.myblogsite.com/images";
$user = "myusername";
$pass = "mysekret";
$ftpdir = "httpdocs/images";
$localdir = "$ENV{HOME}/Desktop";

$usage = <<USAGE;
snapftp:  take a snapshot of something onscreen and ftp it to
          a remote site.

usage:
  snapftp [options] filename [size]

options:
  -h:   the size parameter is for height, not width
  -l:   only make the local copy, don't ftp it
  -s:   take a full screenshot, not just a window or selection
  -d n: delay the snapshot by n seconds

When snapftp is called without the -s option, the mouse pointer
turns into a camera. Move the camera over the window of interest
and click the mouse button to take a snapshot. Alternatively,
press the spacebar to change the pointer into crosshairs and
drag the mouse to select a rectangle to be snapped. The snapshot
will be saved with the given filename on the Desktop and (unless
the -l option is used) will be ftp'd to the images directory of
the all-this blog. The URL of the newly-uploaded file will be in
the clipboard.

The optional size parameter can take two forms. If it is a
number, the snapshot will be resized to make the width equal to
that number. If it is a number followed by a percent sign (%),
the snapshot will be scaled to that percentage of its onscreen
size.

If the -h option is given, the size parameter will refer to
height rather than width.

If the -s option is given, the snapshot will be of the entire
screen, but everything else will be as described above.

The -d option is good for taking snapshots of menus.
USAGE

# Handle the switches.
getopts('hlsd:', \%opt);
$setheight = $opt{h};
$localonly = $opt{l};
$scopt = $opt{s} ? "" : "-iW";    # options for screencapture
$delay = $opt{d} or 0;

# Print usage if no file is given.
die $usage unless (@ARGV);

# Go to the localdir.
chdir($localdir) or die "Couldn't change to $localdir!";

# Set the filenames and 
$fn = shift;
$url = "$baseurl/$fn";

# Capture a portion of the screen and save it to the file.
if ($delay) { sleep $delay; }
`screencapture $scopt $fn` and die "Couldn't take snapshot!";

# Resize the file if asked to
if ($size = shift) {
  # Get the current height and width.
  $info = image_info($fn);
  ($width, $height) = dim($info);

  # Set the new size.
  if ($size =~ /(\d+)%$/) {
    $size = int($width*$1/100);         # scale by percentage
    `sips --resampleWidth $size $fn`;   # resize the image
  }
  else {
    if ($setheight) {
      `sips --resampleHeight $size $fn`;  # resize the image
    }
    else {
      `sips --resampleWidth $size $fn`;   # resize the image
    }
  }
}

unless ($localonly) {
  # Now send the file.
  $ftp = Net::FTP->new($host,Timeout=>240) 
          or die "Couldn't contact $host!";
  $ftp->login($user, $pass);
  $ftp->cwd($ftpdir);
  $ftp->binary;
  $ftp->put($fn);
  $ftp->quit;

  # Put the URL of the remote file on the clipboard.
  open CLIP, "|pbcopy";
  print CLIP $url;
}

I’ve saved this program with the name “snapftp” in my $PATH and made a hard link to it called “sf.command” in a folder that Quicksilver catalogs. The “command” extension makes it a TerminalShellScript, a document that opens the Terminal and runs if double-clicked. Quicksilver knows that TerminalShellScripts are to be run, which is why that action comes up automatically in the second pane. I use “snapftp” when running from the Terminal and debugging; the shortened “sf” needs fewer keystrokes to call from Quicksilver.

Let’s talk about the program itself.

The three Perl modules are, I’m pretty sure, standard on every Mac. The only one I’m hesitant about is Image::Info, which I might have installed myself and just can’t remember that I did it. If you try this out and get an error message that starts out

Can't locate Image/Info.pm in @INC

let me know in the comments and I’ll give instructions on how to install it.

The next section of the program has the parameters for saving the snapshot locally and remotely. Names have been changed to protect the innocent.

The usage message is a reminder to me of how to use the program. It comes up when I run the program with no filename.

The three command-line switches, -h, -l, and -s, are there to give me some flexibility, but the defaults are set so that I’ll rarely use them. Their purpose is described in the usage message.

After dealing with the filename, the screencapture utility is called. This is a Mac-specific command-line program that works like our old friends Command-Shift-3 and Command-Shift-4. Whether it captures the entire screen, an individual window, or a rectangular selection depends on how you invoke it. I’ve had trouble getting screencapture to save anywhere other than the current folder, which is why chdir is used a few lines earlier.

If the snapshot is to be resized, it’s done through sips, a Core Image/Image Events program that can rotate, crop, pad, convert, and (most important for this exercise) resize images. Because sips can only resize to absolute pixel sizes, and I wanted to also be able to scale by percentage, there’s some messing around to convert percent to pixels. This is the part of the code that relies on Image::Info.

The code that connects to the FTP server and uploads the file is pretty self-explanatory. The Net::FTP module is designed to look a lot like a command-line FTP session.

The final step is putting the URL of the remote file on the clipboard. This is done with the help of another Mac-specific command-line utility, pbcopy, which takes standard input and puts it on the clipboard. The program uses Perl’s ability to print to a pipe to feed the URL to pbcopy.

So that’s that. Not what I was expecting when I set out to find a better way to do snapshots, but I’m really happy with the efficiency and flexibility of the result. It does seem odd, though, to have no snapshots in this post. So here goes

Update
I added a -d option to delay the snapshot by a given number of seconds, which is needed to capture menus in the pulled-down state. To use this—or any of the options when invoking the utility from Quicksilver—type the option in the third pane, before the filename.

Further update
A complete rewrite that doesn’t rely on Quicksilver and has a GUI.

Tags: