mirror of
https://github.com/ciromattia/kcc
synced 2026-04-15 21:48:44 +00:00
Compare commits
114 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
148211a5c7 | ||
|
|
23e07f47f0 | ||
|
|
724156c554 | ||
|
|
b972e4c746 | ||
|
|
f0afa1fff2 | ||
|
|
a36c05f0c5 | ||
|
|
4f3a66b4eb | ||
|
|
6369c7ea44 | ||
|
|
f1b8aff8d4 | ||
|
|
be270aa797 | ||
|
|
f33d355024 | ||
|
|
6f913b026e | ||
|
|
220b4e0954 | ||
|
|
bac4a4fd86 | ||
|
|
d923299230 | ||
|
|
a79ebcec86 | ||
|
|
69bbee7648 | ||
|
|
57d7729d05 | ||
|
|
25a1dcb72c | ||
|
|
cff9b73b80 | ||
|
|
23336de5b5 | ||
|
|
d346ca0466 | ||
|
|
22fadf078e | ||
|
|
74acf85683 | ||
|
|
713de03d1d | ||
|
|
5817f52110 | ||
|
|
dfc03aee38 | ||
|
|
6bc8038068 | ||
|
|
eb7d56c1b9 | ||
|
|
f5b515ef79 | ||
|
|
943431346d | ||
|
|
a597173b71 | ||
|
|
c484cc8fff | ||
|
|
f195a4ccbf | ||
|
|
f55bb6dce6 | ||
|
|
e7f49f8330 | ||
|
|
cec9356fc2 | ||
|
|
6d704b382d | ||
|
|
1c5f67111f | ||
|
|
660df4b370 | ||
|
|
19dc5cc6f2 | ||
|
|
67baf90d04 | ||
|
|
07bf41ea6c | ||
|
|
c2cfc9b8db | ||
|
|
7a7a2bc10b | ||
|
|
e3127ed516 | ||
|
|
93a98db43e | ||
|
|
750b55649a | ||
|
|
ca5854a8bd | ||
|
|
712f728a5e | ||
|
|
eb7766a5e5 | ||
|
|
0cabbfde96 | ||
|
|
c5983276e5 | ||
|
|
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
|
||||
|
||||
139
README.md
139
README.md
@@ -1,38 +1,38 @@
|
||||
# KCC
|
||||
|
||||
`KCC` (a.k.a. `KindleComicConverter`) is a Python app to convert comic files or folders to ePub or Panel View Mobipocket.
|
||||
It was initally developed for Kindle but as of version 2.2 it outputs valid ePub 2.0 so _**despite its name, KCC is
|
||||
`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 since v2.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
|
||||
|
||||
It can also optionally optimize images by applying a number of transformations.
|
||||
|
||||
### A word of warning
|
||||
**KCC** _is not_ [Amazon's Kindle Comic Creator](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1001103761) nor is in any way endorsed by Amazon.
|
||||
Amazon's tool is for comic _publishers_ and involves a lot of manual effort, while **KCC** is for comic _readers_.
|
||||
If you want to read some comments over *Amazon's kc2* you can take a look at [this](http://www.mobileread.com/forums/showthread.php?t=207461&page=7#96) and [that](http://www.mobileread.com/forums/showthread.php?t=211047) threads on Mobileread.
|
||||
_kc2_ in no way is a replacement for **KCC** so you can be quite confident we'll going to carry on developing our little monster ;)
|
||||
|
||||
## 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.9.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_osx_2.9.zip)
|
||||
- Win64: [https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.9.zip](https://dl.dropbox.com/u/16806101/KindleComicConverter_win-amd64_2.9.zip)
|
||||
- Win32: [http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.9.zip](http://pawelj.vulturis.eu/Shared/KindleComicConverter_win-x86_2.9.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, GIF, TIFF, BMP
|
||||
- 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 +47,31 @@ 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, K4NT, K4T, 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]
|
||||
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
--nopanelviewhq Disable high quality Panel View [Default=False]
|
||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [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 file (EPUB or CBZ) 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 +85,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,12 +108,35 @@ 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
|
||||
- 2.8: updated rarfile library
|
||||
Panel View support + HQ support (#36) - new option: --nopanelviewhq
|
||||
Split profiles for K4NT and K4T
|
||||
Rewrite of Landscape Mode support (huge readability improvement for KPW)
|
||||
Upscale use now BILINEAR method
|
||||
Added generic CSS file
|
||||
Optimized archive extraction for zip/rar files (#40)
|
||||
- 2.9: Added support for generating a plain CBZ (skipping all the EPUB/Mobi generation) (#45)
|
||||
Prevent output file overwriting the source one: if a duplicate name is detected, append _kcc to the name
|
||||
Rarfile library updated to 2.6
|
||||
Added GIF, TIFF and BMP to supported formats (#42)
|
||||
Filenames slugifications (#28, #31, #9, #8)
|
||||
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
Copyright (c) 2012-2013 Ciro Mattia Gonano with further contributions by Paweł Jastrzębski.
|
||||
KCC is released under ISC LICENSE; see LICENSE.txt for further details.
|
||||
KCC is released under ISC LICENSE; see LICENSE.txt for further details.
|
||||
|
||||
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.9'
|
||||
__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,56 +19,58 @@ __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)
|
||||
filelist = []
|
||||
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))
|
||||
except:
|
||||
pass # the dir exists so we are going to extract the images only.
|
||||
else:
|
||||
cbzFile.extract(f, targetdir)
|
||||
filelist.append(f)
|
||||
cbzFile.extractall(targetdir, filelist)
|
||||
|
||||
def extractCBR(self, targetdir):
|
||||
try:
|
||||
import rarfile
|
||||
except ImportError:
|
||||
self.cbrFile = None
|
||||
return
|
||||
cbrFile = rarfile.RarFile(self.origFileName)
|
||||
filelist = []
|
||||
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)
|
||||
filelist.append(f)
|
||||
cbrFile.extractall(targetdir, filelist)
|
||||
|
||||
def extract(self, targetdir):
|
||||
if '.cbr' == self.filename[1].lower() or '.rar' == self.filename[1].lower():
|
||||
print "\n" + targetdir + "\n"
|
||||
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])):
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
#
|
||||
@@ -16,7 +17,7 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '2.6'
|
||||
__version__ = '2.9'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -60,23 +61,49 @@ 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),
|
||||
"style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
|
||||
"</head>\n",
|
||||
"<body>\n",
|
||||
"<div class=\"fs\">\n",
|
||||
"<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\" class=\"singlePage\"/></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",
|
||||
#"</div>\n",
|
||||
#"<div id=\"", filename[0], "-1-magTargetParent\" class=\"target-mag-parent\">\n",
|
||||
#"<div class=\"target-mag-lb\">\n",
|
||||
#"</div>\n",
|
||||
#"<div id=\"", filename[0], "-1-magTarget\" class=\"target-mag\">\n",
|
||||
#"<img src=\"../" * backref, "Images/", postfix, imgfile, "\" alt=\"", imgfile, "\"/>\n",
|
||||
#"</div></div>\n",
|
||||
"</body>\n",
|
||||
"</html>"
|
||||
imgfile, "\" class=\"singlePage\"/></div>\n"
|
||||
])
|
||||
if options.panelview:
|
||||
if options.righttoleft:
|
||||
f.writelines(["<div id=\"BoxTL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxTL-Panel-Parent\", \"ordinal\":2}'></a></div>\n",
|
||||
"<div id=\"BoxTR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxTR-Panel-Parent\", \"ordinal\":1}'></a></div>\n",
|
||||
"<div id=\"BoxBL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxBL-Panel-Parent\", \"ordinal\":4}'></a></div>\n",
|
||||
"<div id=\"BoxBR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxBR-Panel-Parent\", \"ordinal\":3}'></a></div>\n"
|
||||
])
|
||||
else:
|
||||
f.writelines(["<div id=\"BoxTL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxTL-Panel-Parent\", \"ordinal\":1}'></a></div>\n",
|
||||
"<div id=\"BoxTR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxTR-Panel-Parent\", \"ordinal\":2}'></a></div>\n",
|
||||
"<div id=\"BoxBL\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxBL-Panel-Parent\", \"ordinal\":3}'></a></div>\n",
|
||||
"<div id=\"BoxBR\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify='{\"targetId\":\"",
|
||||
"BoxBR-Panel-Parent\", \"ordinal\":4}'></a></div>\n"
|
||||
])
|
||||
f.writelines(["<div id=\"BoxTL-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxTL-Panel\" class=\"",
|
||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\"/></div></div>\n",
|
||||
"<div id=\"BoxTR-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxTR-Panel\" class=\"",
|
||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\"/></div></div>\n",
|
||||
"<div id=\"BoxBL-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxBL-Panel\" class=\"",
|
||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\"/></div></div>\n",
|
||||
"<div id=\"BoxBR-Panel-Parent\" class=\"target-mag-parent\"><div id=\"BoxBR-Panel\" class=\"",
|
||||
"target-mag\"><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"",
|
||||
imgfile, "\"/></div></div>\n"
|
||||
])
|
||||
f.writelines(["</div>\n</body>\n</html>"])
|
||||
f.close()
|
||||
return path, imgfile
|
||||
|
||||
@@ -102,7 +129,7 @@ def buildNCX(dstdir, title, chapters):
|
||||
f = open(ncxfile, "w")
|
||||
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
|
||||
"<!DOCTYPE ncx PUBLIC \"-//NISO//DTD ncx 2005-1//EN\" ",
|
||||
"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n",
|
||||
"\"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\">\n",
|
||||
"<ncx version=\"2005-1\" xml:lang=\"en-US\" xmlns=\"http://www.daisy.org/z3986/2005/ncx/\">\n",
|
||||
"<head>\n",
|
||||
"<meta name=\"dtb:uid\" content=\"015ffaec-9340-42f8-b163-a0c5ab7d0611\"/>\n",
|
||||
@@ -117,19 +144,19 @@ 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()
|
||||
return
|
||||
|
||||
|
||||
def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
|
||||
def buildOPF(profile, dstdir, title, filelist, cover=None):
|
||||
opffile = os.path.join(dstdir, 'OEBPS', 'content.opf')
|
||||
# read the first file resolution
|
||||
profilelabel, deviceres, palette = image.ProfileData.Profiles[profile]
|
||||
profilelabel, deviceres, palette, gamma, panelviewsize = image.ProfileData.Profiles[profile]
|
||||
imgres = str(deviceres[0]) + "x" + str(deviceres[1])
|
||||
if righttoleft:
|
||||
if options.righttoleft:
|
||||
writingmode = "horizontal-rl"
|
||||
facing = "right"
|
||||
facing1 = "right"
|
||||
@@ -155,16 +182,19 @@ def buildOPF(profile, dstdir, title, filelist, cover=None, righttoleft=False):
|
||||
"<meta name=\"book-type\" content=\"comic\"/>\n",
|
||||
"<meta name=\"zero-gutter\" content=\"true\"/>\n",
|
||||
"<meta name=\"zero-margin\" content=\"true\"/>\n",
|
||||
"<meta name=\"fixed-layout\" content=\"true\"/>\n",
|
||||
"<meta name=\"orientation-lock\" content=\"none\"/>\n",
|
||||
"<meta name=\"original-resolution\" content=\"", imgres, "\"/>\n",
|
||||
"<meta name=\"fixed-layout\" content=\"true\"/>\n"
|
||||
])
|
||||
if options.landscapemode:
|
||||
f.writelines(["<meta name=\"rendition:orientation\" content=\"auto\"/>\n",
|
||||
"<meta name=\"orientation-lock\" content=\"none\"/>\n"])
|
||||
else:
|
||||
f.writelines(["<meta name=\"rendition:orientation\" content=\"portrait\"/>\n",
|
||||
"<meta name=\"orientation-lock\" content=\"portrait\"/>\n"])
|
||||
f.writelines(["<meta name=\"original-resolution\" content=\"", imgres, "\"/>\n",
|
||||
"<meta name=\"primary-writing-mode\" content=\"", writingmode, "\"/>\n",
|
||||
"<meta name=\"rendition:layout\" content=\"pre-paginated\"/>\n",
|
||||
"<meta name=\"rendition:orientation\" content=\"auto\"/>\n",
|
||||
"</metadata>\n<manifest>\n<item id=\"ncx\" href=\"toc.ncx\" ",
|
||||
"media-type=\"application/x-dtbncx+xml\"/>\n"
|
||||
])
|
||||
# set cover
|
||||
"media-type=\"application/x-dtbncx+xml\"/>\n"])
|
||||
if cover is not None:
|
||||
filename = getImageFileName(cover.replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\'))
|
||||
if '.png' == filename[1]:
|
||||
@@ -176,43 +206,57 @@ 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.landscapemode 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("<item id=\"css\" href=\"Text/style.css\" media-type=\"text/css\"/>\n")
|
||||
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 \
|
||||
(options.profile == 'K4' or options.profile == 'KHD'):
|
||||
f.write("<itemref idref=\"blank-page\" properties=\"layout-blank\"/>\n")
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
|
||||
# noinspection PyRedundantParentheses
|
||||
if ((options.righttoleft and facing == 'left') or (not options.righttoleft and facing == 'right')) and\
|
||||
options.landscapemode:
|
||||
f.write("<itemref idref=\"blank-page" + str(splitCountUsed) + "\" properties=\"layout-blank\"/>\n")
|
||||
splitCountUsed += 1
|
||||
if options.landscapemode:
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing1 + "\"/>\n")
|
||||
else:
|
||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
||||
elif entry.endswith("-2"):
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
|
||||
if righttoleft:
|
||||
if options.landscapemode:
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing2 + "\"/>\n")
|
||||
else:
|
||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
||||
if options.righttoleft:
|
||||
facing = "right"
|
||||
else:
|
||||
facing = "left"
|
||||
else:
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n")
|
||||
if options.landscapemode:
|
||||
f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-" + facing + "\"/>\n")
|
||||
else:
|
||||
f.write("<itemref idref=\"page_" + entry + "\"/>\n")
|
||||
if facing == 'right':
|
||||
facing = 'left'
|
||||
else:
|
||||
facing = 'right'
|
||||
f.write("</spine>\n<guide>\n</guide>\n</package>\n")
|
||||
f.close()
|
||||
# finish with standard ePub folders
|
||||
os.mkdir(os.path.join(dstdir, 'META-INF'))
|
||||
f = open(os.path.join(dstdir, 'mimetype'), 'w')
|
||||
f.write('application/epub+zip')
|
||||
@@ -233,6 +277,10 @@ def getImageFileName(imgfile):
|
||||
if filename[0].startswith('.') or\
|
||||
(filename[1].lower() != '.png' and
|
||||
filename[1].lower() != '.jpg' and
|
||||
filename[1].lower() != '.gif' and
|
||||
filename[1].lower() != '.tif' and
|
||||
filename[1].lower() != '.tiff' and
|
||||
filename[1].lower() != '.bmp' and
|
||||
filename[1].lower() != '.jpeg'):
|
||||
return None
|
||||
return filename
|
||||
@@ -248,18 +296,22 @@ def isInFilelist(filename, filelist):
|
||||
|
||||
|
||||
def applyImgOptimization(img, isSplit=False, toRight=False):
|
||||
img.optimizeImage()
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers:
|
||||
img.cutPageNumber()
|
||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight)
|
||||
img.quantizeImage()
|
||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, options.landscapemode,
|
||||
options.nopanelviewhq)
|
||||
img.optimizeImage(options.gamma)
|
||||
if options.forcepng:
|
||||
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,26 +321,38 @@ 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:
|
||||
toRight1 = False
|
||||
toRight2 = True
|
||||
if facing == "left":
|
||||
splitCount += 1
|
||||
facing = "right"
|
||||
else:
|
||||
toRight1 = True
|
||||
toRight2 = False
|
||||
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.forcepng)
|
||||
img1 = image.ComicPage(split[1], options.profile)
|
||||
applyImgOptimization(img1, True, toRight2)
|
||||
img1.saveToDir(dirpath)
|
||||
img1.saveToDir(dirpath, options.forcepng)
|
||||
else:
|
||||
if facing == "right":
|
||||
facing = "left"
|
||||
else:
|
||||
facing = "right"
|
||||
applyImgOptimization(img)
|
||||
img.saveToDir(dirpath)
|
||||
img.saveToDir(dirpath, options.forcepng)
|
||||
|
||||
|
||||
def genEpubStruct(path):
|
||||
@@ -296,13 +360,134 @@ def genEpubStruct(path):
|
||||
filelist = []
|
||||
chapterlist = []
|
||||
cover = None
|
||||
_, deviceres, _, _, panelviewsize = image.ProfileData.Profiles[options.profile]
|
||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
|
||||
#DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
|
||||
#Generic Panel View support + Margins fix for Non-Kindle devices.
|
||||
f.writelines(["@page {\n",
|
||||
"margin-bottom: 0;\n",
|
||||
"margin-top: 0\n",
|
||||
"}\n",
|
||||
"body {\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",
|
||||
"div.fs {\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"position: relative;\n",
|
||||
"display: block;\n",
|
||||
"text-align: center\n",
|
||||
"}\n",
|
||||
"div.fs a {\n",
|
||||
"display: block;\n",
|
||||
"width : 100%;\n",
|
||||
"height: 100%;\n",
|
||||
"}\n",
|
||||
"div.fs div {\n",
|
||||
"position: absolute;\n",
|
||||
"}\n",
|
||||
"img.singlePage {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(deviceres[1]), "px;\n",
|
||||
"width: ", str(deviceres[0]), "px;\n",
|
||||
"}\n",
|
||||
"div.target-mag-parent {\n",
|
||||
"width:100%;\n",
|
||||
"height:100%;\n",
|
||||
"display:none;\n",
|
||||
"}\n",
|
||||
"div.target-mag {\n",
|
||||
"position: absolute;\n",
|
||||
"display: block;\n",
|
||||
"overflow: hidden;\n",
|
||||
"}\n",
|
||||
"div.target-mag img {\n",
|
||||
"position: absolute;\n",
|
||||
"height: ", str(panelviewsize[1]), "px;\n",
|
||||
"width: ", str(panelviewsize[0]), "px;\n",
|
||||
"}\n",
|
||||
"#BoxTL {\n",
|
||||
"top: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTR {\n",
|
||||
"top: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBL {\n",
|
||||
"bottom: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxBR {\n",
|
||||
"bottom: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 50%;\n",
|
||||
"width: 50%;\n",
|
||||
"}\n",
|
||||
"#BoxTL-Panel {\n",
|
||||
"top: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxTL-Panel img {\n",
|
||||
"top: 0%;\n",
|
||||
"left: 0%;\n",
|
||||
"}\n",
|
||||
"#BoxTR-Panel {\n",
|
||||
"top: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxTR-Panel img {\n",
|
||||
"top: 0%;\n",
|
||||
"right: 0%;\n",
|
||||
"}\n",
|
||||
"#BoxBL-Panel {\n",
|
||||
"bottom: 0;\n",
|
||||
"left: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxBL-Panel img {\n",
|
||||
"bottom: 0%;\n",
|
||||
"left: 0%;\n",
|
||||
"}\n",
|
||||
"#BoxBR-Panel {\n",
|
||||
"bottom: 0;\n",
|
||||
"right: 0;\n",
|
||||
"height: 100%;\n",
|
||||
"width: 100%;\n",
|
||||
"}\n",
|
||||
"#BoxBR-Panel img {\n",
|
||||
"bottom: 0%;\n",
|
||||
"right: 0%;\n",
|
||||
"}"
|
||||
])
|
||||
f.close()
|
||||
for (dirpath, dirnames, filenames) in os.walk(os.path.join(path, 'OEBPS', 'Images')):
|
||||
chapter = False
|
||||
for afile in filenames:
|
||||
filename = getImageFileName(afile)
|
||||
if filename is not None:
|
||||
# put credits at the end
|
||||
if "credit" in afile.lower():
|
||||
os.rename(os.path.join(dirpath, afile), os.path.join(dirpath, 'ZZZ999_' + afile))
|
||||
afile = 'ZZZ999_' + afile
|
||||
@@ -322,14 +507,13 @@ def genEpubStruct(path):
|
||||
convert = lambda text: int(text) if text.isdigit() else text
|
||||
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:
|
||||
buildOPF(options.profile, path, options.title, filelist, cover)
|
||||
if options.landscapemode 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 +522,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")
|
||||
@@ -352,73 +541,145 @@ def getWorkFolder(afile):
|
||||
return path
|
||||
|
||||
|
||||
def slugify(value):
|
||||
"""
|
||||
Normalizes string, converts to lowercase, removes non-alpha characters,
|
||||
and converts spaces to hyphens.
|
||||
"""
|
||||
import unicodedata
|
||||
value = unicodedata.normalize('NFKD', unicode(value, 'latin1')).encode('ascii', 'ignore')
|
||||
value = re.sub('[^\w\s\.-]', '', value).strip().lower()
|
||||
value = re.sub('[-\.\s]+', '-', value)
|
||||
value = re.sub(r'([0-9]+)', r'00000\1', value)
|
||||
value = re.sub(r'0*([0-9]{6,})', r'\1', value)
|
||||
return value
|
||||
|
||||
|
||||
def sanitizeTree(filetree):
|
||||
for root, dirs, files in os.walk(filetree):
|
||||
for name in files:
|
||||
if name.startswith('.') or name.lower() == 'thumbs.db':
|
||||
os.remove(os.path.join(root, name))
|
||||
else:
|
||||
splitname = os.path.splitext(name)
|
||||
os.rename(os.path.join(root, name),
|
||||
os.path.join(root, slugify(splitname[0]) + splitname[1]))
|
||||
for name in dirs:
|
||||
if name.startswith('.'):
|
||||
os.remove(os.path.join(root, name))
|
||||
else:
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugify(name)))
|
||||
|
||||
|
||||
def Copyright():
|
||||
print ('comic2ebook v%(__version__)s. '
|
||||
'Written 2012 by Ciro Mattia Gonano.' % globals())
|
||||
|
||||
|
||||
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."
|
||||
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, K4NT, K4T, 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("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
||||
help="Outputs a CBZ archive and does not generate EPUB")
|
||||
parser.add_option("--nopanelviewhq", action="store_true", dest="nopanelviewhq", default=False,
|
||||
help="Disable high quality Panel View [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("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG (For non-Kindle devices) [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 file (EPUB or CBZ) 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)
|
||||
checkOptions()
|
||||
if len(args) != 1:
|
||||
parser.print_help()
|
||||
return
|
||||
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..."
|
||||
genEpubStruct(path)
|
||||
# actually zip the ePub
|
||||
if options.output is not None:
|
||||
if options.output.endswith('.epub'):
|
||||
epubpath = os.path.abspath(options.output)
|
||||
elif os.path.isdir(args[0]):
|
||||
epubpath = os.path.abspath(options.output) + "/" + os.path.basename(args[0]) + '.epub'
|
||||
else:
|
||||
epubpath = os.path.abspath(options.output) + "/" \
|
||||
+ os.path.basename(os.path.splitext(args[0])[0]) + '.epub'
|
||||
elif os.path.isdir(args[0]):
|
||||
epubpath = args[0] + '.epub'
|
||||
if options.cbzoutput:
|
||||
# if CBZ output wanted, compress all images and return filepath
|
||||
print "\nCreating CBZ file..."
|
||||
filepath = getOutputFilename(args[0], options.output, '.cbz')
|
||||
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
|
||||
else:
|
||||
epubpath = os.path.splitext(args[0])[0] + '.epub'
|
||||
make_archive(path + '_comic', 'zip', path)
|
||||
move(path + '_comic.zip', epubpath)
|
||||
print "\nCreating ePub structure..."
|
||||
genEpubStruct(path)
|
||||
# actually zip the ePub
|
||||
filepath = getOutputFilename(args[0], options.output, '.epub')
|
||||
make_archive(path + '_comic', 'zip', path)
|
||||
move(path + '_comic.zip', filepath)
|
||||
rmtree(path)
|
||||
return epubpath
|
||||
return filepath
|
||||
|
||||
|
||||
def getOutputFilename(srcpath, wantedname, ext):
|
||||
if not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
if wantedname is not None:
|
||||
if wantedname.endswith(ext):
|
||||
filename = os.path.abspath(wantedname)
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
|
||||
else:
|
||||
filename = os.path.abspath(options.output) + "/" \
|
||||
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = srcpath + ext
|
||||
else:
|
||||
filename = os.path.splitext(srcpath)[0] + ext
|
||||
if os.path.isfile(filename):
|
||||
filename = os.path.splitext(filename)[0] + '_kcc' + ext
|
||||
return filename
|
||||
|
||||
|
||||
def checkOptions():
|
||||
global options
|
||||
if options.profile == 'K4T' or options.profile == 'KHD':
|
||||
options.landscapemode = True
|
||||
else:
|
||||
options.landscapemode = False
|
||||
if options.profile == 'K3' or options.profile == 'K4NT':
|
||||
#Real Panel View
|
||||
options.panelview = True
|
||||
else:
|
||||
#Virtual Panel View
|
||||
options.panelview = False
|
||||
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX' or options.profile == 'KDXG':
|
||||
options.nopanelviewhq = True
|
||||
|
||||
|
||||
def getEpubPath():
|
||||
|
||||
253
kcc/gui.py
253
kcc/gui.py
@@ -31,6 +31,7 @@ from subprocess import call
|
||||
import os
|
||||
import shutil
|
||||
import stat
|
||||
import traceback
|
||||
|
||||
|
||||
class MainWindow:
|
||||
@@ -39,25 +40,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 +65,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 +79,179 @@ 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),
|
||||
'Bcbz_only': IntVar(None, 0),
|
||||
'Cmangastyle': IntVar(None, 0),
|
||||
'Dnopanelviewhq': IntVar(None, 0),
|
||||
'Eimage_preprocess': IntVar(None, 0),
|
||||
'Fforcepng': IntVar(None, 0),
|
||||
'Gimage_gamma': DoubleVar(None, 0.0),
|
||||
'Himage_upscale': IntVar(None, 0),
|
||||
'Iimage_stretch': IntVar(None, 0),
|
||||
'Jblack_borders': IntVar(None, 0),
|
||||
'Krotate': IntVar(None, 0),
|
||||
'Lnosplitrotate': IntVar(None, 0),
|
||||
'Mcut_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",
|
||||
'Bcbz_only': "Generate CBZ only (skip EPUB/Mobi generation)",
|
||||
'Cmangastyle': "Manga mode",
|
||||
'Dnopanelviewhq': "Disable high quality Panel View",
|
||||
'Eimage_preprocess': "Disable image optimizations",
|
||||
'Fforcepng': "Create PNG files instead of JPEG",
|
||||
'Gimage_gamma': "Custom gamma correction",
|
||||
'Himage_upscale': "Allow image upscaling",
|
||||
'Iimage_stretch': "Stretch images",
|
||||
'Jblack_borders': "Use black borders (instead of white ones)",
|
||||
'Krotate': "Rotate images (instead of splitting them)",
|
||||
'Lnosplitrotate': "Disable both splitting and rotation",
|
||||
'Mcut_page_numbers': "Disable page numbers cutting"
|
||||
}
|
||||
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:
|
||||
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['Bcbz_only'].get() == 1:
|
||||
argv.append("-c")
|
||||
if self.options['Cmangastyle'].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['Dnopanelviewhq'].get() == 1:
|
||||
argv.append("--nopanelviewhq")
|
||||
if self.options['Eimage_preprocess'].get() == 1:
|
||||
argv.append("--noprocessing")
|
||||
if self.options['Fforcepng'].get() == 1:
|
||||
argv.append("--forcepng")
|
||||
if self.options['Gimage_gamma'].get() != 0.0:
|
||||
argv.append("--gamma")
|
||||
argv.append(self.options['Gimage_gamma'].get())
|
||||
if self.options['Himage_upscale'].get() == 1:
|
||||
argv.append("--upscale")
|
||||
if self.options['Iimage_stretch'].get() == 1:
|
||||
argv.append("--stretch")
|
||||
if self.options['Jblack_borders'].get() == 1:
|
||||
argv.append("--blackborders")
|
||||
if self.options['Krotate'].get() == 1:
|
||||
argv.append("--rotate")
|
||||
if self.options['Lnosplitrotate'].get() == 1:
|
||||
argv.append("--nosplitrotate")
|
||||
if self.options['Mcut_page_numbers'].get() == 1:
|
||||
argv.append("--nocutpagenumbers")
|
||||
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 and self.options['Bcbz_only'].get() == 0:
|
||||
try:
|
||||
if os.path.getsize(epub_path) > 314572800:
|
||||
# do not call kindlegen if source is bigger than 300MB
|
||||
tkMessageBox.showwarning('KindleGen Warning',
|
||||
"ePub file %s is bigger than 300MB, 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 +261,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()
|
||||
|
||||
207
kcc/image.py
207
kcc/image.py
@@ -20,7 +20,7 @@ __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
from PIL import Image, ImageOps, ImageDraw, ImageStat
|
||||
from PIL import Image, ImageOps, ImageStat
|
||||
|
||||
|
||||
class ImageFlags:
|
||||
@@ -77,20 +77,22 @@ 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 1", (600, 800), Palette4, 1.8, (900, 1200)),
|
||||
'K2': ("Kindle 2", (600, 800), Palette15, 1.8, (900, 1200)),
|
||||
'K3': ("Kindle Keyboard", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'K4NT': ("Kindle Non-Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'K4T': ("Kindle Touch", (600, 800), Palette16, 1.8, (900, 1200)),
|
||||
'KHD': ("Kindle Paperwhite", (758, 1024), Palette16, 1.8, (1137, 1536)),
|
||||
'KDX': ("Kindle DX", (824, 1200), Palette15, 1.8, (1236, 1800)),
|
||||
'KDXG': ("Kindle DXG", (824, 1200), Palette16, 1.8, (1236, 1800))
|
||||
}
|
||||
|
||||
ProfileLabels = {
|
||||
"Kindle": 'K1',
|
||||
"Kindle 1": 'K1',
|
||||
"Kindle 2": 'K2',
|
||||
"Kindle 3/Keyboard": 'K3',
|
||||
"Kindle 4/NT/Touch": 'K4',
|
||||
"Kindle 4/Non-Touch": 'K4NT',
|
||||
"Kindle 4/Touch": 'K4T',
|
||||
"Kindle Paperwhite": 'KHD',
|
||||
"Kindle DX": 'KDX',
|
||||
"Kindle DXG": 'KDXG'
|
||||
@@ -101,7 +103,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, self.panelviewsize = ProfileData.Profiles[device]
|
||||
except KeyError:
|
||||
raise RuntimeError('Unexpected output device %s' % device)
|
||||
try:
|
||||
@@ -111,18 +113,29 @@ class ComicPage:
|
||||
raise RuntimeError('Cannot read image file %s' % source)
|
||||
self.image = self.image.convert('RGB')
|
||||
|
||||
def saveToDir(self, targetdir):
|
||||
def saveToDir(self, targetdir, forcepng):
|
||||
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 forcepng:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".png"), "PNG")
|
||||
else:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + ".jpg"), "JPEG")
|
||||
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):
|
||||
self.image = self.image.convert('L') # convert to grayscale
|
||||
self.image = self.image.convert("RGB") # convert back to RGB
|
||||
colors = len(self.palette) / 3
|
||||
if colors < 256:
|
||||
self.palette += self.palette[:3] * (256 - colors)
|
||||
@@ -130,46 +143,48 @@ class ComicPage:
|
||||
palImg.putpalette(self.palette)
|
||||
self.image = self.image.quantize(palette=palImg)
|
||||
|
||||
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False):
|
||||
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False,
|
||||
landscapeMode=False, noPanelViewHQ=False):
|
||||
method = Image.ANTIALIAS
|
||||
if black_borders:
|
||||
fill = 'black'
|
||||
else:
|
||||
fill = 'white'
|
||||
if noPanelViewHQ:
|
||||
size = (self.size[0], self.size[1])
|
||||
else:
|
||||
size = (self.panelviewsize[0], self.panelviewsize[1])
|
||||
if isSplit and landscapeMode:
|
||||
upscale = True
|
||||
if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
|
||||
if not upscale:
|
||||
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
|
||||
borderw = (self.size[0] - self.image.size[0])
|
||||
borderh = (self.size[1] - self.image.size[1]) / 2
|
||||
self.image = ImageOps.expand(self.image, border=(0, borderh), fill=fill)
|
||||
tempImg = Image.new(self.image.mode, (self.image.size[0] + borderw, self.image.size[1]), fill)
|
||||
if toRight:
|
||||
tempImg.paste(self.image, (borderw, 0))
|
||||
else:
|
||||
tempImg.paste(self.image, (0, 0))
|
||||
self.image = tempImg
|
||||
else:
|
||||
borderw = (self.size[0] - self.image.size[0]) / 2
|
||||
borderh = (self.size[1] - self.image.size[1]) / 2
|
||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill)
|
||||
borderw = (self.size[0] - self.image.size[0]) / 2
|
||||
borderh = (self.size[1] - self.image.size[1]) / 2
|
||||
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=fill)
|
||||
return self.image
|
||||
else:
|
||||
method = Image.NEAREST
|
||||
|
||||
method = Image.BILINEAR
|
||||
if stretch: # if stretching call directly resize() without other considerations.
|
||||
self.image = self.image.resize(self.size, method)
|
||||
self.image = self.image.resize(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]
|
||||
if isSplit and (self.profile == 'K4' or self.profile == 'KHD'):
|
||||
diff = 2
|
||||
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
|
||||
if isSplit and landscapeMode:
|
||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
|
||||
tempImg = Image.new(self.image.mode, (self.image.size[0] + diff, self.image.size[1]), fill)
|
||||
if toRight:
|
||||
tempImg.paste(self.image, (diff, 0))
|
||||
else:
|
||||
tempImg.paste(self.image, (0, 0))
|
||||
self.image = tempImg
|
||||
else:
|
||||
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
|
||||
self.image = ImageOps.expand(self.image, border=(diff / 2, 0), fill=fill)
|
||||
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
|
||||
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
|
||||
self.image = ImageOps.expand(self.image, border=(0, diff / 2), fill=fill)
|
||||
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
|
||||
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
|
||||
return self.image
|
||||
|
||||
def splitPage(self, targetdir, righttoleft=False, rotate=False):
|
||||
@@ -209,29 +224,6 @@ class ComicPage:
|
||||
else:
|
||||
return None
|
||||
|
||||
def frameImage(self):
|
||||
foreground = tuple(self.palette[:3])
|
||||
background = tuple(self.palette[-3:])
|
||||
widthDev, heightDev = self.size
|
||||
widthImg, heightImg = self.image.size
|
||||
pastePt = (
|
||||
max(0, (widthDev - widthImg) / 2),
|
||||
max(0, (heightDev - heightImg) / 2)
|
||||
)
|
||||
corner1 = (
|
||||
pastePt[0] - 1,
|
||||
pastePt[1] - 1
|
||||
)
|
||||
corner2 = (
|
||||
pastePt[0] + widthImg + 1,
|
||||
pastePt[1] + heightImg + 1
|
||||
)
|
||||
imageBg = Image.new(self.image.mode, self.size, background)
|
||||
imageBg.paste(self.image, pastePt)
|
||||
draw = ImageDraw.Draw(imageBg)
|
||||
draw.rectangle([corner1, corner2], outline=foreground)
|
||||
self.image = imageBg
|
||||
|
||||
def cutPageNumber(self):
|
||||
widthImg, heightImg = self.image.size
|
||||
delta = 2
|
||||
@@ -324,37 +316,60 @@ class ComicPage:
|
||||
# print "New size: %sx%s"%(self.image.size[0],self.image.size[1])
|
||||
return self.image
|
||||
|
||||
def addProgressbar(self, file_number, files_totalnumber, size, howoften):
|
||||
if file_number // howoften != float(file_number) / howoften:
|
||||
return self.image
|
||||
white = (255, 255, 255)
|
||||
black = (0, 0, 0)
|
||||
widthDev, heightDev = size
|
||||
widthImg, heightImg = self.image.size
|
||||
pastePt = (
|
||||
max(0, (widthDev - widthImg) / 2),
|
||||
max(0, (heightDev - heightImg) / 2)
|
||||
)
|
||||
imageBg = Image.new('RGB', size, white)
|
||||
imageBg.paste(self.image, pastePt)
|
||||
self.image = imageBg
|
||||
widthImg, heightImg = self.image.size
|
||||
draw = ImageDraw.Draw(self.image)
|
||||
#Black rectangle
|
||||
draw.rectangle([(0, heightImg - 3), (widthImg, heightImg)], outline=black, fill=black)
|
||||
#White rectangle
|
||||
draw.rectangle([(widthImg * file_number / files_totalnumber, heightImg - 3), (widthImg - 1, heightImg)],
|
||||
outline=black, fill=white)
|
||||
#Making notches
|
||||
for i in range(1, 10):
|
||||
if i <= (10 * file_number / files_totalnumber):
|
||||
notch_colour = white # White
|
||||
else:
|
||||
notch_colour = black # Black
|
||||
draw.line([(widthImg * float(i) / 10, heightImg - 3), (widthImg * float(i) / 10, heightImg)],
|
||||
fill=notch_colour)
|
||||
#The 50%
|
||||
if i == 5:
|
||||
draw.rectangle([(widthImg / 2 - 1, heightImg - 5), (widthImg / 2 + 1, heightImg)],
|
||||
outline=black, fill=notch_colour)
|
||||
return self.image
|
||||
# def addProgressbar(self, file_number, files_totalnumber, size, howoften):
|
||||
# if file_number // howoften != float(file_number) / howoften:
|
||||
# return self.image
|
||||
# white = (255, 255, 255)
|
||||
# black = (0, 0, 0)
|
||||
# widthDev, heightDev = size
|
||||
# widthImg, heightImg = self.image.size
|
||||
# pastePt = (
|
||||
# max(0, (widthDev - widthImg) / 2),
|
||||
# max(0, (heightDev - heightImg) / 2)
|
||||
# )
|
||||
# imageBg = Image.new('RGB', size, white)
|
||||
# imageBg.paste(self.image, pastePt)
|
||||
# self.image = imageBg
|
||||
# widthImg, heightImg = self.image.size
|
||||
# draw = ImageDraw.Draw(self.image)
|
||||
# #Black rectangle
|
||||
# draw.rectangle([(0, heightImg - 3), (widthImg, heightImg)], outline=black, fill=black)
|
||||
# #White rectangle
|
||||
# draw.rectangle([(widthImg * file_number / files_totalnumber, heightImg - 3), (widthImg - 1, heightImg)],
|
||||
# outline=black, fill=white)
|
||||
# #Making notches
|
||||
# for i in range(1, 10):
|
||||
# if i <= (10 * file_number / files_totalnumber):
|
||||
# notch_colour = white # White
|
||||
# else:
|
||||
# notch_colour = black # Black
|
||||
# draw.line([(widthImg * float(i) / 10, heightImg - 3), (widthImg * float(i) / 10, heightImg)],
|
||||
# fill=notch_colour)
|
||||
# #The 50%
|
||||
# if i == 5:
|
||||
# draw.rectangle([(widthImg / 2 - 1, heightImg - 5), (widthImg / 2 + 1, heightImg)],
|
||||
# outline=black, fill=notch_colour)
|
||||
# return self.image
|
||||
#
|
||||
# def frameImage(self):
|
||||
# foreground = tuple(self.palette[:3])
|
||||
# background = tuple(self.palette[-3:])
|
||||
# widthDev, heightDev = self.size
|
||||
# widthImg, heightImg = self.image.size
|
||||
# pastePt = (
|
||||
# max(0, (widthDev - widthImg) / 2),
|
||||
# max(0, (heightDev - heightImg) / 2)
|
||||
# )
|
||||
# corner1 = (
|
||||
# pastePt[0] - 1,
|
||||
# pastePt[1] - 1
|
||||
# )
|
||||
# corner2 = (
|
||||
# pastePt[0] + widthImg + 1,
|
||||
# pastePt[1] + heightImg + 1
|
||||
# )
|
||||
# imageBg = Image.new(self.image.mode, self.size, background)
|
||||
# imageBg.paste(self.image, pastePt)
|
||||
# draw = ImageDraw.Draw(imageBg)
|
||||
# draw.rectangle([corner1, corner2], outline=foreground)
|
||||
# self.image = imageBg
|
||||
|
||||
@@ -70,3 +70,4 @@ class PdfJpgExtract:
|
||||
|
||||
njpg += 1
|
||||
i = iend
|
||||
return self.path
|
||||
|
||||
281
kcc/rarfile.py
281
kcc/rarfile.py
@@ -1,6 +1,6 @@
|
||||
# rarfile.py
|
||||
#
|
||||
# Copyright (c) 2005-2012 Marko Kreen <markokr@gmail.com>
|
||||
# Copyright (c) 2005-2013 Marko Kreen <markokr@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
|
||||
@@ -17,7 +17,7 @@
|
||||
r"""RAR archive reader.
|
||||
|
||||
This is Python module for Rar archive reading. The interface
|
||||
is made as zipfile like as possible.
|
||||
is made as :mod:`zipfile`-like as possible.
|
||||
|
||||
Basic logic:
|
||||
- Parse archive structure with Python.
|
||||
@@ -34,7 +34,17 @@ Example::
|
||||
for f in rf.infolist():
|
||||
print f.filename, f.file_size
|
||||
if f.filename == 'README':
|
||||
print rf.read(f)
|
||||
print(rf.read(f))
|
||||
|
||||
Archive files can also be accessed via file-like object returned
|
||||
by :meth:`RarFile.open`::
|
||||
|
||||
import rarfile
|
||||
|
||||
with rarfile.RarFile('archive.rar') as rf:
|
||||
with rf.open('README') as f:
|
||||
for ln in f:
|
||||
print(ln.strip())
|
||||
|
||||
There are few module-level parameters to tune behaviour,
|
||||
here they are with defaults, and reason to change it::
|
||||
@@ -64,7 +74,7 @@ For more details, refer to source.
|
||||
|
||||
"""
|
||||
|
||||
__version__ = '2.5'
|
||||
__version__ = '2.6'
|
||||
|
||||
# export only interesting items
|
||||
__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
|
||||
@@ -73,7 +83,7 @@ __all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile']
|
||||
## Imports and compat - support both Python 2.x and 3.x
|
||||
##
|
||||
|
||||
import sys, os, struct
|
||||
import sys, os, struct, errno
|
||||
from struct import pack, unpack
|
||||
from binascii import crc32
|
||||
from tempfile import mkstemp
|
||||
@@ -148,45 +158,45 @@ except ImportError:
|
||||
## Module configuration. Can be tuned after importing.
|
||||
##
|
||||
|
||||
# default fallback charset
|
||||
#: default fallback charset
|
||||
DEFAULT_CHARSET = "windows-1252"
|
||||
|
||||
# list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
|
||||
#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed
|
||||
TRY_ENCODINGS = ('utf8', 'utf-16le')
|
||||
|
||||
# 'unrar', 'rar' or full path to either one
|
||||
#: 'unrar', 'rar' or full path to either one
|
||||
UNRAR_TOOL = "unrar"
|
||||
|
||||
# Command line args to use for opening file for reading.
|
||||
#: Command line args to use for opening file for reading.
|
||||
OPEN_ARGS = ('p', '-inul')
|
||||
|
||||
# Command line args to use for extracting file to disk.
|
||||
#: Command line args to use for extracting file to disk.
|
||||
EXTRACT_ARGS = ('x', '-y', '-idq')
|
||||
|
||||
# args for testrar()
|
||||
#: args for testrar()
|
||||
TEST_ARGS = ('t', '-idq')
|
||||
|
||||
# whether to speed up decompression by using tmp archive
|
||||
#: whether to speed up decompression by using tmp archive
|
||||
USE_EXTRACT_HACK = 1
|
||||
|
||||
# limit the filesize for tmp archive usage
|
||||
#: limit the filesize for tmp archive usage
|
||||
HACK_SIZE_LIMIT = 20*1024*1024
|
||||
|
||||
# whether to parse file/archive comments.
|
||||
#: whether to parse file/archive comments.
|
||||
NEED_COMMENTS = 1
|
||||
|
||||
# whether to convert comments to unicode strings
|
||||
#: whether to convert comments to unicode strings
|
||||
UNICODE_COMMENTS = 0
|
||||
|
||||
# When RAR is corrupt, stopping on bad header is better
|
||||
# On unknown/misparsed RAR headers reporting is better
|
||||
#: When RAR is corrupt, stopping on bad header is better
|
||||
#: On unknown/misparsed RAR headers reporting is better
|
||||
REPORT_BAD_HEADER = 0
|
||||
|
||||
# Convert RAR time tuple into datetime() object
|
||||
#: Convert RAR time tuple into datetime() object
|
||||
USE_DATETIME = 0
|
||||
|
||||
# Separator for path name components. RAR internally uses '\\'.
|
||||
# Use '/' to be similar with zipfile.
|
||||
#: Separator for path name components. RAR internally uses '\\'.
|
||||
#: Use '/' to be similar with zipfile.
|
||||
PATH_SEP = '\\'
|
||||
|
||||
##
|
||||
@@ -320,6 +330,8 @@ class RarMemoryError(RarExecError):
|
||||
"""Memory error"""
|
||||
class RarCreateError(RarExecError):
|
||||
"""Create error"""
|
||||
class RarNoFilesError(RarExecError):
|
||||
"""No files that match pattern were found"""
|
||||
class RarUserBreak(RarExecError):
|
||||
"""User stop"""
|
||||
class RarUnknownError(RarExecError):
|
||||
@@ -335,49 +347,57 @@ def is_rarfile(fn):
|
||||
|
||||
|
||||
class RarInfo(object):
|
||||
'''An entry in rar archive.
|
||||
|
||||
@ivar filename:
|
||||
File name with relative path.
|
||||
Default path separator is '/', to change set rarfile.PATH_SEP.
|
||||
Always unicode string.
|
||||
@ivar date_time:
|
||||
Modification time, tuple of (year, month, day, hour, minute, second).
|
||||
Or datetime() object if USE_DATETIME is set.
|
||||
@ivar file_size:
|
||||
Uncompressed size.
|
||||
@ivar compress_size:
|
||||
Compressed size.
|
||||
@ivar compress_type:
|
||||
Compression method: 0x30 - 0x35.
|
||||
@ivar extract_version:
|
||||
Minimal Rar version needed for decompressing.
|
||||
@ivar host_os:
|
||||
Host OS type, one of RAR_OS_* constants.
|
||||
@ivar mode:
|
||||
File attributes. May be either dos-style or unix-style, depending on host_os.
|
||||
@ivar CRC:
|
||||
CRC-32 of uncompressed file, unsigned int.
|
||||
@ivar volume:
|
||||
Volume nr, starting from 0.
|
||||
@ivar volume_file:
|
||||
Volume file name, where file starts.
|
||||
@ivar type:
|
||||
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
|
||||
@ivar flags:
|
||||
For files, RAR_FILE_* bits.
|
||||
@ivar comment:
|
||||
File comment (unicode string or None).
|
||||
r'''An entry in rar archive.
|
||||
|
||||
@ivar mtime:
|
||||
Optional time field: Modification time, with float seconds.
|
||||
Same as .date_time but with more precision.
|
||||
@ivar ctime:
|
||||
Optional time field: creation time, with float seconds.
|
||||
@ivar atime:
|
||||
Optional time field: last access time, with float seconds.
|
||||
@ivar arctime:
|
||||
Optional time field: archival time, with float seconds.
|
||||
:mod:`zipfile`-compatible fields:
|
||||
|
||||
filename
|
||||
File name with relative path.
|
||||
Default path separator is '\\', to change set rarfile.PATH_SEP.
|
||||
Always unicode string.
|
||||
date_time
|
||||
Modification time, tuple of (year, month, day, hour, minute, second).
|
||||
Or datetime() object if USE_DATETIME is set.
|
||||
file_size
|
||||
Uncompressed size.
|
||||
compress_size
|
||||
Compressed size.
|
||||
CRC
|
||||
CRC-32 of uncompressed file, unsigned int.
|
||||
comment
|
||||
File comment. Byte string or None. Use UNICODE_COMMENTS
|
||||
to get automatic decoding to unicode.
|
||||
volume
|
||||
Volume nr, starting from 0.
|
||||
|
||||
RAR-specific fields:
|
||||
|
||||
compress_type
|
||||
Compression method: 0x30 - 0x35.
|
||||
extract_version
|
||||
Minimal Rar version needed for decompressing.
|
||||
host_os
|
||||
Host OS type, one of RAR_OS_* constants.
|
||||
mode
|
||||
File attributes. May be either dos-style or unix-style, depending on host_os.
|
||||
volume_file
|
||||
Volume file name, where file starts.
|
||||
mtime
|
||||
Optional time field: Modification time, with float seconds.
|
||||
Same as .date_time but with more precision.
|
||||
ctime
|
||||
Optional time field: creation time, with float seconds.
|
||||
atime
|
||||
Optional time field: last access time, with float seconds.
|
||||
arctime
|
||||
Optional time field: archival time, with float seconds.
|
||||
|
||||
Internal fields:
|
||||
|
||||
type
|
||||
One of RAR_BLOCK_* types. Only entries with type==RAR_BLOCK_FILE are shown in .infolist().
|
||||
flags
|
||||
For files, RAR_FILE_* bits.
|
||||
'''
|
||||
|
||||
__slots__ = (
|
||||
@@ -417,7 +437,7 @@ class RarInfo(object):
|
||||
'header_offset',
|
||||
'salt',
|
||||
'volume_file',
|
||||
)
|
||||
)
|
||||
|
||||
def isdir(self):
|
||||
'''Returns True if the entry is a directory.'''
|
||||
@@ -431,19 +451,27 @@ class RarInfo(object):
|
||||
|
||||
class RarFile(object):
|
||||
'''Parse RAR structure, provide access to files in archive.
|
||||
|
||||
@ivar comment:
|
||||
Archive comment (unicode string or None).
|
||||
'''
|
||||
|
||||
#: Archive comment. Byte string or None. Use UNICODE_COMMENTS
|
||||
#: to get automatic decoding to unicode.
|
||||
comment = None
|
||||
|
||||
def __init__(self, rarfile, mode="r", charset=None, info_callback=None, crc_check = True):
|
||||
"""Open and parse a RAR archive.
|
||||
|
||||
@param rarfile: archive file name
|
||||
@param mode: only 'r' is supported.
|
||||
@param charset: fallback charset to use, if filenames are not already Unicode-enabled.
|
||||
@param info_callback: debug callback, gets to see all archive entries.
|
||||
@param crc_check: set to False to disable CRC checks
|
||||
Parameters:
|
||||
|
||||
rarfile
|
||||
archive file name
|
||||
mode
|
||||
only 'r' is supported.
|
||||
charset
|
||||
fallback charset to use, if filenames are not already Unicode-enabled.
|
||||
info_callback
|
||||
debug callback, gets to see all archive entries.
|
||||
crc_check
|
||||
set to False to disable CRC checks
|
||||
"""
|
||||
self.rarfile = rarfile
|
||||
self.comment = None
|
||||
@@ -455,6 +483,7 @@ class RarFile(object):
|
||||
self._needs_password = False
|
||||
self._password = None
|
||||
self._crc_check = crc_check
|
||||
self._vol_list = []
|
||||
|
||||
self._main = None
|
||||
|
||||
@@ -487,6 +516,14 @@ class RarFile(object):
|
||||
'''Return RarInfo objects for all files/directories in archive.'''
|
||||
return self._info_list
|
||||
|
||||
def volumelist(self):
|
||||
'''Returns filenames of archive volumes.
|
||||
|
||||
In case of single-volume archive, the list contains
|
||||
just the name of main archive file.
|
||||
'''
|
||||
return self._vol_list
|
||||
|
||||
def getinfo(self, fname):
|
||||
'''Return RarInfo for file.'''
|
||||
|
||||
@@ -508,7 +545,8 @@ class RarFile(object):
|
||||
raise NoRarEntry("No such file: "+fname)
|
||||
|
||||
def open(self, fname, mode = 'r', psw = None):
|
||||
'''Return open file object, where the data can be read.
|
||||
'''Returns file-like object (:class:`RarExtFile`),
|
||||
from where the data can be read.
|
||||
|
||||
The object implements io.RawIOBase interface, so it can
|
||||
be further wrapped with io.BufferedReader and io.TextIOWrapper.
|
||||
@@ -520,9 +558,14 @@ class RarFile(object):
|
||||
uncompressed files, on compressed files the seeking is implemented
|
||||
by reading ahead and/or restarting the decompression.
|
||||
|
||||
@param fname: file name or RarInfo instance.
|
||||
@param mode: must be 'r'
|
||||
@param psw: password to use for extracting.
|
||||
Parameters:
|
||||
|
||||
fname
|
||||
file name or RarInfo instance.
|
||||
mode
|
||||
must be 'r'
|
||||
psw
|
||||
password to use for extracting.
|
||||
'''
|
||||
|
||||
if mode != 'r':
|
||||
@@ -569,8 +612,12 @@ class RarFile(object):
|
||||
|
||||
For longer files using .open() may be better idea.
|
||||
|
||||
@param fname: filename or RarInfo instance
|
||||
@param psw: password to use for extracting.
|
||||
Parameters:
|
||||
|
||||
fname
|
||||
filename or RarInfo instance
|
||||
psw
|
||||
password to use for extracting.
|
||||
"""
|
||||
|
||||
f = self.open(fname, 'r', psw)
|
||||
@@ -591,9 +638,14 @@ class RarFile(object):
|
||||
def extract(self, member, path=None, pwd=None):
|
||||
"""Extract single file into current directory.
|
||||
|
||||
@param member: filename or RarInfo instance
|
||||
@param path: optional destination path
|
||||
@param pwd: optional password to use
|
||||
Parameters:
|
||||
|
||||
member
|
||||
filename or RarInfo instance
|
||||
path
|
||||
optional destination path
|
||||
pwd
|
||||
optional password to use
|
||||
"""
|
||||
if isinstance(member, RarInfo):
|
||||
fname = member.filename
|
||||
@@ -604,9 +656,14 @@ class RarFile(object):
|
||||
def extractall(self, path=None, members=None, pwd=None):
|
||||
"""Extract all files into current directory.
|
||||
|
||||
@param path: optional destination path
|
||||
@param members: optional filename or RarInfo instance list to extract
|
||||
@param pwd: optional password to use
|
||||
Parameters:
|
||||
|
||||
path
|
||||
optional destination path
|
||||
members
|
||||
optional filename or RarInfo instance list to extract
|
||||
pwd
|
||||
optional password to use
|
||||
"""
|
||||
fnlist = []
|
||||
if members is not None:
|
||||
@@ -691,6 +748,7 @@ class RarFile(object):
|
||||
more_vols = 0
|
||||
endarc = 0
|
||||
volfile = self.rarfile
|
||||
self._vol_list = [self.rarfile]
|
||||
while 1:
|
||||
if endarc:
|
||||
h = None # don't read past ENDARC
|
||||
@@ -705,6 +763,7 @@ class RarFile(object):
|
||||
self._fd = fd
|
||||
more_vols = 0
|
||||
endarc = 0
|
||||
self._vol_list.append(volfile)
|
||||
continue
|
||||
break
|
||||
h.volume = volume
|
||||
@@ -729,7 +788,7 @@ class RarFile(object):
|
||||
# RAR 2.x does not write RAR_BLOCK_ENDARC
|
||||
if h.flags & RAR_FILE_SPLIT_AFTER:
|
||||
more_vols = 1
|
||||
# RAR 2.x does not set RAR_MAIN_FIRSTVOLUME
|
||||
# RAR 2.x does not set RAR_MAIN_FIRSTVOLUME
|
||||
if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE:
|
||||
raise NeedFirstVolume("Need to start from first volume")
|
||||
|
||||
@@ -950,7 +1009,7 @@ class RarFile(object):
|
||||
pos += S_COMMENT_HDR.size
|
||||
data = hdata[pos : pos_next]
|
||||
cmt = rar_decompress(ver, meth, data, declen, sflags,
|
||||
crc, self._password)
|
||||
crc, self._password)
|
||||
if not self._crc_check:
|
||||
h.comment = self._decode_comment(cmt)
|
||||
elif crc32(cmt) & 0xFFFF == crc:
|
||||
@@ -1073,7 +1132,7 @@ class RarFile(object):
|
||||
|
||||
# decompress
|
||||
cmt = rar_decompress(inf.extract_version, inf.compress_type, data,
|
||||
inf.file_size, inf.flags, inf.CRC, psw, inf.salt)
|
||||
inf.file_size, inf.flags, inf.CRC, psw, inf.salt)
|
||||
|
||||
# check crc
|
||||
if self._crc_check:
|
||||
@@ -1208,7 +1267,7 @@ class UnicodeFilename:
|
||||
|
||||
|
||||
class RarExtFile(RawIOBase):
|
||||
"""Base class for 'file-like' object that RarFile.open() returns.
|
||||
"""Base class for file-like object that :meth:`RarFile.open` returns.
|
||||
|
||||
Provides public methods and common crc checking.
|
||||
|
||||
@@ -1216,13 +1275,15 @@ class RarExtFile(RawIOBase):
|
||||
- no short reads - .read() and .readinfo() read as much as requested.
|
||||
- no internal buffer, use io.BufferedReader for that.
|
||||
|
||||
@ivar name:
|
||||
filename of the archive entry.
|
||||
If :mod:`io` module is available (Python 2.6+, 3.x), then this calls
|
||||
will inherit from :class:`io.RawIOBase` class. This makes line-based
|
||||
access available: :meth:`RarExtFile.readline` and ``for ln in f``.
|
||||
"""
|
||||
|
||||
def __init__(self, rf, inf):
|
||||
"""Fill common fields"""
|
||||
#: Filename of the archive entry
|
||||
name = None
|
||||
|
||||
def __init__(self, rf, inf):
|
||||
RawIOBase.__init__(self)
|
||||
|
||||
# standard io.* properties
|
||||
@@ -1323,7 +1384,13 @@ class RarExtFile(RawIOBase):
|
||||
return self.inf.file_size - self.remain
|
||||
|
||||
def seek(self, ofs, whence = 0):
|
||||
"""Seek in data."""
|
||||
"""Seek in data.
|
||||
|
||||
On uncompressed files, the seeking works by actual
|
||||
seeks so it's fast. On compresses files its slow
|
||||
- forward seeking happends by reading ahead,
|
||||
backwards by re-opening and decompressing from the start.
|
||||
"""
|
||||
|
||||
# disable crc check when seeking
|
||||
self.crc_check = 0
|
||||
@@ -1372,8 +1439,17 @@ class RarExtFile(RawIOBase):
|
||||
"""Returns True"""
|
||||
return True
|
||||
|
||||
def writable(self):
|
||||
"""Returns False.
|
||||
|
||||
Writing is not supported."""
|
||||
return False
|
||||
|
||||
def seekable(self):
|
||||
"""Returns True"""
|
||||
"""Returns True.
|
||||
|
||||
Seeking is supported, although it's slow on compressed files.
|
||||
"""
|
||||
return True
|
||||
|
||||
def readall(self):
|
||||
@@ -1668,7 +1744,7 @@ def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=No
|
||||
date = 0
|
||||
mode = 0x20
|
||||
fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc,
|
||||
date, vers, meth, len(fname), mode)
|
||||
date, vers, meth, len(fname), mode)
|
||||
fhdr += fname
|
||||
if flags & RAR_FILE_SALT:
|
||||
if not salt:
|
||||
@@ -1757,8 +1833,15 @@ def custom_popen(cmd):
|
||||
creationflags = 0x08000000 # CREATE_NO_WINDOW
|
||||
|
||||
# run command
|
||||
p = Popen(cmd, bufsize = 0, stdout = PIPE, stdin = PIPE, stderr = STDOUT,
|
||||
creationflags = creationflags)
|
||||
try:
|
||||
p = Popen(cmd, bufsize = 0,
|
||||
stdout = PIPE, stdin = PIPE, stderr = STDOUT,
|
||||
creationflags = creationflags)
|
||||
except OSError:
|
||||
ex = sys.exc_info()[1]
|
||||
if ex.errno == errno.ENOENT:
|
||||
raise RarExecError("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL)
|
||||
raise
|
||||
return p
|
||||
|
||||
def check_returncode(p, out):
|
||||
@@ -1770,9 +1853,9 @@ def check_returncode(p, out):
|
||||
|
||||
# map return code to exception class
|
||||
errmap = [None,
|
||||
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
||||
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
||||
RarCreateError] # codes from rar.txt
|
||||
RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError,
|
||||
RarWriteError, RarOpenError, RarUserError, RarMemoryError,
|
||||
RarCreateError, RarNoFilesError] # codes from rar.txt
|
||||
if code > 0 and code < len(errmap):
|
||||
exc = errmap[code]
|
||||
elif code == 255:
|
||||
|
||||
@@ -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.9"
|
||||
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.9",
|
||||
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