mirror of
https://github.com/ciromattia/kcc
synced 2026-04-16 14:08:45 +00:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb0856c841 | ||
|
|
c21e8e398e | ||
|
|
bba0223efc | ||
|
|
9709bc9c12 | ||
|
|
0d61442c18 | ||
|
|
89fa1393db | ||
|
|
15e86b28ab | ||
|
|
a72d1128dd | ||
|
|
0fd3f6bc0f | ||
|
|
d76ce6c92f | ||
|
|
b6fa993c17 | ||
|
|
81509012b4 | ||
|
|
6db1ab0792 | ||
|
|
513ea0a7cd | ||
|
|
640b5117f9 | ||
|
|
c9d558a353 | ||
|
|
378aff4f35 | ||
|
|
6142c87fa3 | ||
|
|
b01e8e2bc2 | ||
|
|
10724489fc | ||
|
|
3365f111e4 | ||
|
|
b1d22cd05b | ||
|
|
74f2250952 | ||
|
|
36516fd594 | ||
|
|
2dab7a707b | ||
|
|
fc8b26e292 | ||
|
|
5c0964c8fa | ||
|
|
6836c6ee17 | ||
|
|
6adc2dff93 | ||
|
|
63deec88db | ||
|
|
c37e281d69 | ||
|
|
4a0497addc | ||
|
|
e2f5c549aa | ||
|
|
a8195d44ee | ||
|
|
18a505637d | ||
|
|
62475e12c6 | ||
|
|
46888e10d8 | ||
|
|
eb406aada0 | ||
|
|
1582d03fab | ||
|
|
4cfac52d6a | ||
|
|
04ea816365 | ||
|
|
6a298175f0 | ||
|
|
690f0298e6 | ||
|
|
fc93697c28 | ||
|
|
32391f6a5a | ||
|
|
f163eac853 | ||
|
|
ce824f4cab | ||
|
|
df41ad405e | ||
|
|
685cdc929b | ||
|
|
5e65c5149c | ||
|
|
718af10be7 | ||
|
|
b162425e52 | ||
|
|
f1b63420f6 | ||
|
|
95e7329abf | ||
|
|
5d3b7e83f5 | ||
|
|
751e6eb4e7 | ||
|
|
074e31cb2e | ||
|
|
e7e87d03cd | ||
|
|
38669d08ed | ||
|
|
825eafad7f | ||
|
|
f805984c1c |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -2,4 +2,6 @@
|
||||
*.cbz
|
||||
*.cbr
|
||||
.idea
|
||||
build
|
||||
build
|
||||
awkcc
|
||||
.DS_Store
|
||||
|
||||
117
README.md
117
README.md
@@ -1,38 +1,37 @@
|
||||
# KCC
|
||||
|
||||
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View Mobipocket.
|
||||
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View MOBI.
|
||||
It was initally developed for Kindle but as of version 2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is
|
||||
actually a comic 2 epub converter that every ereader owner can happily use**_.
|
||||
|
||||
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
|
||||
- Enhancing contrast.
|
||||
- Cutting page numbering.
|
||||
- Cropping white borders.
|
||||
- Resizing larger images to device's native resolution.
|
||||
- Quantizing images to device's palette.
|
||||
|
||||
## BINARY RELEASES
|
||||
You can find the latest released binary at the following links:
|
||||
- OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.6.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.6.zip)
|
||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.6.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.6.zip)
|
||||
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.6.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.6.zip) *(thanks to [AcidWeb](https://github.com/AcidWeb))*
|
||||
- Linux: just download sourcecode and launch `python kcc.py` *(provided you have Python and Pillow installed)*
|
||||
- OS X: [https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.7.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.7.zip)
|
||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.7.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.7.zip)
|
||||
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.7.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.7.zip) *(thanks to [AcidWeb](https://github.com/AcidWeb))*
|
||||
- Linux: Just download sourcecode and launch `python kcc.py` *(Provided you have Python and Pillow installed)*
|
||||
|
||||
## 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:
|
||||
- PNG, JPG
|
||||
- Folders
|
||||
- CBZ, ZIP
|
||||
- CBR, RAR *(with `unrar` executable)*
|
||||
- folders
|
||||
- PDF *(extracting only contained JPG images)*
|
||||
|
||||
~~For now the script does not understand folder depth, so it will work on flat folders/archives only.~~
|
||||
As of v. 1.50, KCC supports subfolders!
|
||||
- CBR, RAR *(With `unrar` executable)*
|
||||
- PDF *(Extracting only contained JPG images)*
|
||||
|
||||
## OPTIONAL REQUIREMENTS
|
||||
- `kindlegen` in /usr/local/bin/ *(for .mobi generation)*
|
||||
- [unrar](http://www.rarlab.com/download.htm) *(for CBR support)*
|
||||
- `kindlegen` v2.7+ in a directory reachable by your PATH or in KCC directory *(For .mobi generation)*
|
||||
- [unrar](http://www.rarlab.com/download.htm) *(For 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)
|
||||
### 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)
|
||||
- [Pillow](http://pypi.python.org/pypi/Pillow/) 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.
|
||||
|
||||
@@ -47,40 +46,29 @@ Conversion being done, you should find an .epub and a .mobi files alongside the
|
||||
### Standalone `comic2ebook.py` usage:
|
||||
|
||||
```
|
||||
comic2ebook.py [options] comic_file|comic_folder
|
||||
Options:
|
||||
--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, KDX,
|
||||
KDXG or KHD) [default=KHD]
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [default=filename]
|
||||
-m, --manga-style 'Manga style' (right-to-left reading and splitting)
|
||||
[default=False]
|
||||
-v, --verbose Verbose output [default=False]
|
||||
--no-image-processing
|
||||
Do not 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]
|
||||
--black-borders Use black borders (instead of white ones) when not
|
||||
stretching and ratio is not like the device's one
|
||||
[default=False]
|
||||
--no-cut-page-numbers
|
||||
Do not try to cut page numbering on images
|
||||
[default=True]
|
||||
--rotate Disable page spliting. Instead rotate images
|
||||
[default=False]
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output directory or file for generated ePub
|
||||
```
|
||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
||||
|
||||
The script takes care of creating an *.epub* from your archive/folder, then:
|
||||
1. Run `Kindlegen` on the generated *.epub*. Depending on how many images you have, this may take awhile. Once completed, the `.mobi` file should be in the directory.
|
||||
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!
|
||||
Options:
|
||||
--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, KDX, KDXG or KHD) [Default=KHD]
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
|
||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
||||
--nodithering Disable image quantization [Default=False]
|
||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
||||
--upscale Resize images smaller than device's resolution [Default=False]
|
||||
--stretch Stretch images to device's resolution [Default=False]
|
||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output generated EPUB to specified directory or file
|
||||
-v, --verbose Verbose output [Default=False]
|
||||
```
|
||||
|
||||
## CREDITS
|
||||
KCC is made by [Ciro Mattia Gonano](http://github.com/ciromattia) and [Paweł Jastrzębski](http://github.com/AcidWeb)
|
||||
@@ -94,10 +82,7 @@ The app relies and includes the following scripts/binaries:
|
||||
- `rarfile.py` script © 2005-2011 **Marko Kreen** <markokr@gmail.com>, released with ISC License
|
||||
- the icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC Attribution-NonCommercial-ShareAlike 3.0 Unported](http://creativecommons.org/licenses/by-nc-sa/3.0/) License
|
||||
- `image.py` class from **Alex Yatskov**'s [Mangle](http://foosoft.net/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches
|
||||
|
||||
Also, for .mobi generation you need to have `kindlegen` v2.7+ (with KF8 support) which is downloadable from Amazon website
|
||||
and installed in a directory reachable by your PATH (e.g. `/usr/local/bin/` or `C:\Windows\`)
|
||||
|
||||
- `magic.py` from [python-magic](https://github.com/ahupp/python-magic) library
|
||||
|
||||
## CHANGELOG
|
||||
- 1.00: Initial version
|
||||
@@ -120,10 +105,20 @@ and installed in a directory reachable by your PATH (e.g. `/usr/local/bin/` or `
|
||||
- 2.4: Use temporary directory as workdir (fixes converting from external volumes and zipfiles renaming)
|
||||
Fixed "add folders" from GUI.
|
||||
- 2.5: Added --black-borders option to set added borders black when page's ratio is not the device's one (#11).
|
||||
Fixes epub containing zipped itself (#10).
|
||||
- 2.6: Added --rotate option to rotate landscape images instead of splitting them.
|
||||
Added --output option to customize ePub output dir/file.
|
||||
Fixes epub containing zipped itself (#10)
|
||||
- 2.6: Added --rotate option to rotate landscape images instead of splitting them (#16, #24)
|
||||
Added --output option to customize ePub output dir/file (#22)
|
||||
Add rendition:layout and rendition:orientation ePub meta tags (supported by new kindlegen 2.8)
|
||||
Fixed natural sorting for files (#18)
|
||||
- 2.7: Lots of GUI improvements (#27, #13)
|
||||
Added gamma support within --gamma option (defaults to profile-specified gamma) (#26, #27)
|
||||
Added --nodithering option to prevent dithering optimizations (#27)
|
||||
Epub margins support (#30)
|
||||
Fixed no file added if file has no spaces on Windows (#25)
|
||||
Gracefully exit if unrar missing (#15)
|
||||
Do not call kindlegen if source epub is bigger than 320MB (#17)
|
||||
Get filetype from magic number (#14)
|
||||
PDF conversion works again
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
|
||||
BIN
comic2ebook.ico
Normal file
BIN
comic2ebook.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 345 KiB |
15
kcc.py
15
kcc.py
@@ -16,8 +16,8 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '2.6'
|
||||
__license__ = 'ISC'
|
||||
__version__ = '2.7'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
@@ -26,9 +26,14 @@ from kcc import gui
|
||||
from sys import platform
|
||||
import os
|
||||
|
||||
root = Tk()
|
||||
root.resizable(width=False, height=False)
|
||||
root.config(padx=5, pady=5, takefocus=True)
|
||||
root.title("Kindle Comic Converter v" + __version__)
|
||||
root.wm_attributes("-topmost", 1)
|
||||
if platform == 'darwin':
|
||||
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
|
||||
root = Tk()
|
||||
app = gui.MainWindow(master=root,title="Kindle Comic Converter v" + __version__)
|
||||
root.tkraise()
|
||||
elif platform == 'win32':
|
||||
root.iconbitmap(default='comic2ebook.ico')
|
||||
gui.MainWindow(master=root)
|
||||
root.mainloop()
|
||||
|
||||
@@ -19,26 +19,28 @@ __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
import zipfile
|
||||
import rarfile
|
||||
|
||||
|
||||
class CBxArchive:
|
||||
def __init__(self, origFileName):
|
||||
self.cbxexts = ['.zip', '.cbz', '.rar', '.cbr']
|
||||
self.origFileName = origFileName
|
||||
self.filename = os.path.splitext(origFileName)
|
||||
if zipfile.is_zipfile(origFileName):
|
||||
self.compressor = 'zip'
|
||||
elif rarfile.is_rarfile(origFileName):
|
||||
self.compressor = 'rar'
|
||||
else:
|
||||
self.compressor = None
|
||||
|
||||
def isCbxFile(self):
|
||||
return self.filename[1].lower() in self.cbxexts
|
||||
return self.compressor is not None
|
||||
|
||||
def extractCBZ(self, targetdir):
|
||||
try:
|
||||
from zipfile import ZipFile
|
||||
except ImportError:
|
||||
self.cbzFile = None
|
||||
cbzFile = ZipFile(self.origFileName)
|
||||
cbzFile = zipfile.ZipFile(self.origFileName)
|
||||
for f in cbzFile.namelist():
|
||||
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
||||
pass # skip MacOS special files
|
||||
pass # skip MacOS special files
|
||||
elif f.endswith('/'):
|
||||
try:
|
||||
os.makedirs(os.path.join(targetdir, f))
|
||||
@@ -48,27 +50,22 @@ class CBxArchive:
|
||||
cbzFile.extract(f, targetdir)
|
||||
|
||||
def extractCBR(self, targetdir):
|
||||
try:
|
||||
import rarfile
|
||||
except ImportError:
|
||||
self.cbrFile = None
|
||||
return
|
||||
cbrFile = rarfile.RarFile(self.origFileName)
|
||||
for f in cbrFile.namelist():
|
||||
if f.startswith('__MACOSX') or f.endswith('.DS_Store'):
|
||||
pass # skip MacOS special files
|
||||
elif f.endswith('/'):
|
||||
try:
|
||||
os.makedirs(os.path.join(targetdir,f))
|
||||
os.makedirs(os.path.join(targetdir, f))
|
||||
except:
|
||||
pass # the dir exists so we are going to extract the images only.
|
||||
else:
|
||||
cbrFile.extract(f, targetdir)
|
||||
|
||||
def extract(self, targetdir):
|
||||
if '.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower():
|
||||
if self.compressor == 'rar':
|
||||
self.extractCBR(targetdir)
|
||||
elif '.cbz' == self.filename[1].lower() or '.zip' == self.filename[1].lower():
|
||||
elif self.compressor == 'zip':
|
||||
self.extractCBZ(targetdir)
|
||||
adir = os.listdir(targetdir)
|
||||
if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])):
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '2.6'
|
||||
__version__ = '2.7'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -60,10 +60,14 @@ def buildHTML(path, imgfile):
|
||||
"<head>\n",
|
||||
"<title>", filename[0], "</title>\n",
|
||||
"<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n",
|
||||
"<link href=\"", "../" * (backref - 1),
|
||||
"stylesheet.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"<link href=\"", "../" * (backref - 1),
|
||||
"page_styles.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"</head>\n",
|
||||
"<body>\n",
|
||||
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\" class=\"singlePage\"/></div>\n",
|
||||
"<body class=\"kcc\">\n",
|
||||
"<div class=\"kcc1\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\" class=\"kcc2\"/></div>\n",
|
||||
#"<div id=\"", filename[0], "-1\">\n",
|
||||
#"<a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"", filename[0],
|
||||
#"-1-magTargetParent\", \"ordinal\":1}'></a>\n",
|
||||
@@ -117,7 +121,7 @@ def buildNCX(dstdir, title, chapters):
|
||||
folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
title = os.path.basename(folder)
|
||||
filename = getImageFileName(os.path.join(folder, chapter[1]))
|
||||
f.write("<navPoint id=\"" + folder.replace('/', '_') + "\"><navLabel><text>" + title
|
||||
f.write("<navPoint id=\"" + folder.replace('/', '_').replace('\\', '_') + "\"><navLabel><text>" + title
|
||||
+ "</text></navLabel><content src=\"" + filename[0] + ".html\"/></navPoint>\n")
|
||||
f.write("</navMap>\n</ncx>")
|
||||
f.close()
|
||||
@@ -127,7 +131,7 @@ def buildNCX(dstdir, title, chapters):
|
||||
def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
|
||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
||||
# read the first file resolution
|
||||
profilelabel, deviceres, palette = image.ProfileData.Profiles[profile]
|
||||
profilelabel, deviceres, palette, gamma = image.ProfileData.Profiles[profile]
|
||||
imgres = str(deviceres[0]) + "x" + str(deviceres[1])
|
||||
if righttoleft:
|
||||
writingmode = "horizontal-rl"
|
||||
@@ -176,27 +180,31 @@ def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
|
||||
for path in filelist:
|
||||
folder = path[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\')
|
||||
filename = getImageFileName(path[1])
|
||||
uniqueid = os.path.join(folder, filename[0]).replace('/', '_')
|
||||
uniqueid = os.path.join(folder, filename[0]).replace('/', '_').replace('\\', '_')
|
||||
reflist.append(uniqueid)
|
||||
f.write("<item id=\"page_" + uniqueid + "\" href=\""
|
||||
+ os.path.join(folder.replace('Images', 'Text'), filename[0])
|
||||
+ folder.replace('Images', 'Text') + "/" + filename[0]
|
||||
+ ".html\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
if '.png' == filename[1]:
|
||||
mt = 'image/png'
|
||||
else:
|
||||
mt = 'image/jpeg'
|
||||
f.write("<item id=\"img_" + uniqueid + "\" href=\"" + os.path.join(folder, path[1]) + "\" media-type=\""
|
||||
f.write("<item id=\"img_" + uniqueid + "\" href=\"" + folder + "/" + path[1] + "\" media-type=\""
|
||||
+ mt + "\"/>\n")
|
||||
if (options.profile == 'K4' or options.profile == 'KHD') and splittedSomething:
|
||||
f.write("<item id=\"blank-page\" href=\""
|
||||
+ os.path.join('Text', 'blank.html')
|
||||
+ "\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
if (options.profile == 'K4' or options.profile == 'KHD') and splitCount > 0:
|
||||
splitCountUsed = 1
|
||||
while splitCountUsed <= splitCount:
|
||||
f.write("<item id=\"blank-page" + str(splitCountUsed) +
|
||||
"\" href=\"Text/blank.html\" media-type=\"application/xhtml+xml\"/>\n")
|
||||
splitCountUsed += 1
|
||||
f.write("</manifest>\n<spine toc=\"ncx\">\n")
|
||||
splitCountUsed = 1
|
||||
for entry in reflist:
|
||||
if entry.endswith("-1"):
|
||||
if (righttoleft and facing == 'left') or (not righttoleft and facing == 'right') and \
|
||||
if ((righttoleft and facing == 'left') or (not righttoleft and facing == 'right')) and \
|
||||
(options.profile == 'K4' or options.profile == 'KHD'):
|
||||
f.write("<itemref idref=\"blank-page\" properties=\"layout-blank\"/>\n")
|
||||
f.write("<itemref idref=\"blank-page" + str(splitCountUsed) + "\" properties=\"layout-blank\"/>\n")
|
||||
splitCountUsed += 1
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
|
||||
elif entry.endswith("-2"):
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
|
||||
@@ -248,18 +256,21 @@ def isInFilelist(filename, filelist):
|
||||
|
||||
|
||||
def applyImgOptimization(img, isSplit=False, toRight=False):
|
||||
img.optimizeImage()
|
||||
img.optimizeImage(options.gamma)
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers:
|
||||
img.cutPageNumber()
|
||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight)
|
||||
img.quantizeImage()
|
||||
if not options.notquantize:
|
||||
img.quantizeImage()
|
||||
|
||||
|
||||
def dirImgProcess(path):
|
||||
global options
|
||||
global splittedSomething
|
||||
splittedSomething = False
|
||||
global options, splitCount
|
||||
if options.righttoleft:
|
||||
facing = "right"
|
||||
else:
|
||||
facing = "left"
|
||||
|
||||
for (dirpath, dirnames, filenames) in os.walk(path):
|
||||
for afile in filenames:
|
||||
@@ -269,9 +280,11 @@ def dirImgProcess(path):
|
||||
else:
|
||||
print ".",
|
||||
img = image.ComicPage(os.path.join(dirpath, afile), options.profile)
|
||||
split = img.splitPage(dirpath, options.righttoleft, options.rotate)
|
||||
if options.nosplitrotate:
|
||||
split = None
|
||||
else:
|
||||
split = img.splitPage(dirpath, options.righttoleft, options.rotate)
|
||||
if split is not None:
|
||||
splittedSomething = True
|
||||
if options.verbose:
|
||||
print "Splitted " + afile
|
||||
if options.righttoleft:
|
||||
@@ -280,15 +293,27 @@ def dirImgProcess(path):
|
||||
else:
|
||||
toRight1 = True
|
||||
toRight2 = False
|
||||
if options.righttoleft:
|
||||
if facing == "left":
|
||||
splitCount += 1
|
||||
facing = "right"
|
||||
else:
|
||||
if facing == "right":
|
||||
splitCount += 1
|
||||
facing = "left"
|
||||
img0 = image.ComicPage(split[0], options.profile)
|
||||
applyImgOptimization(img0, True, toRight1)
|
||||
img0.saveToDir(dirpath)
|
||||
img0.saveToDir(dirpath, options.notquantize)
|
||||
img1 = image.ComicPage(split[1], options.profile)
|
||||
applyImgOptimization(img1, True, toRight2)
|
||||
img1.saveToDir(dirpath)
|
||||
img1.saveToDir(dirpath, options.notquantize)
|
||||
else:
|
||||
if facing == "right":
|
||||
facing = "left"
|
||||
else:
|
||||
facing = "right"
|
||||
applyImgOptimization(img)
|
||||
img.saveToDir(dirpath)
|
||||
img.saveToDir(dirpath, options.notquantize)
|
||||
|
||||
|
||||
def genEpubStruct(path):
|
||||
@@ -297,6 +322,34 @@ def genEpubStruct(path):
|
||||
chapterlist = []
|
||||
cover = None
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'page_styles.css'), 'w')
|
||||
f.writelines(["@page {\n",
|
||||
" margin-bottom: 0;\n",
|
||||
" margin-top: 0\n",
|
||||
"}\n"])
|
||||
f.close()
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'stylesheet.css'), 'w')
|
||||
f.writelines([".kcc {\n",
|
||||
" display: block;\n",
|
||||
" margin-bottom: 0;\n",
|
||||
" margin-left: 0;\n",
|
||||
" margin-right: 0;\n",
|
||||
" margin-top: 0;\n",
|
||||
" padding-bottom: 0;\n",
|
||||
" padding-left: 0;\n",
|
||||
" padding-right: 0;\n",
|
||||
" padding-top: 0;\n",
|
||||
" text-align: left\n",
|
||||
"}\n",
|
||||
".kcc1 {\n",
|
||||
" display: block;\n",
|
||||
" text-align: center\n",
|
||||
"}\n",
|
||||
".kcc2 {\n",
|
||||
" height: auto;\n",
|
||||
" width: auto\n",
|
||||
"}\n"])
|
||||
f.close()
|
||||
for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
chapter = False
|
||||
for afile in filenames:
|
||||
@@ -323,13 +376,12 @@ def genEpubStruct(path):
|
||||
alphanum_key = lambda key: [convert(c) for c in re.split('([0-9]+)', key)]
|
||||
filelist.sort(key=lambda name: (alphanum_key(name[0].lower()), alphanum_key(name[1].lower())))
|
||||
buildOPF(options.profile, path, options.title, filelist, cover, options.righttoleft)
|
||||
if (options.profile == 'K4' or options.profile == 'KHD') and splittedSomething:
|
||||
if (options.profile == 'K4' or options.profile == 'KHD') and splitCount > 0:
|
||||
filelist.append(buildBlankHTML(os.path.join(path, 'OEBPS', 'Text')))
|
||||
|
||||
|
||||
def getWorkFolder(afile):
|
||||
workdir = tempfile.mkdtemp()
|
||||
fname = os.path.splitext(afile)
|
||||
if os.path.isdir(afile):
|
||||
try:
|
||||
import shutil
|
||||
@@ -338,13 +390,18 @@ def getWorkFolder(afile):
|
||||
path = workdir
|
||||
except OSError:
|
||||
raise
|
||||
elif fname[1].lower() == '.pdf':
|
||||
elif afile.lower().endswith('.pdf'):
|
||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
||||
path = pdf.extract()
|
||||
else:
|
||||
cbx = cbxarchive.CBxArchive(afile)
|
||||
if cbx.isCbxFile():
|
||||
path = cbx.extract(workdir)
|
||||
try:
|
||||
path = cbx.extract(workdir)
|
||||
except OSError:
|
||||
print 'Unrar not found, please download from ' + \
|
||||
'http://www.rarlab.com/download.htm and put into your PATH.'
|
||||
sys.exit(21)
|
||||
else:
|
||||
raise TypeError
|
||||
move(path, path + "_temp")
|
||||
@@ -358,38 +415,44 @@ def Copyright():
|
||||
|
||||
|
||||
def Usage():
|
||||
print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images"
|
||||
print "Optimized for creating Mobipockets to be read into Kindle Paperwhite"
|
||||
print "Generates HTML, NCX and OPF for a Comic ebook from a bunch of images."
|
||||
print "Optimized for creating MOBI files to be read on Kindle Paperwhite."
|
||||
parser.print_help()
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
global parser, options, epub_path
|
||||
global parser, options, epub_path, splitCount
|
||||
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, KDXG 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]")
|
||||
help="Comic title [Default=filename]")
|
||||
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="'Manga style' (right-to-left reading and splitting) [default=False]")
|
||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output [default=False]")
|
||||
parser.add_option("--no-image-processing", action="store_false", dest="imgproc", default=True,
|
||||
help="Do not 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("--black-borders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders (instead of white ones) when not stretching and ratio "
|
||||
+ "is not like the device's one [default=False]")
|
||||
parser.add_option("--no-cut-page-numbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Do not try to cut page numbering on images [default=True]")
|
||||
help="Manga style (Right-to-left reading and splitting) [Default=False]")
|
||||
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
||||
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
|
||||
parser.add_option("--nodithering", action="store_true", dest="notquantize", default=False,
|
||||
help="Disable image quantization [Default=False]")
|
||||
parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||
parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
||||
help="Resize images smaller than device's resolution [Default=False]")
|
||||
parser.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
||||
help="Stretch images to device's resolution [Default=False]")
|
||||
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders instead of white ones when not stretching and ratio "
|
||||
+ "is not like the device's one [Default=False]")
|
||||
parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
||||
help="Rotate landscape pages instead of splitting them [default=False]")
|
||||
help="Rotate landscape pages instead of splitting them [Default=False]")
|
||||
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
||||
help="Disable splitting and rotation [Default=False]")
|
||||
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Do not try to cut page numbering on images [Default=True]")
|
||||
parser.add_option("-o", "--output", action="store", dest="output", default=None,
|
||||
help="Output directory or file for generated ePub")
|
||||
help="Output generated EPUB to specified directory or file")
|
||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output [Default=False]")
|
||||
options, args = parser.parse_args(argv)
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
@@ -397,10 +460,11 @@ def main(argv=None):
|
||||
path = getWorkFolder(args[0])
|
||||
if options.title == 'defaulttitle':
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
splitCount = 0
|
||||
if options.imgproc:
|
||||
print "Processing images..."
|
||||
dirImgProcess(path + "/OEBPS/Images/")
|
||||
print "Creating ePub structure..."
|
||||
print "\nCreating ePub structure..."
|
||||
genEpubStruct(path)
|
||||
# actually zip the ePub
|
||||
if options.output is not None:
|
||||
|
||||
244
kcc/gui.py
244
kcc/gui.py
@@ -28,9 +28,11 @@ import comic2ebook
|
||||
import kindlestrip
|
||||
from image import ProfileData
|
||||
from subprocess import call
|
||||
from subprocess import check_call
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import traceback
|
||||
|
||||
|
||||
class MainWindow:
|
||||
@@ -39,25 +41,22 @@ class MainWindow:
|
||||
self.filelist = []
|
||||
self.refresh_list()
|
||||
|
||||
def change_gamma(self):
|
||||
if self.aEntry['state'] == DISABLED:
|
||||
self.aEntry['state'] = NORMAL
|
||||
else:
|
||||
self.aEntry['state'] = DISABLED
|
||||
|
||||
def open_files(self):
|
||||
filetypes = [('all files', '.*'), ('Comic files', ('*.cbr', '*.cbz', '*.zip', '*.rar', '*.pdf'))]
|
||||
f = tkFileDialog.askopenfilenames(title="Choose a file...", filetypes=filetypes)
|
||||
filetypes = [('All files', '.*'), ('Comic files', ('*.cbr', '*.cbz', '*.zip', '*.rar', '*.pdf'))]
|
||||
f = tkFileDialog.askopenfilenames(title="Choose files", filetypes=filetypes)
|
||||
if not isinstance(f, tuple):
|
||||
try:
|
||||
import re
|
||||
f = re.findall('\{(.*?)\}', f)
|
||||
except:
|
||||
import tkMessageBox
|
||||
tkMessageBox.showerror(
|
||||
"Open file",
|
||||
"askopenfilename() returned other than a tuple and no regex module could be found"
|
||||
)
|
||||
sys.exit(1)
|
||||
f = self.master.tk.splitlist(f)
|
||||
self.filelist.extend(f)
|
||||
self.refresh_list()
|
||||
|
||||
def open_folder(self):
|
||||
f = tkFileDialog.askdirectory(title="Choose a folder...")
|
||||
f = tkFileDialog.askdirectory(title="Choose folder:")
|
||||
self.filelist.extend([f])
|
||||
self.refresh_list()
|
||||
|
||||
@@ -67,6 +66,13 @@ class MainWindow:
|
||||
for afile in self.filelist:
|
||||
self.filelocation.insert(END, afile)
|
||||
self.filelocation.config(state=DISABLED)
|
||||
try:
|
||||
if len(self.filelist) > 0:
|
||||
self.submit['state'] = NORMAL
|
||||
else:
|
||||
self.submit['state'] = DISABLED
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def initialize(self):
|
||||
self.filelocation = Listbox(self.master)
|
||||
@@ -74,118 +80,171 @@ class MainWindow:
|
||||
self.refresh_list()
|
||||
|
||||
self.clear_file = Button(self.master, text="Clear files", command=self.clear_files)
|
||||
self.clear_file.grid(row=4, column=0, rowspan=3)
|
||||
self.open_file = Button(self.master, text="Add files...", command=self.open_files)
|
||||
self.open_file.grid(row=4, column=1, rowspan=3)
|
||||
self.open_folder = Button(self.master, text="Add folder...", command=self.open_folder)
|
||||
self.open_folder.grid(row=4, column=2, rowspan=3)
|
||||
self.clear_file.grid(row=4, column=0, sticky=W + E + N + S)
|
||||
self.open_file = Button(self.master, text="Add files", command=self.open_files)
|
||||
self.open_file.grid(row=4, column=1, sticky=W + E + N + S)
|
||||
self.open_folder = Button(self.master, text="Add folder", command=self.open_folder)
|
||||
self.open_folder.grid(row=4, column=2, sticky=W + E + N + S)
|
||||
|
||||
self.profile = StringVar()
|
||||
profiles = sorted(ProfileData.ProfileLabels.iterkeys())
|
||||
self.profile.set(profiles[-1])
|
||||
w = apply(OptionMenu, (self.master, self.profile) + tuple(profiles))
|
||||
w.grid(row=1, column=3)
|
||||
w.grid(row=4, column=3, sticky=W + E + N + S)
|
||||
|
||||
self.options = {
|
||||
'epub_only': IntVar(None, 0),
|
||||
'image_preprocess': IntVar(None, 1),
|
||||
'rotate': IntVar(None, 0),
|
||||
'cut_page_numbers': IntVar(None, 1),
|
||||
'mangastyle': IntVar(None, 0),
|
||||
'image_upscale': IntVar(None, 0),
|
||||
'image_stretch': IntVar(None, 0),
|
||||
'black_borders': IntVar(None, 0)
|
||||
'Aepub_only': IntVar(None, 0),
|
||||
'Bmangastyle': IntVar(None, 0),
|
||||
'Cimage_preprocess': IntVar(None, 0),
|
||||
'Dnotquantize': IntVar(None, 0),
|
||||
'Eimage_gamma': DoubleVar(None, 0.0),
|
||||
'Fimage_upscale': IntVar(None, 0),
|
||||
'Gimage_stretch': IntVar(None, 0),
|
||||
'Hblack_borders': IntVar(None, 0),
|
||||
'Irotate': IntVar(None, 0),
|
||||
'Jnosplitrotate': IntVar(None, 0),
|
||||
'Kcut_page_numbers': IntVar(None, 0)
|
||||
}
|
||||
self.optionlabels = {
|
||||
'epub_only': "Generate ePub only (does not call 'kindlegen')",
|
||||
'image_preprocess': "Apply image optimizations",
|
||||
'rotate': "Rotate landscape images instead of splitting them",
|
||||
'cut_page_numbers': "Cut page numbers",
|
||||
'mangastyle': "Manga-style (right-to-left reading, applies to reading and splitting)",
|
||||
'image_upscale': "Allow image upscaling",
|
||||
'image_stretch': "Stretch images",
|
||||
'black_borders': "Use black borders"
|
||||
'Aepub_only': "Generate EPUB only",
|
||||
'Cimage_preprocess': "Disable image optimizations",
|
||||
'Dnotquantize': "Disable image quantization",
|
||||
'Jnosplitrotate': "Disable splitting and rotation",
|
||||
'Irotate': "Rotate images instead splitting them",
|
||||
'Kcut_page_numbers': "Disable page numbers cutting",
|
||||
'Bmangastyle': "Manga mode",
|
||||
'Eimage_gamma': "Custom gamma correction",
|
||||
'Fimage_upscale': "Allow image upscaling",
|
||||
'Gimage_stretch': "Stretch images",
|
||||
'Hblack_borders': "Use black borders"
|
||||
}
|
||||
for key in self.options:
|
||||
aCheckButton = Checkbutton(self.master, text=self.optionlabels[key], variable=self.options[key])
|
||||
aCheckButton.grid(column=3, sticky='w')
|
||||
self.progressbar = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate')
|
||||
self.optionsButtons = {}
|
||||
for key in sorted(self.options):
|
||||
if isinstance(self.options[key], IntVar) or isinstance(self.options[key], BooleanVar):
|
||||
self.optionsButtons[key] = Checkbutton(self.master, text=self.optionlabels[key],
|
||||
variable=self.options[key])
|
||||
self.optionsButtons[key].grid(columnspan=4, sticky=W + N + S)
|
||||
elif isinstance(self.options[key], DoubleVar):
|
||||
self.optionsButtons[key] = Checkbutton(self.master, text=self.optionlabels[key],
|
||||
command=self.change_gamma)
|
||||
self.optionsButtons[key].grid(columnspan=4, sticky=W + N + S)
|
||||
self.aEntry = Entry(self.master, textvariable=self.options[key])
|
||||
self.aEntry['state'] = DISABLED
|
||||
self.aEntry.grid(column=3, row=(self.master.grid_size()[1] - 1), sticky=W + N + S)
|
||||
|
||||
self.submit = Button(self.master, text="Execute!", command=self.start_conversion, fg="red")
|
||||
self.submit.grid(column=3)
|
||||
self.progressbar.grid(column=0, columnspan=4, sticky=W + E + N + S)
|
||||
self.submit = Button(self.master, text="CONVERT", command=self.start_conversion, fg="red", state=DISABLED)
|
||||
self.submit.grid(columnspan=4, sticky=W + E + N + S)
|
||||
aLabel = Label(self.master, text="File progress:", anchor=W, justify=LEFT)
|
||||
aLabel.grid(column=0, sticky=E)
|
||||
self.progress_file = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate', maximum=4)
|
||||
self.progress_file.grid(column=1, columnspan=3, row=(self.master.grid_size()[1] - 1), sticky=W + E + N + S)
|
||||
aLabel = Label(self.master, text="Overall progress:", anchor=W, justify=LEFT)
|
||||
aLabel.grid(column=0, sticky=E)
|
||||
self.progress_overall = ttk.Progressbar(orient=HORIZONTAL, length=200, mode='determinate')
|
||||
self.progress_overall.grid(column=1, columnspan=3, row=(self.master.grid_size()[1] - 1), sticky=W + E + N + S)
|
||||
|
||||
self.notelabel = Label(self.master,
|
||||
text="GUI can seem frozen while converting, kindly wait until some message appears!")
|
||||
self.notelabel.grid(column=0, columnspan=4, sticky=W + E + N + S)
|
||||
retcode = call("kindlegen", shell=True)
|
||||
if retcode == 1:
|
||||
self.optionsButtons['Aepub_only'].select()
|
||||
self.optionsButtons['Aepub_only']['state'] = DISABLED
|
||||
|
||||
def start_conversion(self):
|
||||
self.progressbar.start()
|
||||
self.submit['state'] = DISABLED
|
||||
self.master.update()
|
||||
self.convert()
|
||||
self.progressbar.stop()
|
||||
self.submit['state'] = NORMAL
|
||||
self.master.update()
|
||||
|
||||
def convert(self):
|
||||
if len(self.filelist) < 1:
|
||||
tkMessageBox.showwarning('No file selected', "You should really select some files to convert...")
|
||||
tkMessageBox.showwarning('No files selected!', "Please choose files to convert.")
|
||||
return
|
||||
profilekey = ProfileData.ProfileLabels[self.profile.get()]
|
||||
argv = ["-p", profilekey]
|
||||
if self.options['image_preprocess'].get() == 0:
|
||||
argv.append("--no-image-processing")
|
||||
if self.options['rotate'].get() == 1:
|
||||
if self.options['Eimage_gamma'].get() != 0.0:
|
||||
argv.append("--gamma")
|
||||
argv.append(self.options['Eimage_gamma'].get())
|
||||
if self.options['Cimage_preprocess'].get() == 1:
|
||||
argv.append("--noprocessing")
|
||||
if self.options['Dnotquantize'].get() == 1:
|
||||
argv.append("--nodithering")
|
||||
if self.options['Jnosplitrotate'].get() == 1:
|
||||
argv.append("--nosplitrotate")
|
||||
if self.options['Irotate'].get() == 1:
|
||||
argv.append("--rotate")
|
||||
if self.options['cut_page_numbers'].get() == 0:
|
||||
argv.append("--no-cut-page-numbers")
|
||||
if self.options['mangastyle'].get() == 1:
|
||||
if self.options['Kcut_page_numbers'].get() == 1:
|
||||
argv.append("--nocutpagenumbers")
|
||||
if self.options['Bmangastyle'].get() == 1:
|
||||
argv.append("-m")
|
||||
if self.options['image_upscale'].get() == 1:
|
||||
argv.append("--upscale-images")
|
||||
if self.options['image_stretch'].get() == 1:
|
||||
argv.append("--stretch-images")
|
||||
if self.options['black_borders'].get() == 1:
|
||||
argv.append("--black-borders")
|
||||
if self.options['Fimage_upscale'].get() == 1:
|
||||
argv.append("--upscale")
|
||||
if self.options['Gimage_stretch'].get() == 1:
|
||||
argv.append("--stretch")
|
||||
if self.options['Hblack_borders'].get() == 1:
|
||||
argv.append("--blackborders")
|
||||
errors = False
|
||||
left_files = len(self.filelist)
|
||||
filenum = 0
|
||||
self.progress_overall['value'] = 0
|
||||
self.progress_overall['maximum'] = left_files
|
||||
for entry in self.filelist:
|
||||
self.progress_overall['value'] = filenum
|
||||
self.progress_file['value'] = 1
|
||||
self.master.update()
|
||||
filenum += 1
|
||||
subargv = list(argv)
|
||||
try:
|
||||
subargv.append(entry)
|
||||
epub_path = comic2ebook.main(subargv)
|
||||
except Exception, err:
|
||||
tkMessageBox.showerror('Error comic2ebook', "Error on file %s:\n%s" % (subargv[-1], str(err)))
|
||||
errors = True
|
||||
continue
|
||||
if self.options['epub_only'] == 1:
|
||||
continue
|
||||
try:
|
||||
retcode = call("kindlegen \"" + epub_path + "\"", shell=True)
|
||||
if retcode < 0:
|
||||
print >>sys.stderr, "Child was terminated by signal", -retcode
|
||||
else:
|
||||
print >>sys.stderr, "Child returned", retcode
|
||||
except OSError as e:
|
||||
tkMessageBox.showerror('Error kindlegen', "Error on file %s:\n%s" % (epub_path, e))
|
||||
errors = True
|
||||
continue
|
||||
mobifile = epub_path.replace('.epub', '.mobi')
|
||||
try:
|
||||
shutil.move(mobifile, mobifile + '_tostrip')
|
||||
kindlestrip.main((mobifile + '_tostrip', mobifile))
|
||||
os.remove(mobifile + '_tostrip')
|
||||
except Exception, err:
|
||||
tkMessageBox.showerror('Error', "Error on file %s:\n%s" % (mobifile, str(err)))
|
||||
self.progress_file['value'] = 2
|
||||
self.master.update()
|
||||
except Exception as err:
|
||||
type_, value_, traceback_ = sys.exc_info()
|
||||
tkMessageBox.showerror('KCC Error', "Error on file %s:\n%s\nTraceback:\n%s" %
|
||||
(subargv[-1], str(err), traceback.format_tb(traceback_)))
|
||||
errors = True
|
||||
continue
|
||||
if self.options['Aepub_only'].get() == 0:
|
||||
try:
|
||||
if os.path.getsize(epub_path) > 335544320:
|
||||
# do not call kindlegen if source is bigger than 320MB
|
||||
tkMessageBox.showwarning('KindleGen Warning',
|
||||
"ePub file %s is bigger than 320MB, not suitable for kindlegen" %
|
||||
epub_path)
|
||||
continue
|
||||
retcode = call("kindlegen \"" + epub_path + "\"", shell=True)
|
||||
if retcode < 0:
|
||||
print >>sys.stderr, "Child was terminated by signal", -retcode
|
||||
else:
|
||||
print >>sys.stderr, "Child returned", retcode
|
||||
self.progress_file['value'] = 3
|
||||
self.master.update()
|
||||
except OSError as e:
|
||||
tkMessageBox.showerror('KindleGen Error', "Error on file %s:\n%s" % (epub_path, e))
|
||||
errors = True
|
||||
continue
|
||||
mobifile = epub_path.replace('.epub', '.mobi')
|
||||
try:
|
||||
shutil.move(mobifile, mobifile + '_tostrip')
|
||||
kindlestrip.main((mobifile + '_tostrip', mobifile))
|
||||
os.remove(mobifile + '_tostrip')
|
||||
self.progress_file['value'] = 4
|
||||
self.master.update()
|
||||
except Exception, err:
|
||||
tkMessageBox.showerror('KindleStrip Error', "Error on file %s:\n%s" % (mobifile, str(err)))
|
||||
errors = True
|
||||
continue
|
||||
else:
|
||||
self.progress_file['value'] = 4
|
||||
self.master.update()
|
||||
if errors:
|
||||
tkMessageBox.showinfo(
|
||||
"Done",
|
||||
"Conversion finished (some errors have been reported)"
|
||||
)
|
||||
tkMessageBox.showwarning("Done", "Conversion completed with errors.")
|
||||
else:
|
||||
tkMessageBox.showinfo(
|
||||
"Done",
|
||||
"Conversion successfully done!"
|
||||
)
|
||||
tkMessageBox.showinfo("Done", "Conversion successful!")
|
||||
# reset progressbars
|
||||
self.progress_overall['value'] = 0
|
||||
self.progress_file['value'] = 0
|
||||
self.master.update()
|
||||
|
||||
def remove_readonly(self, fn, path):
|
||||
if fn is os.rmdir:
|
||||
@@ -195,8 +254,7 @@ class MainWindow:
|
||||
os.chmod(path, stat.S_IWRITE)
|
||||
os.remove(path)
|
||||
|
||||
def __init__(self, master, title):
|
||||
def __init__(self, master):
|
||||
self.filelist = []
|
||||
self.master = master
|
||||
self.master.title(title)
|
||||
self.initialize()
|
||||
|
||||
33
kcc/image.py
33
kcc/image.py
@@ -77,13 +77,13 @@ class ProfileData:
|
||||
]
|
||||
|
||||
Profiles = {
|
||||
'K1': ("Kindle", (600, 800), Palette4),
|
||||
'K2': ("Kindle 2", (600, 800), Palette15),
|
||||
'K3': ("Kindle 3/Keyboard", (600, 800), Palette16),
|
||||
'K4': ("Kindle 4/NT/Touch", (600, 800), Palette16),
|
||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16),
|
||||
'KDX': ("Kindle DX", (824, 1200), Palette15),
|
||||
'KDXG': ("Kindle DXG", (824, 1200), Palette16)
|
||||
'K1': ("Kindle", (600, 800), Palette4, 1.8),
|
||||
'K2': ("Kindle 2", (600, 800), Palette15, 1.8),
|
||||
'K3': ("Kindle 3/Keyboard", (600, 800), Palette16, 1.8),
|
||||
'K4': ("Kindle 4/NT/Touch", (600, 800), Palette16, 1.8),
|
||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8),
|
||||
'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8),
|
||||
'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8)
|
||||
}
|
||||
|
||||
ProfileLabels = {
|
||||
@@ -101,7 +101,7 @@ class ComicPage:
|
||||
def __init__(self, source, device):
|
||||
try:
|
||||
self.profile = device
|
||||
self.profile_label, self.size, self.palette = ProfileData.Profiles[device]
|
||||
self.profile_label, self.size, self.palette, self.gamma = ProfileData.Profiles[device]
|
||||
except KeyError:
|
||||
raise RuntimeError('Unexpected output device %s' % device)
|
||||
try:
|
||||
@@ -111,16 +111,25 @@ class ComicPage:
|
||||
raise RuntimeError('Cannot read image file %s' % source)
|
||||
self.image = self.image.convert('RGB')
|
||||
|
||||
def saveToDir(self, targetdir):
|
||||
def saveToDir(self, targetdir, notquantize):
|
||||
filename = os.path.basename(self.origFileName)
|
||||
try:
|
||||
self.image = self.image.convert('L') # convert to grayscale
|
||||
self.image.save(os.path.join(targetdir, filename), "JPEG")
|
||||
os.remove(os.path.join(targetdir, filename))
|
||||
if notquantize:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".jpg"), "JPEG")
|
||||
else:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".png"), "PNG")
|
||||
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 optimizeImage(self, gamma):
|
||||
if gamma < 0.1:
|
||||
gamma = self.gamma
|
||||
if gamma == 1.0:
|
||||
self.image = ImageOps.autocontrast(self.image)
|
||||
else:
|
||||
self.image = ImageOps.autocontrast(Image.eval(self.image, lambda a: 255 * (a / 255.) ** gamma))
|
||||
|
||||
def quantizeImage(self):
|
||||
colors = len(self.palette) / 3
|
||||
|
||||
@@ -70,3 +70,4 @@ class PdfJpgExtract:
|
||||
|
||||
njpg += 1
|
||||
i = iend
|
||||
return self.path
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleAllowMixedLocalizations</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>*</string>
|
||||
</array>
|
||||
<key>CFBundleTypeOSTypes</key>
|
||||
<array>
|
||||
<string>****</string>
|
||||
</array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>KindleComicConverter 2.0, Written 2012 by Ciro Mattia Gonano</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>droplet</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.github.ciromattia.kcc</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>KindleComicConverter 1.20</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>dplt</string>
|
||||
<key>LSMinimumSystemVersionByArchitecture</key>
|
||||
<dict>
|
||||
<key>x86_64</key>
|
||||
<string>10.6</string>
|
||||
</dict>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>WindowState</key>
|
||||
<dict>
|
||||
<key>dividerCollapsed</key>
|
||||
<true/>
|
||||
<key>eventLogLevel</key>
|
||||
<integer>-1</integer>
|
||||
<key>name</key>
|
||||
<string>ScriptWindowState</string>
|
||||
<key>positionOfDivider</key>
|
||||
<real>568</real>
|
||||
<key>savedFrame</key>
|
||||
<string>144 338 889 690 0 0 1680 1028 </string>
|
||||
<key>selectedTabView</key>
|
||||
<string>event log</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
Binary file not shown.
@@ -1,22 +0,0 @@
|
||||
{\rtf1\ansi\ansicpg1252\cocoartf1187\cocoasubrtf340
|
||||
{\fonttbl\f0\fnil\fcharset0 Verdana;}
|
||||
{\colortbl;\red255\green255\blue255;\red76\green78\blue78;}
|
||||
\pard\tx576\tx1152\tx1728\tx2304\tx2880\tx3456\tx4032\tx4608\tx5184\tx5760\tx6337\tx6913\tx7489\tx8065\tx8641\tx9217\tx9793\tx10369\tx10945\tx11521\tx12097\tx12674\tx13250\tx13826\tx14402\tx14978\tx15554\tx16130\tx16706\tx17282\tx17858\tx18435\tx19011\tx19587\tx20163\tx20739\tx21315\tx21891\tx22467\tx23043\tx23619\tx24195\tx24772\tx25348\tx25924\tx26500\tx27076\tx27652\tx28228\tx28804\tx29380\tx29956\tx30532\tx31109\tx31685\tx32261\tx32837\tx33413\tx33989\tx34565\tx35141\tx35717\tx36293\tx36870\tx37446\tx38022\tx38598\tx39174\tx39750\tx40326\tx40902\tx41478\tx42054\tx42630\tx43207\tx43783\tx44359\tx44935\tx45511\tx46087\tx46663\tx47239\tx47815\tx48391\tx48967\tx49544\tx50120\tx50696\tx51272\tx51848\tx52424\tx53000\tx53576\tx54152\tx54728\tx55305\tx55881\tx56457\tx57033\tx57609\li785\fi-786\pardirnatural
|
||||
|
||||
\f0\fs24 \cf2 \CocoaLigature0 Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>\
|
||||
\
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.\
|
||||
\
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\
|
||||
\
|
||||
This script heavily relies on KindleStrip (C) by Paul Durrant and released in public domain (http://www.mobileread.com/forums/showthread.php?t=96903)\
|
||||
Also, you need to have kindlegen v2.7 (with KF8 support) which is downloadable from Amazon website.\
|
||||
\
|
||||
Changelog:\
|
||||
1.0: first release\
|
||||
1.10: add CBZ/CBR support to comic2ebook.py\
|
||||
1.11: add CBZ/CBR support to KindleComicConverter\
|
||||
1.2: added image page splitting and optimizations\
|
||||
\
|
||||
Todo:\
|
||||
- bundle a script to manipulate images (to get rid of Mangle/E-nki/whatsoever)}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 362 B |
47
setup.py
47
setup.py
@@ -1,33 +1,36 @@
|
||||
"""
|
||||
py2app/py2exe build script for MyApplication.
|
||||
py2app/cx_Freeze build script for KCC.
|
||||
|
||||
Will automatically ensure that all build prerequisites are available
|
||||
via ez_setup
|
||||
Will automatically ensure that all build prerequisites are available via ez_setup
|
||||
|
||||
Usage (Mac OS X):
|
||||
python setup.py py2app
|
||||
|
||||
Usage (Windows):
|
||||
python setup.py py2exe
|
||||
python setup.py build
|
||||
"""
|
||||
import ez_setup
|
||||
ez_setup.use_setuptools()
|
||||
from ez_setup import use_setuptools
|
||||
use_setuptools()
|
||||
|
||||
import sys
|
||||
from setuptools import setup
|
||||
|
||||
NAME = 'KindleComicConverter'
|
||||
VERSION = "2.6"
|
||||
mainscript = 'kcc.py'
|
||||
NAME = "KindleComicConverter"
|
||||
VERSION = "2.7"
|
||||
MAIN = "kcc.py"
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
includefiles = ['README.md', 'MANIFEST.in', 'LICENSE.txt', 'comic2ebook.ico', 'comic2ebook.icns']
|
||||
includes = []
|
||||
excludes = []
|
||||
|
||||
if sys.platform == "darwin":
|
||||
from setuptools import setup
|
||||
extra_options = dict(
|
||||
setup_requires=['py2app'],
|
||||
app=[mainscript],
|
||||
app=[MAIN],
|
||||
options=dict(
|
||||
py2app=dict(
|
||||
argv_emulation=True,
|
||||
iconfile='resources/comic2ebook.icns',
|
||||
iconfile='comic2ebook.icns',
|
||||
plist=dict(
|
||||
CFBundleName=NAME,
|
||||
CFBundleShortVersionString=VERSION,
|
||||
@@ -43,13 +46,17 @@ elif sys.platform == 'win32':
|
||||
from cx_Freeze import setup, Executable
|
||||
base = "Win32GUI"
|
||||
extra_options = dict(
|
||||
executables=[Executable("kcc.py", base=base)]
|
||||
)
|
||||
options={"build_exe": {"include_files": includefiles, 'excludes': excludes, 'compressed': True}},
|
||||
executables=[Executable(MAIN,
|
||||
base=base,
|
||||
icon="comic2ebook.ico",
|
||||
copyDependentFiles=True,
|
||||
appendScriptToExe=True,
|
||||
appendScriptToLibrary=False,
|
||||
compress=True)])
|
||||
else:
|
||||
extra_options = dict(
|
||||
# Normally unix-like platforms will use "setup.py install"
|
||||
# and install the main script as such
|
||||
scripts=[mainscript],
|
||||
scripts=[MAIN],
|
||||
)
|
||||
|
||||
setup(
|
||||
@@ -71,12 +78,10 @@ setup(
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Topic :: Multimedia :: Graphics :: Graphics Conversion',
|
||||
'Topic :: Utilities'
|
||||
],
|
||||
packages=['kcc'],
|
||||
# make sure to add custom_fixers to the MANIFEST.in
|
||||
include_package_data=True,
|
||||
**extra_options
|
||||
)
|
||||
|
||||
22
setup_console.py
Normal file
22
setup_console.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""
|
||||
cx_freeze build script for Windows KCC No-GUI release.
|
||||
|
||||
Usage (Windows):
|
||||
python setup_console.py build
|
||||
"""
|
||||
import sys
|
||||
from cx_Freeze import setup, Executable
|
||||
sys.path.insert(0, 'kcc')
|
||||
|
||||
setup(
|
||||
name = "KindleComicConverter",
|
||||
version = "2.7",
|
||||
author = "Ciro Mattia Gonano",
|
||||
author_email = "ciromattia@gmail.com",
|
||||
description = "A tool to convert comics (CBR/CBZ/PDFs/image folders) to MOBI.",
|
||||
license= "ISC License (ISCL)",
|
||||
keywords= "kindle comic mobipocket mobi cbz cbr manga",
|
||||
url = "http://github.com/ciromattia/kcc",
|
||||
executables = [Executable("kcc/comic2ebook.py", compress=True, copyDependentFiles=True, appendScriptToExe=True, appendScriptToLibrary=False),
|
||||
Executable("kcc/kindlestrip.py", compress=True, copyDependentFiles=True, appendScriptToExe=True, appendScriptToLibrary=False)]
|
||||
)
|
||||
86
setup_cx.py
86
setup_cx.py
@@ -1,86 +0,0 @@
|
||||
"""
|
||||
py2app/py2exe build script for MyApplication.
|
||||
|
||||
Will automatically ensure that all build prerequisites are available
|
||||
via ez_setup
|
||||
|
||||
Usage (Mac OS X):
|
||||
python setup_cx.py bdist_dmg
|
||||
|
||||
Usage (Windows):
|
||||
python setup_cx.py bdist_msi
|
||||
"""
|
||||
import sys
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
NAME='KindleComicConverter'
|
||||
VERSION="2.4"
|
||||
mainscript = 'kcc.py'
|
||||
|
||||
base = None
|
||||
|
||||
if sys.platform == 'darwin':
|
||||
extra_options = dict(
|
||||
bundle-iconfile='resources/comic2ebook.icns',
|
||||
volume-label=NAME + " " + VERSION
|
||||
app=[mainscript],
|
||||
options=dict(
|
||||
py2app=dict(
|
||||
argv_emulation=True,
|
||||
iconfile='resources/comic2ebook.icns',
|
||||
plist=dict(
|
||||
CFBundleName = NAME,
|
||||
CFBundleShortVersionString = VERSION,
|
||||
CFBundleGetInfoString = NAME + " " + VERSION + ", written 2012-2013 by Ciro Mattia Gonano",
|
||||
CFBundleExecutable = NAME,
|
||||
CFBundleIdentifier = 'com.github.ciromattia.kcc',
|
||||
CFBundleSignature = 'dplt'
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
elif sys.platform == 'win32':
|
||||
base = "Win32GUI"
|
||||
extra_options = dict(
|
||||
upgrade-code = 'KindleComicConverter',
|
||||
executables = [Executable("kcc.py", base=base)]
|
||||
)
|
||||
else:
|
||||
extra_options = dict(
|
||||
# Normally unix-like platforms will use "setup.py install"
|
||||
# and install the main script as such
|
||||
scripts=[mainscript],
|
||||
)
|
||||
|
||||
options = dict(
|
||||
|
||||
)
|
||||
|
||||
setup(
|
||||
name=NAME,
|
||||
version=VERSION,
|
||||
author="Ciro Mattia Gonano",
|
||||
author_email="ciromattia@gmail.com",
|
||||
description=("A tool to convert comics (CBR/CBZ/PDFs/image folders) to Mobipocket."),
|
||||
license = "ISC License (ISCL)",
|
||||
keywords = "kindle comic mobipocket mobi cbz cbr manga",
|
||||
url = "http://github.com/ciromattia/kcc",
|
||||
classifiers=[
|
||||
'Development Status :: 4 - Beta'
|
||||
'License :: OSI Approved :: ISC License (ISCL)',
|
||||
'Environment :: Console',
|
||||
'Environment :: MacOS X',
|
||||
'Environment :: Win32 (MS Windows)',
|
||||
'Environment :: X11 Applications',
|
||||
'Intended Audience :: End Users/Desktop',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Topic :: Multimedia :: Graphics :: Graphics Conversion',
|
||||
'Topic :: Utilities'
|
||||
],
|
||||
# make sure to add custom_fixers to the MANIFEST.in
|
||||
include_package_data=True,
|
||||
executables = [Executable("kcc.py", base=base)]),
|
||||
**extra_options
|
||||
)
|
||||
Reference in New Issue
Block a user