#!/usr/bin/env python
"""
Read a list of image filenames, reduce the quality to a specified level and
create a PDF file as output with each image on its own page. This can be used to
package a list of scanned images in a single file.

Requirements: Python, PIL, Reportlab.


  History: I was trying to submit an exam for an online course, and I wanted to
  package the images of the pages for my solutions which I had acquired with my
  digital camera, and I could not find a single free software program that did
  this properly. I got really frustrated, given how simple this seemed to be, so
  I wrote this very simple script to solve my problem. The PDF community is
  still living the 1980s with its shareware and bad commercial software that
  only runs on Windows, it's quite frustrating, but thanks to great tools like
  Reportlab and PDFtk, there is a way out for those with a bit of patience and
  skill. I hope you enjoy this FREE software, the result of a few hours of
  hacking on a saturday afternoon [2009-08-01]. Please feel FREE to modify this
  program to your heart's content. If you make changes which you think others
  could benefit from, please send me a patch and I'll integrate it in here with
  your name.

  Copyright (C) 2009 Martin Blais.
  This code is licensed under the terms of the GNU GPLv3.

  Keywords: jpg, jpeg, pdf, jpg2pdf, jpeg2pdf, jpgtopdf, jpegtopdf, image2pdf,
  convert pdf to jpeg

"""
__author__ = 'Martin Blais <blais@furius.ca>'

# stdlib imports
import sys, os, re, tempfile, logging
from subprocess import *
from os.path import *
from StringIO import StringIO

# pil imports
from PIL import Image

# reportlab imports
from reportlab.pdfgen import canvas
from reportlab.lib.utils import ImageReader
from reportlab.lib import pagesizes
from reportlab.lib.units import cm, mm, inch, pica


def main():
    import optparse
    parser = optparse.OptionParser(__doc__.strip())

    parser.add_option('-o', '--outfile', action='store', default='converted.pdf',
                      help="Name of output file to produce.")

    parser.add_option('-q', '--quality', action='store', type='int', default=None,
                      help=("Reduce the quality of the images to the specified one"
                            " (Default: no resampling)."))

    # FIXME: Remove this option and infer the size from the .pagesize option.
    parser.add_option('-s', '--size', action='store', type='int', default=None,
                      help="Reduce the size of the images (longest side, in pixels).")

    # FIXME: Rename this to .format or something more differentiating.
    parser.add_option('-p', '--pagesize', action='store', default='letter',
                      help="The page format to output.")

    parser.add_option('-f', '--fill', action='store_true',
                      help="Expand the images to fill the entire page")

    opts, args = parser.parse_args()

    logging.basicConfig(level=logging.INFO, format='%(levelname)-8s: %(message)s')

    # Validate chosen page size.
    if opts.pagesize == 'letter':
        size = pagesizes.letter
    elif opts.pagesize == 'landscape':
        size = pagesizes.landscape(pagesizes.letter)
    else:
        parser.error("Invalid page size: %s" % opts.pagesize)

    # Figure out what files we're going to process.
    if not args:
        parser.error("You need to provide a list of input files.")
    infiles = args

    # Read the images and create the output file, at once.
    c = canvas.Canvas(opts.outfile, pagesize=size)
    for fn in infiles:
        logging.info("Processing... %s" % fn)
        if not isfile(fn):
            logging.warning("Invalid filename: %s" % fn)
            continue

        with tempfile.NamedTemporaryFile('w') as f:

            # Reduce the image quality, in order to reduce total size.
            if opts.quality or opts.size:
                img = Image.open(fn)

                # Reduce the image if necessary.
                if opts.size:
                    msize = float(max(*img.size))
                    w = int(img.size[0]/msize*opts.size)
                    h = int(img.size[1]/msize*opts.size)
                    img = img.resize((w, h), Image.ANTIALIAS)

                if opts.quality: # branches because of treatment of kw args in PIL
                    img.save(f.name, format="JPEG", quality=opts.quality)
                else:
                    img.save(f.name, format="JPEG")
                fn = f.name

            # Draw the image to fill up the entire.
            ir = ImageReader(fn)
            c.drawImage(ir, 0, 0, size[0], size[1],
                        preserveAspectRatio=not opts.fill)
            c.showPage()

    c.save()

    # Add tags for Mac OS.
    if os.name == 'mac':
        import macfs; macfs.FSSpec(pdfPath).SetCreatorType('CARO', 'PDF ')


if __name__ == '__main__':
    main()
