Screen captures with file upload

Things have come full circle. Several years ago I wrote a little script called snapftp, which combined screen capturing with file uploading via FTP to streamline the process of including screenshots in my posts here. As I mentioned last night, this later evolved into snapflickr, a similar script for screen capturing and uploading to Flickr. Now that I’ve returned to hosting images on my web server, I need to return to something similar to snapftp, but I want this one to be smarter.

First, I won’t be using FTP anymore. I never had any security issues with FTP, but it just makes more sense to use SCP. Second, I want to eliminate (or at least greatly reduce) the chance of duplicate file names. And finally, I’m going to implement this as a Keyboard Maestro macro, which gives me a bit more flexibility in divvying up the work between Python and shell scripts.

Using the new macro, called SnapSCP, is only slightly more complicated than using the SnapClip macro. I invoke it through the ⌃⇧4 keyboard combination, choose a window or rectangular area to capture, give the image file a name, and decide whether it should have a border. The file is then saved locally in my ~/Pictures/Screenshots folder and is uploaded to an images directory on the blog server. An HTML <img> tag is put on the clipboard for pasting into a post.1

Here’s the GUI presented for setting the name and choosing whether to add a border:

SnapSCP

The Name field is used to populate the alt and title attributes of the img, and it’s also part of the file name. But it isn’t the complete file name, because that would make it too easy to create file name conflicts. So the file name is whatever’s written in the Name field, prefixed by the date in yyyymmdd format. Thus, the file name of the image above is 20150125-SnapSCP.png. Although I could never enter unique names over the course of a year, month, or even a week, I figure I can remember to give unique names during a single day.

Here’s the Keyboard Maestro macro:

Keyboard Maestro SnapSCP

The first Python script is

python:
 1:  #!/usr/bin/python
 2:  
 3:  import Pashua
 4:  import tempfile
 5:  import Image
 6:  import sys, os
 7:  import subprocess
 8:  from datetime import date
 9:  
10:  # Local parameters
11:  dstring = date.today().strftime('%Y%m%d')
12:  type = "png"
13:  localdir = os.environ['HOME'] + "/Pictures/Screenshots"
14:  tf, tfname = tempfile.mkstemp(suffix='.'+type, dir=localdir)
15:  bgcolor = (61, 101, 156)
16:  bigshadow = (25, 5, 25, 35)
17:  smallshadow = (0, 0, 0, 0)
18:  border = 16
19:  
20:  # Dialog box configuration
21:  conf = '''
22:  # Window properties
23:  *.title = Snapshot
24:  
25:  # File name text field properties
26:  fn.type = textfield
27:  fn.default = Snapshot
28:  fn.width = 264
29:  fn.x = 54
30:  fn.y = 40
31:  fnl.type = text
32:  fnl.default = Name:
33:  fnl.x = 0
34:  fnl.y = 42
35:  
36:  # Border checkbox properties
37:  bd.type = checkbox
38:  bd.label = Background border
39:  bd.x = 10
40:  bd.y = 5
41:  
42:  # Default button
43:  db.type = defaultbutton
44:  db.label = Save
45:  
46:  # Cancel button
47:  cb.type = cancelbutton
48:  '''
49:  
50:  # Capture a portion of the screen and save it to a temporary file.
51:  subprocess.call(["screencapture", "-ioW", "-t", type, tfname])
52:  
53:  # Exit if the user canceled the screencapture.
54:  if not os.access(tfname, os.F_OK):
55:    os.remove(tfname)
56:    sys.exit()
57:  
58:  # Open the dialog box and get the input.
59:  dialog = Pashua.run(conf)
60:  if dialog['cb'] == '1':
61:    os.remove(tfname)
62:    sys.exit()
63:  
64:  # Add a desktop background border if asked for.
65:  snap = Image.open(tfname)
66:  if dialog['bd'] == '1':
67:    # Make a solid-colored background bigger than the screenshot.
68:    snapsize = tuple([ x + 2*border for x in snap.size ])
69:    bg = Image.new('RGB', snapsize, bgcolor)
70:    bg.paste(snap, (border, border))
71:    bg.save(tfname)
72:  
73:  # Rename the temporary file using today's date (yyyymmdd) and the 
74:  # name provided by the user.
75:  name = dialog['fn'].strip()
76:  fname =  '{localdir}/{dstring}-{name}.{type}'.format(**locals())
77:  os.rename(tfname, fname)
78:  
79:  print fname

Most of this follows the same logic as the script in the SnapClip macro, so I won’t repeat that explanation here. The main differences are:

As you can see in the KM screenshot, the output of this script, if any, is saved to a KM variable called fname. If this variable exists and is not empty, the image file is then uploaded to the server through this one-line shell script:

scp -P 22 "$KMVAR_fname" user@leancrew.com:path/to/all-this/images2015/

The -P option to scp should be set to the server’s SSH port. As you’ve probably guessed, my server’s SSH port isn’t actually set to 22.

The first argument is the local file, which we get from the KMVAR_fname environment variable. For each variable defined in a macro, Keyboard Maestro sets an environment variable of the form KMVAR_variable that can be accessed from any script. The quotes around the variable are there to accommodate characters (like spaces) that have special meaning to the shell.

The second argument is the remote directory to which the file is uploaded. Because I have SSH keys set up, I don’t need to provide a password—scp looks in the ~/.ssh/ directories on both the server and my local machine for the information necessary to log in securely.

The final Python script generates the img tag and puts it on the clipboard.

python:
 1:  #!/usr/bin/python
 2:  
 3:  from datetime import date
 4:  import os
 5:  import os.path
 6:  import urllib
 7:  
 8:  # File names: with and without date prefix and extension.
 9:  fname = os.path.basename(os.environ['KMVAR_fname'])
10:  name = os.path.splitext(fname)[0].split('-', 1)[-1]
11:  fname = urllib.quote(fname)
12:  
13:  print '<img class="ss" src="http://leancrew.com/all-this/images2015/{fname}" alt="{name}" title="{name}" />'.format(**locals())

Like the shell script, it accesses the file name through the KMVAR_fname environment variable. It extracts the parts necessary for the src, alt, and title attributes, and combines them into a full image tag. Line 11 URL-encodes the file name, which isn’t actually necessary but seems like good practice. I’ve noticed, for example, that Transmit encodes when you right-click on an item and choose the Copy URL command.

Because I use LaunchBar’s Clipboard History feature, I don’t have to paste the image tag immediately after running SnapSCP. I can take several screenshots in a row, in any order, and then paste the links into a post later. This is one of the many advantages of writing your own scripts—you can create commands that not only fit in with how you think, but also fit in with the rest of the tools you use.


  1. Although I write in Markdown, I don’t use the Markdown syntax for images. I’ve never found it especially intuitive, and it isn’t convenient for assigning classes to the image. But one of the great things about Markdown is that it allows you to drop down into HTML when you want to.