From f231e9112a2006de619a4cfb20dd9a2db566851e Mon Sep 17 00:00:00 2001 From: Ciro Mattia Gonano Date: Thu, 17 Jan 2013 16:20:32 +0100 Subject: [PATCH] Added a bunch of optimizations (autocontrast, page centering) --- README.md | 49 +++++++++++++++++++++++++------------ kcc/comic2ebook.py | 61 +++++++++++++++++++++++++++++++--------------- kcc/gui.py | 1 + kcc/image.py | 53 ++++++++++++++++++++-------------------- 4 files changed, 102 insertions(+), 62 deletions(-) diff --git a/README.md b/README.md index 27a720c..b2fca62 100644 --- a/README.md +++ b/README.md @@ -1,32 +1,34 @@ # KindleComicConverter `KindleComicConverter` is a Python app which aim is to convert comic files or folders to a comic-type (Mobipocket) ebook to take advantage of the new Panel View mode on Amazon's Kindle. +It also optimizes comic images by: +- enhancing contrast +- cutting page numbering +- cropping white borders +- resizing larger images to device's native resolution +- quantizing images to device's palette ## INPUT FORMATS `kcc` can understand and convert, at the moment, the following file types: - CBZ, ZIP -- CBR, RAR +- CBR, RAR *(with `unrar` executable)* - flat folders -- PDF *(extracting only contained JPG images)* +- PDF *(extracting only contained JPG images)* + For now the script does not understand folder depth, so it will work on flat folders/archives only. ## REQUIREMENTS - `kindlegen` in /usr/local/bin/ -- [unrar](http://www.rarlab.com/download.htm) and [rarfile.py](http://developer.berlios.de/project/showfiles.php?group_id=5373&release_id=18844) for `calibre2ebook.py` automatic CBR extracting. +- [unrar](http://www.rarlab.com/download.htm) CBR support. ### for compiling/running from source: - Python 2.7+ (included in MacOS and Linux, follow the [official documentation](http://www.python.org/getit/windows/) to install on Windows) -- You are strongly encouraged to get the [Python Imaging Library](http://www.pythonware.com/products/pil/) that, altough optional, provides a bunch of comic optimizations like split double pages, resize to optimal resolution, improve contrast and palette, etc. +- [Python Imaging Library](http://www.pythonware.com/products/pil/) for comic optimizations like split double pages, resize to optimal resolution, improve contrast and palette, etc. Please refer to official documentation for installing into your system. ## USAGE -Drop a folder or a CBZ/CBR file over the app, after a while you'll get a comic-type .mobi to sideload on your Kindle. -The script takes care of calling `comic2ebook.py`, `kindlegen` and `kindlestrip.py`. -> **WARNING:** at the moment the droplet *ALWAYS* uses the **KHD** profile (*Kindle Paperwhite*). -> If you want to specify other profiles, please use the script from command line. - -### standalone `comic2ebook.py` usage: +### Standalone `comic2ebook.py` usage: ``` comic2ebook.py [options] comic_file|comic_folder @@ -34,18 +36,32 @@ comic2ebook.py [options] comic_file|comic_folder --version show program's version number and exit -h, --help show this help message and exit -p PROFILE, --profile=PROFILE - Device profile (choose one among K1, K2, K3, K4, KHD - [default]) + Device profile (choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [default=KHD] -t TITLE, --title=TITLE - Comic title - -m, --manga-style Split pages 'manga style' (right-to-left reading) + Comic title [default=filename] + -m, --manga-style Split pages 'manga style' (right-to-left reading) [default=False] + -v, --verbose Verbose output [default=False] + -i, --image-processing + Apply image preprocessing (page splitting and optimizations) [default=True] + --upscale-images Resize images smaller than device's resolution [default=False] + --stretch-images Stretch images to device's resolution [default=False] + --cut-page-numbers Try to cut page numbering on images [default=True] ``` The script takes care of unzipping/unrarring the file if it's an archive, creating a directory of images which should be then filled with a `.opf`, `.ncx`, and many `.html` files, then: 1. Run `Kindlegen` on `content.opf`. Depending on how many images you have, this may take awhile. Once completed, the `.mobi` file should be in the directory. -2. Remove the SRCS record to reduce the `.mobi` filesize in half. You can use [Kindlestrip](http://www.mobileread.com/forums/showthread.php?t=96903). +2. (optionally) remove the SRCS record to reduce the `.mobi` filesize in half. You can use [Kindlestrip](http://www.mobileread.com/forums/showthread.php?t=96903). 3. Copy the `.mobi` file to your Kindle! +### AppleScript Droplet (may be outdated) + +Drop a folder or a CBZ/CBR file over the app, after a while you'll get a comic-type .mobi to sideload on your Kindle. +The script takes care of calling `comic2ebook.py`, `kindlegen` and `kindlestrip.py`. + +> **WARNING:** at the moment the droplet *ALWAYS* uses the **KHD** profile (*Kindle Paperwhite*). +> If you want to specify other profiles, please use the script from command line. + + ## CREDITS This script born as a cross-platform alternative to `KindleComicParser` by **Dc5e** (published in [this mobileread forum thread](http://www.mobileread.com/forums/showthread.php?t=192783)) @@ -71,13 +87,14 @@ and installed in `/usr/local/bin/` WARNING: PIL is required for all image mangling! - 1.30 - Fixed an issue in OPF generation for device resolution Reworked options system (call with -h option to get the inline help) + - 1.40 - Added some options for controlling image optimization + Further optimization (ImageOps, page numbering cut, autocontrast) - 2.00 - GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support. ## TODO - Add gracefully exit for CBR if no rarfile.py and no unrar executable are found - Improve error reporting - Recurse into dirtree for multiple comics - - Support pages extraction from PDF files ## COPYRIGHT diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index 8d23a68..1d57946 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -141,13 +141,21 @@ def main(argv=None): usage = "Usage: %prog [options] comic_file|comic_folder" parser = OptionParser(usage=usage, version=__version__) parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD", - help="Device profile (choose one among K1, K2, K3, K4, KDX or KHD) [default=KHD]") + help="Device profile (choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [default=KHD]") parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", help="Comic title [default=filename]") parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, help="Split pages 'manga style' (right-to-left reading) [default=False]") parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="Verbose output [default=False]") + parser.add_option("-i", "--image-processing", action="store_false", dest="imgproc", default=True, + help="Apply image preprocessing (page splitting and optimizations) [default=True]") + parser.add_option("--upscale-images", action="store_true", dest="upscale", default=False, + help="Resize images smaller than device's resolution [default=False]") + parser.add_option("--stretch-images", action="store_true", dest="stretch", default=False, + help="Stretch images to device's resolution [default=False]") + parser.add_option("--cut-page-numbers", action="store_false", dest="cutpagenumbers", default=True, + help="Try to cut page numbering on images [default=True]") options, args = parser.parse_args(argv) if len(args) != 1: parser.print_help() @@ -163,26 +171,36 @@ def main(argv=None): if cbx.isCbxFile(): cbx.extract() dir = cbx.getPath() + else: + try: + import shutil + shutil.copytree(dir, dir + "_orig") + #dir = dir + "_orig" + except OSError as exc: + raise filelist = [] - try: - print "Splitting double pages..." - for file in os.listdir(dir): - if getImageFileName(file) is not None: - img = image.ComicPage(dir+'/'+file, options.profile) - img.splitPage(dir, options.righttoleft) - for file in os.listdir(dir): - if getImageFileName(file) is not None: - print "Optimizing " + file + " for " + options.profile - img = image.ComicPage(dir+'/'+file, options.profile) - img.cutPageNumber() - img.cropWhiteSpace(5.0) - img.resizeImage() - #img.frameImage() - #img.addProgressbar() - img.quantizeImage() - img.saveToDir(dir) - except ImportError: - print "Could not load PIL, not optimizing image" + if options.imgproc: + try: + if options.verbose: + print "Splitting double pages..." + for file in os.listdir(dir): + if getImageFileName(file) is not None: + img = image.ComicPage(dir+'/'+file, options.profile) + img.splitPage(dir, options.righttoleft) + for file in os.listdir(dir): + if getImageFileName(file) is not None: + if options.verbose: + print "Optimizing " + file + " for " + options.profile + img = image.ComicPage(dir+'/'+file, options.profile) + img.optimizeImage() + img.cropWhiteSpace(10.0) + if options.cutpagenumbers: + img.cutPageNumber() + img.resizeImage(options.upscale,options.stretch) + img.quantizeImage() + img.saveToDir(dir) + except ImportError: + print "Could not load PIL, not optimizing image" for file in os.listdir(dir): if getImageFileName(file) is not None and isInFilelist(file,filelist) == False: @@ -193,11 +211,14 @@ def main(argv=None): filename = HTMLbuilder(dir,file).getResult() if filename is not None: filelist.append(filename) + if options.title == 'defaulttitle': + options.title = os.path.basename(dir) NCXbuilder(dir,options.title) # ensure we're sorting files alphabetically filelist = sorted(filelist, key=lambda name: name[0]) OPFBuilder(options.profile,dir,options.title,filelist) + if __name__ == "__main__": Copyright() main(sys.argv[1:]) diff --git a/kcc/gui.py b/kcc/gui.py index 6e0a7f5..4822107 100644 --- a/kcc/gui.py +++ b/kcc/gui.py @@ -95,6 +95,7 @@ class MainWindow: subargv = list(argv) subargv.append(entry) comic2ebook.main(subargv) + print "Done!" def __init__(self, master, title): diff --git a/kcc/image.py b/kcc/image.py index c6042ce..42bf56b 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -16,7 +16,7 @@ # along with this program. If not, see . import os -from PIL import Image, ImageDraw, ImageStat +from PIL import Image, ImageOps, ImageDraw, ImageStat class ImageFlags: Orient = 1 << 0 @@ -103,6 +103,9 @@ class ComicPage: except IOError as e: raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e)) + def optimizeImage(self): + self.image = ImageOps.autocontrast(self.image) + def quantizeImage(self): colors = len(self.palette) / 3 if colors < 256: @@ -111,33 +114,31 @@ class ComicPage: palImg.putpalette(self.palette) self.image = self.image.quantize(palette=palImg) - def stretchImage(self): - widthDev, heightDev = self.size - self.image = self.image.resize((widthDev, heightDev), Image.ANTIALIAS) + def resizeImage(self,upscale=False, stretch=False): + method = Image.ANTIALIAS + if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]: + if not upscale: + return self.image + else: + method = Image.NEAREST - # TODO: - # - add option to stretch page - # - add option to upscale page - # - if ratio is not equal to dev size and stretch is not enabled, add white - # background and center it (otherwise K3 does not display page - # center-aligned but left-aligned) - def resizeImage(self): - widthDev, heightDev = self.size - widthImg, heightImg = self.image.size - if widthImg <= widthDev and heightImg <= heightDev: + if stretch: # if stretching call directly resize() without other considerations. + self.image = self.image.resize(self.size,method) return self.image - ratioImg = float(widthImg) / float(heightImg) - ratioWidth = float(widthImg) / float(widthDev) - ratioHeight = float(heightImg) / float(heightDev) - if ratioWidth > ratioHeight: - widthImg = widthDev - heightImg = int(widthDev / ratioImg) - elif ratioWidth < ratioHeight: - heightImg = heightDev - widthImg = int(heightDev * ratioImg) - else: - widthImg, heightImg = self.size - self.image = self.image.resize((widthImg, heightImg), Image.ANTIALIAS) + + ratioDev = float(self.size[0]) / float(self.size[1]) + if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev: + diff = int(self.image.size[1] * ratioDev) - self.image.size[0] + newImage = Image.new('RGB', (self.image.size[0] + diff, self.image.size[1]), (255,255,255)) + newImage.paste(self.image, (diff / 2, 0, diff / 2 + self.image.size[0], self.image.size[1])) + self.image = newImage + elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev: + diff = int(self.image.size[0] * ratioDev) - self.image.size[1] + newImage = Image.new('RGB', (self.image.size[0], self.image.size[1] + diff), (255,255,255)) + newImage.paste(self.image, (0, diff / 2, self.image.size[0], diff / 2 + self.image.size[1])) + self.image = newImage + self.image = ImageOps.fit(self.image, self.size, method = method, centering = (0.5,0.5)) + return self.image def splitPage(self, targetdir, righttoleft=False): width, height = self.image.size