On working around mechanical failures with software

Running a business involves reams of paperwork. Reams of paperwork eventually means miscategorized, misplaced, and mishandled documents. The (partial) solution: a scanner, of course. I say partial because it’s still easy to have all your digital files in complete disarray, but at least archiving and searching is a fair bit easier.

Of course really good scanners are not exactly cheap. We bought a Scanjet 5590, which, for the price, is pretty decent. Most importantly it has a document feeder, crucial if you’re scanning long, or many, documents.

Now I encourage the use of both sides of paper, but it is a mechanical nightmare to scan double-sided paper correctly. Sadly the 5590, while officially supporting this feature, is not of sufficient quality to make this really useful–I’d say it jams about 50% of the time.

I’m used to using software to work around hardware bugs. Back in my last year of university, we discovered that the DSP we were using for our big project had a bug in its serial port: after a few kB, the serial port would lock up and refuse to handle any more data until it was reset. After asking the manufacturer about this problem, they verified that it was indeed a bug and would be fixed in the next revision–not too useful when your deadline is in a couple months. We ended up writing a simple lock-step protocol, with the firmware resetting the serial port if it hadn’t received expected data after a short period of time.

But back to scanners. I wasn’t prepared to shell out the many thousands of dollars required for professional scanning/photocopying/window-cleaning equipment. Luckily, there is a wonderful little utility called pdftk, the PDF toolkit. It’s available for Linux, Windows (at least via Cygwin), and probably a bunch of other systems. It can split PDFs into separate files for each page as well as concatenate PDFs, perfect for what I was doing. (It can also do a whole pile of other things, less important for me at the moment.) So the solution is to scan one side of all the pages through the feeder into one file, scan all the other sides into a separate file, and use a series of pdftk commands to split and then merge the files into a single PDF with all the pages in the correct order.

I wouldn’t be much of a programmer if I was content to repeat those commands every time I needed to scan a double-sided document, so I spent a little bit of time and wrote a short Python program to do it all for me. I present it below the cut. It should work in Linux as well as in Windows.

#!/usr/bin/env python

import os, os.path, subprocess, sys, tempfile

def usage(prog):
  print '%s <right-side PDF> <left-side PDF> <output PDF>' \
        % prog

def cleanup(tmpdir):
  os.system('rm -rf %s' % tmpdir)

if len(sys.argv) < 4:
  usage(sys.argv[0])
  sys.exit(1)

rightfile = sys.argv[1]
leftfile = sys.argv[2]
outputfile = sys.argv[3]
tmpdir = tempfile.mkdtemp()
rightdir = os.path.join(tmpdir, 'right')
leftdir = os.path.join(tmpdir, 'left')
os.mkdir(rightdir)
os.mkdir(leftdir)

os.system('cp %s %s' % (rightfile, rightdir))
os.system('cp %s %s' % (leftfile, leftdir))

for i in ((rightfile, rightdir), (leftfile, leftdir)):
  p = subprocess.Popen(['pdftk', i[0], 'burst'], cwd=i[1])
  rc = p.wait()
  if rc != 0:
    print 'Failed to burst %s.' % i[0]
    cleanup(tmpdir)
    sys.exit(2)

rfcount = 0
for f in os.listdir(rightdir):
  if f[:3] == 'pg_': rfcount += 1
lfcount = 0
for f in os.listdir(leftdir):
  if f[:3] == 'pg_': lfcount += 1

if not (0 <= (rfcount - lfcount) <= 1):
  print 'The number of right-side pages (%d) ' % rfcount + \
        'is not equal to or one greater than the'
  print 'number of left-side pages (%d).' % lfcount
  cleanup(tmpdir)
  sys.exit(2)

command = ['pdftk']
for i in range(1, rfcount+1):
  rpage = os.path.join(rightdir, 'pg_%04d.pdf' % i)
  lpage = os.path.join(leftdir, 'pg_%04d.pdf' % i)
  command.append(rpage)
  if os.path.exists(lpage):
    command.append(lpage)

command.extend(['cat', 'output', outputfile])

p = subprocess.Popen(command)
rc = p.wait()
if rc != 0:
  print 'Failed to concatenate pages.'
  cleanup(tmpdir)
  sys.exit(2)

cleanup(tmpdir)