2011-10-07 : Amundsen-Scott South Pole Station

The Independent JPEG Group is responsible for a C-based JPEG library that is used by lots of software. Their library distribution also includes the very useful binary executables “cjpeg” and “djpeg”. When combined, they can be used to transcode JPEGs. I’ve used these programs gainfully to transcode JPEGs to 14% of their original size with no visible change in picture quality.

To prove the point, I’m including the difference between the original seven megabyte version and the transcoded version. A completely black image means there are zero differences. In this one you might be able to make out faint shades of color:

diff

Here is that same “diff” with increased brightness and contrast:

diff amplified

So I found myself transcoding JPEGs pretty frequently to optimize transmission of images between offices. It was a little bit of a pain to do this in bulk, so I wrote a quick Python wrapper for these tools called “shrinkjpg” that makes it exceedingly easy to transcode a JPEG in one short command. E.G.: shrinkjpg biggie.jpg After running that command, you’ll see a smaller file called “biggie-opt.jpg” in the current working directory. If you want to work in-place, shrinkjpg --overwrite biggie.jpg will re-write the file in place. You can also set the quality level.

#!/usr/bin/python

import subprocess
from optparse import OptionParser
import tempfile
import os

version = '1.0'

op = OptionParser(version="%%prog v%s" % (version))
op.add_option("-q", "--quality",
              dest="quality", action="store", type="int", default="65",
              help="Output quality rating on 1-100 scale. Defaults to 65")
op.add_option("-o", "--overwrite",
              dest="overwrite", action="store_true", default=False,
              help="Whether or not to overwrite the file in-place")
(options, args) = op.parse_args()

for thisFilePath in args:
    if os.path.isfile(thisFilePath):
        # Figure out a filename for the new file
        (inputFilePath, inputFileName) = os.path.split(thisFilePath)
        (inputFileBaseName, inputFileExt) = os.path.splitext(inputFileName)
        if options.overwrite:
            outputFilePath = thisFilePath
        else:
            counter = 0
            while True:
                if counter > 0:
                    counterSuffix = "-opt-%s" % (counter)
                else:
                    counterSuffix = ""

                outputFilePath = os.path.join(inputFilePath, "%s-opt%s%s" % (
                    inputFileBaseName, counterSuffix, inputFileExt))

                if os.path.exists(outputFilePath):
                    counter += 1
                else:
                    break

        # Decode the file
        (tempFH, tempPath) = tempfile.mkstemp(suffix='.bmp')
        decoder = subprocess.Popen(
            ['djpeg', '-bmp', '-dct', 'float', thisFilePath], stdout=tempFH)
        decoder.wait()
        os.close(tempFH)

        # Encode the file
        if decoder.returncode == 0:
            print "Encoding %s" % (thisFilePath)
            encoder = subprocess.Popen(
                ['cjpeg', '-quality', str(options.quality), '-optimize',
                 '-progressive', '-outfile', outputFilePath, tempPath])
            encoder.wait()
        else:
            print "Decoder failed (%i), cannot continue" % (decoder.returncode)

        # Cleanup
        os.unlink(tempPath)