1
0
mirror of https://github.com/ciromattia/kcc synced 2025-12-20 13:11:47 +00:00

Added a bunch of optimizations (autocontrast, page centering)

This commit is contained in:
Ciro Mattia Gonano
2013-01-17 16:20:32 +01:00
parent df752eb110
commit f231e9112a
4 changed files with 102 additions and 62 deletions

View File

@@ -1,32 +1,34 @@
# KindleComicConverter # 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. `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 ## INPUT FORMATS
`kcc` can understand and convert, at the moment, the following file types: `kcc` can understand and convert, at the moment, the following file types:
- CBZ, ZIP - CBZ, ZIP
- CBR, RAR - CBR, RAR *(with `unrar` executable)*
- flat folders - 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. For now the script does not understand folder depth, so it will work on flat folders/archives only.
## REQUIREMENTS ## REQUIREMENTS
- `kindlegen` in /usr/local/bin/ - `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: ### 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) - 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. Please refer to official documentation for installing into your system.
## USAGE ## 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*). ### Standalone `comic2ebook.py` usage:
> If you want to specify other profiles, please use the script from command line.
### standalone `comic2ebook.py` usage:
``` ```
comic2ebook.py [options] comic_file|comic_folder 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 --version show program's version number and exit
-h, --help show this help message and exit -h, --help show this help message and exit
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (choose one among K1, K2, K3, K4, KHD Device profile (choose one among K1, K2, K3, K4, KDX, KDXG or KHD) [default=KHD]
[default])
-t TITLE, --title=TITLE -t TITLE, --title=TITLE
Comic title Comic title [default=filename]
-m, --manga-style Split pages 'manga style' (right-to-left reading) -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: 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. 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! 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 ## 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)) 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! WARNING: PIL is required for all image mangling!
- 1.30 - Fixed an issue in OPF generation for device resolution - 1.30 - Fixed an issue in OPF generation for device resolution
Reworked options system (call with -h option to get the inline help) 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. - 2.00 - GUI! AppleScript is gone and Tk is used to provide cross-platform GUI support.
## TODO ## TODO
- Add gracefully exit for CBR if no rarfile.py and no unrar executable are found - Add gracefully exit for CBR if no rarfile.py and no unrar executable are found
- Improve error reporting - Improve error reporting
- Recurse into dirtree for multiple comics - Recurse into dirtree for multiple comics
- Support pages extraction from PDF files
## COPYRIGHT ## COPYRIGHT

View File

@@ -141,13 +141,21 @@ def main(argv=None):
usage = "Usage: %prog [options] comic_file|comic_folder" usage = "Usage: %prog [options] comic_file|comic_folder"
parser = OptionParser(usage=usage, version=__version__) parser = OptionParser(usage=usage, version=__version__)
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD", 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", parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [default=filename]") help="Comic title [default=filename]")
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Split pages 'manga style' (right-to-left reading) [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, parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
help="Verbose output [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) options, args = parser.parse_args(argv)
if len(args) != 1: if len(args) != 1:
parser.print_help() parser.print_help()
@@ -163,8 +171,17 @@ def main(argv=None):
if cbx.isCbxFile(): if cbx.isCbxFile():
cbx.extract() cbx.extract()
dir = cbx.getPath() dir = cbx.getPath()
filelist = [] else:
try: try:
import shutil
shutil.copytree(dir, dir + "_orig")
#dir = dir + "_orig"
except OSError as exc:
raise
filelist = []
if options.imgproc:
try:
if options.verbose:
print "Splitting double pages..." print "Splitting double pages..."
for file in os.listdir(dir): for file in os.listdir(dir):
if getImageFileName(file) is not None: if getImageFileName(file) is not None:
@@ -172,13 +189,14 @@ def main(argv=None):
img.splitPage(dir, options.righttoleft) img.splitPage(dir, options.righttoleft)
for file in os.listdir(dir): for file in os.listdir(dir):
if getImageFileName(file) is not None: if getImageFileName(file) is not None:
if options.verbose:
print "Optimizing " + file + " for " + options.profile print "Optimizing " + file + " for " + options.profile
img = image.ComicPage(dir+'/'+file, options.profile) img = image.ComicPage(dir+'/'+file, options.profile)
img.optimizeImage()
img.cropWhiteSpace(10.0)
if options.cutpagenumbers:
img.cutPageNumber() img.cutPageNumber()
img.cropWhiteSpace(5.0) img.resizeImage(options.upscale,options.stretch)
img.resizeImage()
#img.frameImage()
#img.addProgressbar()
img.quantizeImage() img.quantizeImage()
img.saveToDir(dir) img.saveToDir(dir)
except ImportError: except ImportError:
@@ -193,11 +211,14 @@ def main(argv=None):
filename = HTMLbuilder(dir,file).getResult() filename = HTMLbuilder(dir,file).getResult()
if filename is not None: if filename is not None:
filelist.append(filename) filelist.append(filename)
if options.title == 'defaulttitle':
options.title = os.path.basename(dir)
NCXbuilder(dir,options.title) NCXbuilder(dir,options.title)
# ensure we're sorting files alphabetically # ensure we're sorting files alphabetically
filelist = sorted(filelist, key=lambda name: name[0]) filelist = sorted(filelist, key=lambda name: name[0])
OPFBuilder(options.profile,dir,options.title,filelist) OPFBuilder(options.profile,dir,options.title,filelist)
if __name__ == "__main__": if __name__ == "__main__":
Copyright() Copyright()
main(sys.argv[1:]) main(sys.argv[1:])

View File

@@ -95,6 +95,7 @@ class MainWindow:
subargv = list(argv) subargv = list(argv)
subargv.append(entry) subargv.append(entry)
comic2ebook.main(subargv) comic2ebook.main(subargv)
print "Done!" print "Done!"
def __init__(self, master, title): def __init__(self, master, title):

View File

@@ -16,7 +16,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import os import os
from PIL import Image, ImageDraw, ImageStat from PIL import Image, ImageOps, ImageDraw, ImageStat
class ImageFlags: class ImageFlags:
Orient = 1 << 0 Orient = 1 << 0
@@ -103,6 +103,9 @@ class ComicPage:
except IOError as e: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e)) raise RuntimeError('Cannot write image in directory %s: %s' %(targetdir,e))
def optimizeImage(self):
self.image = ImageOps.autocontrast(self.image)
def quantizeImage(self): def quantizeImage(self):
colors = len(self.palette) / 3 colors = len(self.palette) / 3
if colors < 256: if colors < 256:
@@ -111,33 +114,31 @@ class ComicPage:
palImg.putpalette(self.palette) palImg.putpalette(self.palette)
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def stretchImage(self): def resizeImage(self,upscale=False, stretch=False):
widthDev, heightDev = self.size method = Image.ANTIALIAS
self.image = self.image.resize((widthDev, heightDev), Image.ANTIALIAS) if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
if not upscale:
# 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:
return self.image 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: else:
widthImg, heightImg = self.size method = Image.NEAREST
self.image = self.image.resize((widthImg, heightImg), Image.ANTIALIAS)
if stretch: # if stretching call directly resize() without other considerations.
self.image = self.image.resize(self.size,method)
return self.image
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): def splitPage(self, targetdir, righttoleft=False):
width, height = self.image.size width, height = self.image.size