From 4b40325086a11d90342fba07b0a045cd8b229630 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 28 Feb 2014 22:57:40 -0500 Subject: [PATCH 1/7] Start of converting the module to a proper package. --- setup.py | 36 ++++++++++ img2pdf.py => src/img2pdf.py | 127 ++++++++++++++++++++++------------- jp2.py => src/jp2.py | 0 3 files changed, 117 insertions(+), 46 deletions(-) create mode 100644 setup.py rename img2pdf.py => src/img2pdf.py (72%) rename jp2.py => src/jp2.py (100%) diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..0b3e989 --- /dev/null +++ b/setup.py @@ -0,0 +1,36 @@ +from setuptools import setup + +setup ( + name='img2pdf', + version='1.0.0.dev0', + author = "Johannes 'josch' Schauer", + description = "Convert images to PDF via direct JPEG inclusion.", + long_description = open('README.md').read(), + license = "GPL", + keywords = "jpeg pdf converter", + classifiers = [ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: Implementation :: CPython', + 'License :: OSI Approved :: General Public License', + 'Programming Language :: Python', + 'Natural Language :: English', + 'Operating System :: OS Independent'], + url = 'http://pypi.python.org/pypi/img2pdf', + package_dir={"": "src"}, + py_modules=['img2pdf', 'jp2'], + include_package_data = True, + test_suite = 'tests.test_suite', + zip_safe = True, + install_requires=( + 'Pillow', + ), + entry_points=''' + [console_scripts] + img2pdf = img2pdf:main + ''', + ) diff --git a/img2pdf.py b/src/img2pdf.py similarity index 72% rename from img2pdf.py rename to src/img2pdf.py index b763668..6beb897 100755 --- a/img2pdf.py +++ b/src/img2pdf.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python -# # Copyright (C) 2012-2013 Johannes 'josch' Schauer # # This program is free software: you can redistribute it and/or modify @@ -15,17 +13,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -import Image import sys import zlib import argparse import struct +from Pillow import Image from datetime import datetime from jp2 import parsejp2 def parse(cont, indent=1): if type(cont) is dict: - return "<<\n"+"\n".join([4*indent*" "+"%s %s"%(k, parse(v, indent+1)) for k, v in cont.items()])+"\n"+4*(indent-1)*" "+">>" + return "<<\n"+"\n".join( + [4 * indent * " " + "%s %s" % (k, parse(v, indent+1)) + for k, v in cont.items()])+"\n"+4*(indent-1)*" "+">>" elif type(cont) is int or type(cont) is float: return str(cont) elif isinstance(cont, obj): @@ -35,26 +35,29 @@ def parse(cont, indent=1): elif type(cont) is list: return "[ "+" ".join([parse(c, indent) for c in cont])+" ]" -class obj(): +class obj(object): def __init__(self, content, stream=None): self.content = content self.stream = stream def tostring(self): if self.stream: - return "%d 0 obj "%self.identifier+parse(self.content)+"\nstream\n"+self.stream+"\nendstream\nendobj\n" + return "%d 0 obj " % ( + self.identifier+parse(self.content) + + "\nstream\n" + self.stream + "\nendstream\nendobj\n") else: return "%d 0 obj "%self.identifier+parse(self.content)+" endobj\n" -class pdfdoc(): - objects = list() +class pdfdoc(object): - def __init__(self, version=3, title=None, author=None, creator=None, producer=None, - creationdate=None, moddate=None, subject=None, keywords=None): + def __init__(self, version=3, title=None, author=None, creator=None, + producer=None, creationdate=None, moddate=None, subject=None, + keywords=None): self.version = version # default pdf version 1.3 now = datetime.now() + objects = [] - info = dict() + info = {} if title: info["/Title"] = "("+title+")" if author: @@ -78,7 +81,8 @@ def __init__(self, version=3, title=None, author=None, creator=None, producer=No self.info = obj(info) - # create an incomplete pages object so that a /Parent entry can be added to each page + # create an incomplete pages object so that a /Parent entry can be + # added to each page self.pages = obj({ "/Type": "/Pages", "/Kids": [], @@ -106,7 +110,8 @@ def addimage(self, color, width, height, dpi, imgformat, imgdata): error_out("unsupported color space: %s"%color) exit(1) - pdf_x, pdf_y = 72.0*width/dpi[0], 72.0*height/dpi[1] # pdf units = 1/72 inch + # pdf units = 1/72 inch + pdf_x, pdf_y = 72.0*width/dpi[0], 72.0*height/dpi[1] if pdf_x < 3.00 or pdf_y < 3.00: warning_out("pdf width or height is below 3.00 - decrease the dpi") @@ -126,7 +131,8 @@ def addimage(self, color, width, height, dpi, imgformat, imgdata): "/Width": width, "/Height": height, "/ColorSpace": color, - "/BitsPerComponent": 8, # hardcoded as PIL doesnt provide bits for non-jpeg formats + # hardcoded as PIL doesnt provide bits for non-jpeg formats + "/BitsPerComponent": 8, "/Length": len(imgdata) }, imgdata) @@ -178,9 +184,9 @@ def tostring(self): result += "%%EOF\n" return result -def main(images, dpi, title=None, author=None, creator=None, producer=None, - creationdate=None, moddate=None, subject=None, keywords=None, - colorspace=None, verbose=False): +def convert(images, dpi, title=None, author=None, creator=None, producer=None, + creationdate=None, moddate=None, subject=None, keywords=None, + colorspace=None, verbose=False): def debug_out(message): if verbose: @@ -190,7 +196,8 @@ def error_out(message): def warning_out(message): sys.stderr.write("W: "+message+"\n") - pdf = pdfdoc(3, title, author, creator, producer, creationdate, moddate, subject, keywords) + pdf = pdfdoc(3, title, author, creator, producer, creationdate, + moddate, subject, keywords) for im in images: rawdata = im.read() @@ -261,34 +268,62 @@ def warning_out(message): return pdf.tostring() -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='lossless conversion/embedding of images (in)to pdf') - parser.add_argument('images', metavar='infile', type=argparse.FileType('rb'), - nargs='+', help='input file(s)') - parser.add_argument('-o', '--output', metavar='out', type=argparse.FileType('wb'), - default=sys.stdout, help='output file (default: stdout)') - def positive_float(string): - value = float(string) - if value <= 0: - msg = "%r is not positive"%string - raise argparse.ArgumentTypeError(msg) - return value - parser.add_argument('-d', '--dpi', metavar='dpi', type=positive_float, help='dpi for pdf output (default: 96.0)') - parser.add_argument('-t', '--title', metavar='title', type=str, help='title for metadata') - parser.add_argument('-a', '--author', metavar='author', type=str, help='author for metadata') - parser.add_argument('-c', '--creator', metavar='creator', type=str, help='creator for metadata') - parser.add_argument('-p', '--producer', metavar='producer', type=str, help='producer for metadata') - def valid_date(string): - return datetime.strptime(string, "%Y-%m-%dT%H:%M:%S") - parser.add_argument('-r', '--creationdate', metavar='creationdate', - type=valid_date, help='creation date for metadata in YYYY-MM-DDTHH:MM:SS format') - parser.add_argument('-m', '--moddate', metavar='moddate', - type=valid_date, help='modification date for metadata in YYYY-MM-DDTHH:MM:SS format') - parser.add_argument('-s', '--subject', metavar='subject', type=str, help='subject for metadata') - parser.add_argument('-k', '--keywords', metavar='kw', type=str, nargs='+', help='keywords for metadata') - parser.add_argument('-C', '--colorspace', metavar='colorspace', type=str, help='force PIL colorspace (one of: RGB, L, 1)') - parser.add_argument('-v', '--verbose', help='verbose mode', action="store_true") - args = parser.parse_args() + +def positive_float(string): + value = float(string) + if value <= 0: + msg = "%r is not positive"%string + raise argparse.ArgumentTypeError(msg) + return value + +def valid_date(string): + return datetime.strptime(string, "%Y-%m-%dT%H:%M:%S") + +parser = argparse.ArgumentParser( + description='Lossless conversion/embedding of images (in)to pdf') +parser.add_argument( + 'images', metavar='infile', type=argparse.FileType('rb'), + nargs='+', help='input file(s)') +parser.add_argument( + '-o', '--output', metavar='out', type=argparse.FileType('wb'), + default=sys.stdout, help='output file (default: stdout)') +parser.add_argument( + '-d', '--dpi', metavar='dpi', type=positive_float, + help='dpi for pdf output (default: 96.0)') +parser.add_argument( + '-t', '--title', metavar='title', type=str, + help='title for metadata') +parser.add_argument( + '-a', '--author', metavar='author', type=str, + help='author for metadata') +parser.add_argument( + '-c', '--creator', metavar='creator', type=str, + help='creator for metadata') +parser.add_argument( + '-p', '--producer', metavar='producer', type=str, + help='producer for metadata') +parser.add_argument( + '-r', '--creationdate', metavar='creationdate', type=valid_date, + help='creation date for metadata in YYYY-MM-DDTHH:MM:SS format') +parser.add_argument( + '-m', '--moddate', metavar='moddate', type=valid_date, + help='modification date for metadata in YYYY-MM-DDTHH:MM:SS format') +parser.add_argument( + '-s', '--subject', metavar='subject', type=str, + help='subject for metadata') +parser.add_argument( + '-k', '--keywords', metavar='kw', type=str, nargs='+', + help='keywords for metadata') +parser.add_argument( + '-C', '--colorspace', metavar='colorspace', type=str, + help='force PIL colorspace (one of: RGB, L, 1)') +parser.add_argument( + '-v', '--verbose', help='verbose mode', action="store_true") + +def main(args=None): + if args is None: + args = sys.argv[1:] + args = parser.parse_args(args) args.output.write(main(args.images, args.dpi, args.title, args.author, args.creator, args.producer, args.creationdate, args.moddate, args.subject, args.keywords, args.colorspace, args.verbose)) diff --git a/jp2.py b/src/jp2.py similarity index 100% rename from jp2.py rename to src/jp2.py From 3a96bb46c962022372d26cc3e79f09b304379b64 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Fri, 28 Feb 2014 23:51:53 -0500 Subject: [PATCH 2/7] Added tests for the package. --- .gitignore | 3 +++ src/img2pdf.py | 46 +++++++++++++++++++++----------------- src/tests/__init__.py | 7 ++++++ src/tests/test.jpg | Bin 0 -> 2348 bytes src/tests/test.pdf | Bin 0 -> 3289 bytes src/tests/test.png | Bin 0 -> 1130 bytes src/tests/test_img2pdf.py | 20 +++++++++++++++++ 7 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 .gitignore create mode 100644 src/tests/__init__.py create mode 100644 src/tests/test.jpg create mode 100644 src/tests/test.pdf create mode 100644 src/tests/test.png create mode 100644 src/tests/test_img2pdf.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1d2abaa --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.pyc +build +src/*.egg-info diff --git a/src/img2pdf.py b/src/img2pdf.py index 6beb897..3e25faa 100755 --- a/src/img2pdf.py +++ b/src/img2pdf.py @@ -17,10 +17,21 @@ import zlib import argparse import struct -from Pillow import Image +from PIL import Image from datetime import datetime from jp2 import parsejp2 +# XXX: Switch to use logging module. +def debug_out(message, verbose=True): + if verbose: + sys.stderr.write("D: "+message+"\n") + +def error_out(message): + sys.stderr.write("E: "+message+"\n") + +def warning_out(message): + sys.stderr.write("W: "+message+"\n") + def parse(cont, indent=1): if type(cont) is dict: return "<<\n"+"\n".join( @@ -42,8 +53,9 @@ def __init__(self, content, stream=None): def tostring(self): if self.stream: - return "%d 0 obj " % ( - self.identifier+parse(self.content) + + return ( + "%d 0 obj " % self.identifier + + parse(self.content) + "\nstream\n" + self.stream + "\nendstream\nendobj\n") else: return "%d 0 obj "%self.identifier+parse(self.content)+" endobj\n" @@ -55,7 +67,7 @@ def __init__(self, version=3, title=None, author=None, creator=None, keywords=None): self.version = version # default pdf version 1.3 now = datetime.now() - objects = [] + self.objects = [] info = {} if title: @@ -188,14 +200,6 @@ def convert(images, dpi, title=None, author=None, creator=None, producer=None, creationdate=None, moddate=None, subject=None, keywords=None, colorspace=None, verbose=False): - def debug_out(message): - if verbose: - sys.stderr.write("D: "+message+"\n") - def error_out(message): - sys.stderr.write("E: "+message+"\n") - def warning_out(message): - sys.stderr.write("W: "+message+"\n") - pdf = pdfdoc(3, title, author, creator, producer, creationdate, moddate, subject, keywords) @@ -216,37 +220,37 @@ def warning_out(message): if dpi: ndpi = dpi, dpi - debug_out("input dpi (forced) = %d x %d"%ndpi) + debug_out("input dpi (forced) = %d x %d"%ndpi, verbose) else: ndpi = (96, 96) # TODO: read real dpi - debug_out("input dpi = %d x %d"%ndpi) + debug_out("input dpi = %d x %d"%ndpi, verbose) if colorspace: color = colorspace debug_out("input colorspace (forced) = %s"%(ics)) else: color = ics - debug_out("input colorspace = %s"%(ics)) + debug_out("input colorspace = %s"%(ics), verbose) else: width, height = imgdata.size imgformat = imgdata.format if dpi: ndpi = dpi, dpi - debug_out("input dpi (forced) = %d x %d"%ndpi) + debug_out("input dpi (forced) = %d x %d"%ndpi, verbose) else: ndpi = imgdata.info.get("dpi", (96, 96)) - debug_out("input dpi = %d x %d"%ndpi) + debug_out("input dpi = %d x %d"%ndpi, verbose) if colorspace: color = colorspace - debug_out("input colorspace (forced) = %s"%(color)) + debug_out("input colorspace (forced) = %s"%(color), verbose) else: color = imgdata.mode - debug_out("input colorspace = %s"%(color)) + debug_out("input colorspace = %s"%(color), verbose) - debug_out("width x height = %d x %d"%(width,height)) - debug_out("imgformat = %s"%imgformat) + debug_out("width x height = %d x %d"%(width,height), verbose) + debug_out("imgformat = %s"%imgformat, verbose) # depending on the input format, determine whether to pass the raw # image or the zlib compressed color information diff --git a/src/tests/__init__.py b/src/tests/__init__.py new file mode 100644 index 0000000..8fd6866 --- /dev/null +++ b/src/tests/__init__.py @@ -0,0 +1,7 @@ +import unittest +import test_img2pdf + +def test_suite(): + return unittest.TestSuite(( + unittest.makeSuite(test_img2pdf.TestImg2Pdf), + )) diff --git a/src/tests/test.jpg b/src/tests/test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2c036e93991fe97cf6e9a6c1a60232f96d0d8d90 GIT binary patch literal 2348 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 literal 0 HcmV?d00001 diff --git a/src/tests/test_img2pdf.py b/src/tests/test_img2pdf.py new file mode 100644 index 0000000..82bc316 --- /dev/null +++ b/src/tests/test_img2pdf.py @@ -0,0 +1,20 @@ +import datetime +import os +import unittest +import img2pdf + +HERE = os.path.dirname(__file__) +moddate = datetime.datetime(2014, 1, 1) + +class TestImg2Pdf(unittest.TestCase): + def test_jpg2pdf(self): + with open(os.path.join(HERE, 'test.jpg'), 'r') as img_fp: + with open(os.path.join(HERE, 'test.pdf'), 'r') as pdf_fp: + self.assertEqual( + img2pdf.convert([img_fp], 150, + creationdate=moddate, moddate=moddate), + pdf_fp.read()) + + def test_png2pdf(self): + with open(os.path.join(HERE, 'test.png'), 'r') as img_fp: + self.assertRaises(SystemExit, img2pdf.convert, [img_fp], 150) From 95120a904e55b5868847151a939ec13e3ae63978 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 1 Mar 2014 10:52:04 -0500 Subject: [PATCH 3/7] Make sure that all files are added to the release package. --- MANIFEST.in | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 MANIFEST.in diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..534bab3 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include README.md +include test_comp.sh +recursive-include src *.jpg +recursive-include src *.pdf +recursive-include src *.png +recursive-include src *.py From e3a9bb710e9e9667525ca9dac9a435ce8f175450 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 1 Mar 2014 10:58:28 -0500 Subject: [PATCH 4/7] Added changelog and tweaked license entry. --- CHANGES.rst | 14 ++++++++++++++ setup.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 CHANGES.rst diff --git a/CHANGES.rst b/CHANGES.rst new file mode 100644 index 0000000..69a9b36 --- /dev/null +++ b/CHANGES.rst @@ -0,0 +1,14 @@ +======= +CHANGES +======= + +1.0.0 (unreleased) +------------------ + +- Initial PyPI release. + +- Modified code to create proper package. + +- Added tests. + +- Added console script entry point. diff --git a/setup.py b/setup.py index 0b3e989..3e698df 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: Implementation :: CPython', - 'License :: OSI Approved :: General Public License', + 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 'Programming Language :: Python', 'Natural Language :: English', 'Operating System :: OS Independent'], From c9afc0877da74dab10920684178f25f4af853625 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 1 Mar 2014 11:07:22 -0500 Subject: [PATCH 5/7] Fix a bug in the console script. --- src/img2pdf.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/img2pdf.py b/src/img2pdf.py index 3e25faa..c242b79 100755 --- a/src/img2pdf.py +++ b/src/img2pdf.py @@ -328,6 +328,8 @@ def main(args=None): if args is None: args = sys.argv[1:] args = parser.parse_args(args) - args.output.write(main(args.images, args.dpi, args.title, args.author, - args.creator, args.producer, args.creationdate, args.moddate, - args.subject, args.keywords, args.colorspace, args.verbose)) + args.output.write( + convert( + args.images, args.dpi, args.title, args.author, + args.creator, args.producer, args.creationdate, args.moddate, + args.subject, args.keywords, args.colorspace, args.verbose)) From 4a7bfe85b2ec8d36f3f41af808d7d63488412006 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 1 Mar 2014 11:07:35 -0500 Subject: [PATCH 6/7] Provide some installation instructions using the typical Python tool chain. --- README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d73efb7..37bfd31 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ img2pdf Lossless conversion of images to PDF without unnecessarily re-encoding JPEG and JPEG2000 files. Thus, no loss of quality and no unnecessary large output file. -background +Background ---------- PDF is able to embed JPEG and JPEG2000 images as they are without re-encoding @@ -15,7 +15,7 @@ If you know how to embed JPEG and JPEG2000 images into a PDF container without recompression, using existing tools, please contact me so that I can put this code into the garbage bin :D -functionality +Functionality ------------- The program will take image filenames from commandline arguments and output a @@ -46,7 +46,7 @@ imagemagick. While a run of above convert command with a 2.8MB JPEG takes 27 seconds (on average) on my machine, conversion using img2pdf takes just a fraction of a second. -commandline arguments +Commandline Arguments --------------------- At least one input file argument must be given as img2pdf needs to seek in the @@ -67,7 +67,7 @@ Specify -C or --colorspace to force a colorspace using PIL short handles like More help is available with the -h or --help option. -bugs +Bugs ---- If you find a JPEG or JPEG2000 file that, when embedded can not be read by the @@ -86,3 +86,26 @@ I have not yet figured out how to read the colorspace from jpeg2000 files. Therefor jpeg2000 files use DeviceRGB per default. If your jpeg2000 files are of any other colorspace you must force it using the --colorspace option. Like -C L for DeviceGray. + +Installation +------------ + +You can install the package using: + + $ pip install img2pdf + +If you want to install from source code simply use: + + $ cd img2pdf/ + $ pip install . + +To test the console script without installing the package on your system, +simply use virtualenv: + + $ cd img2pdf/ + $ virtualenv ve + $ ve/bin/pip install . + +You can then test the converter using: + + $ ve/bin/img2pdf -o test.pdf src/tests/test.jpg From 868b6dd0b12d397d21e4c780a7bfc8a2c5d5bbf6 Mon Sep 17 00:00:00 2001 From: Stephan Richter Date: Sat, 1 Mar 2014 11:09:29 -0500 Subject: [PATCH 7/7] Add usage as a library. --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 37bfd31..f835c83 100644 --- a/README.md +++ b/README.md @@ -109,3 +109,8 @@ simply use virtualenv: You can then test the converter using: $ ve/bin/img2pdf -o test.pdf src/tests/test.jpg + +Note that the package can also be used as a library as follows: + + import img2pdf + pdf_bytes = img2pdf('test.jpg', dpi=150)