Easy JPEG Transcoding
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:
Here is that same “diff” with increased brightness and contrast:
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)