1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-17 14:38:47 +00:00

Compare commits

..

55 Commits
3.0 ... 3.2.1

Author SHA1 Message Date
Paweł Jastrzębski
4c96de9cdf Updated README and version bump 2013-09-06 10:00:08 +02:00
Paweł Jastrzębski
3a645abe6f Hotfix for Russian OS 2013-09-06 09:46:22 +02:00
Paweł Jastrzębski
27bd6f96e7 Webtoon mode improvements 2013-08-19 16:46:27 +02:00
Paweł Jastrzębski
a98fac2e95 Webtoon mode improvements 2013-08-17 16:03:36 +02:00
Paweł Jastrzębski
f645b65a9e Added volume auto-split 2013-08-17 12:01:19 +02:00
Paweł Jastrzębski
6eaf8cc374 comic2panel: Detection of corrupted files 2013-08-16 19:25:26 +02:00
Paweł Jastrzębski
61c0b691ab Webtoon mode improvements 2013-08-16 17:10:18 +02:00
Paweł Jastrzębski
394cefb2de Sanitize job input 2013-08-16 17:09:49 +02:00
Paweł Jastrzębski
30d6a55e3c Webtoon mode improvements 2013-08-16 13:28:27 +02:00
Paweł Jastrzębski
e32018e8f6 Improved cleanup (close #58) 2013-08-15 10:05:03 +02:00
Paweł Jastrzębski
6b002a8475 Webtoon mode improvements 2013-08-15 09:48:50 +02:00
Paweł Jastrzębski
97e23c8f50 Web...toon mode improvements 2013-08-14 09:30:31 +02:00
Paweł Jastrzębski
e558ffd807 Temporary disabling new location of tmp files 2013-08-13 13:33:16 +02:00
Paweł Jastrzębski
71d158ca45 Fixed tmp directories - Need more coffee. 2013-08-13 10:56:49 +02:00
Paweł Jastrzębski
5f8e5e0be9 Fixed tmp directories 2013-08-13 10:48:42 +02:00
Paweł Jastrzębski
ff91eb1407 Webstrip mode improvements 2013-08-13 10:30:53 +02:00
Paweł Jastrzębski
877a859ef4 Fixed abort bug 2013-08-13 09:33:46 +02:00
Paweł Jastrzębski
f8b29cd967 Webstrip mode improvements 2013-08-13 09:25:53 +02:00
Paweł Jastrzębski
3d2554c557 Fixed tmp directories 2013-08-12 16:32:40 +02:00
Paweł Jastrzębski
b7d7204d40 Updated README and version bump 2013-08-12 15:59:32 +02:00
Paweł Jastrzębski
876d26d174 GUI: Added webstrip support 2013-08-12 14:01:57 +02:00
Paweł Jastrzębski
3ccb1a63aa Moved location of temp files 2013-08-12 13:03:03 +02:00
Paweł Jastrzębski
c8bb9b4f5f comic2panel: GUI support and itegration with comic2ebook 2013-08-12 12:59:58 +02:00
Paweł Jastrzębski
723be29118 Updated README 2013-08-11 14:56:26 +02:00
Paweł Jastrzębski
2865915cdf comic2panel: Autodetect border color 2013-08-11 14:47:06 +02:00
Paweł Jastrzębski
d3e0c2bb6e Improved detection of corrupted files 2013-08-11 12:58:34 +02:00
Paweł Jastrzębski
5e7ae73861 Updated README 2013-08-09 15:55:39 +02:00
Paweł Jastrzębski
61206b2169 Help cleanup 2013-08-09 15:25:00 +02:00
Paweł Jastrzębski
92e2a8913b No size limit anymore. KCC now split EPUB files before handling them to KindleGen. 2013-08-08 20:21:05 +02:00
Paweł Jastrzębski
ad827828d7 comic2panel: Fixed processing of last panel 2013-08-05 15:47:56 +02:00
Paweł Jastrzębski
faf16084a3 comic2panel: Multiprocessing support 2013-08-05 15:40:28 +02:00
Paweł Jastrzębski
38d2b55456 Added higly experimental parser of webstrips 2013-08-03 16:17:26 +02:00
Paweł Jastrzębski
abcebc54e8 Updated README 2013-08-03 10:23:45 +02:00
Paweł Jastrzębski
40cb963c99 Improved handling of slugification conflicts 2013-08-02 10:52:51 +02:00
Paweł Jastrzębski
9d267a6cc4 Updated README and version bump 2013-07-22 08:43:35 +02:00
Paweł Jastrzębski
462f24149b Implemented Devernay idea 2013-07-22 08:32:59 +02:00
Paweł Jastrzębski
4744b62f91 Interruption fix 2013-07-21 20:25:14 +02:00
Paweł Jastrzębski
08244e7fdc Conversion can be now interrupted 2013-07-21 20:18:04 +02:00
Paweł Jastrzębski
f64fb1bee1 Multiselect - OSX fix 2013-07-21 19:13:39 +02:00
Paweł Jastrzębski
743c3b2466 Added multiselect to add file/directory dialogs 2013-07-21 18:24:24 +02:00
Paweł Jastrzębski
aefa36fef8 Refactored KindleGen error handling 2013-07-11 13:25:32 +02:00
Paweł Jastrzębski
d7f6503196 Added profile: Kindle for Android 2013-07-11 11:47:18 +02:00
Paweł Jastrzębski
3375b8c898 Don't stop conversion if KindleGen return only warnings 2013-07-11 11:01:35 +02:00
Paweł Jastrzębski
52e5919ccd Bucket of dirty hax to improve readability on Linux/OSX 2013-07-10 09:05:44 +02:00
Paweł Jastrzębski
1c2e57adb6 Updated status messages 2013-07-10 08:50:52 +02:00
Paweł Jastrzębski
2e99d6ee0a Updated status messages 2013-07-10 08:29:14 +02:00
Paweł Jastrzębski
6744815f77 Fixed JobList scroll bars 2013-07-10 08:24:13 +02:00
Paweł Jastrzębski
0ba44ab2d3 HTML tag support for addMessage() 2013-07-10 07:51:19 +02:00
Paweł Jastrzębski
a6006450de Improved upscale/stretch options handling 2013-07-08 11:07:37 +02:00
Paweł Jastrzębski
c20e2ba451 Fixed row alignment (#55) 2013-07-04 15:32:15 +02:00
Paweł Jastrzębski
7a0b387c1c Small tweak of NoSplit option 2013-07-04 15:05:16 +02:00
Ciro Mattia Gonano
fdfe5fbe39 Disable horizontal mode check when "no split/rotate" is checked. 2013-07-04 11:50:20 +02:00
Paweł Jastrzębski
35751efad5 Little tweaks 2013-06-28 00:08:36 +02:00
Mateusz
7005c9e40a Fix misleading message when processing empty directories 2013-06-27 23:40:30 +02:00
Paweł Jastrzębski
118cf25ff7 Older UnRAR exit code is 7 when it is called without parameters 2013-06-27 18:13:39 +02:00
14 changed files with 1021 additions and 244 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ dist
kindlegen* kindlegen*
.DS_Store .DS_Store
Thumbs.db Thumbs.db
UnRAR.exe

View File

@@ -88,15 +88,18 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Enable image upscaling.&lt;br/&gt;Aspect ratio will be preserved.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Nothing&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will not be resized.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - Stretching&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will be resized. Aspect ratio will be not preserved.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Upscaling&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will be resized. Aspect ratio will be preserved.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Upscale images</string> <string>Stretch/Upscale</string>
</property>
<property name="tristate">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QCheckBox" name="StretchBox"> <widget class="QCheckBox" name="WebtoonBox">
<property name="font"> <property name="font">
<font> <font>
<pointsize>11</pointsize> <pointsize>11</pointsize>
@@ -106,10 +109,10 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:12pt;&quot;&gt;Enable image stretching.&lt;br/&gt;Aspect ratio will be not preserved.&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;EXPERIMENTAL!&lt;br/&gt;&lt;/span&gt;Enable auto-splitting of webtoons like &lt;span style=&quot; font-style:italic;&quot;&gt;Tower of God&lt;/span&gt; or &lt;span style=&quot; font-style:italic;&quot;&gt;Noblesse&lt;/span&gt;.&lt;br/&gt;Pages with a low width, high height and vertical panel flow.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Stretch images</string> <string>Webtoon mode</string>
</property> </property>
</widget> </widget>
</item> </item>

16
KCC.ui
View File

@@ -59,6 +59,9 @@
</font> </font>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item row="1" column="0"> <item row="1" column="0">
<widget class="QCheckBox" name="ProcessingBox"> <widget class="QCheckBox" name="ProcessingBox">
<property name="focusPolicy"> <property name="focusPolicy">
@@ -78,23 +81,26 @@
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable image upscaling.&lt;br/&gt;Aspect ratio will be preserved.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Nothing&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will not be resized.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Indeterminate - Stretching&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will be resized. Aspect ratio will be not preserved.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Upscaling&lt;br/&gt;&lt;/span&gt;Images smaller than device resolution will be resized. Aspect ratio will be preserved.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Upscale images</string> <string>Stretch/Upscale</string>
</property>
<property name="tristate">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="1"> <item row="3" column="1">
<widget class="QCheckBox" name="StretchBox"> <widget class="QCheckBox" name="WebtoonBox">
<property name="focusPolicy"> <property name="focusPolicy">
<enum>Qt::NoFocus</enum> <enum>Qt::NoFocus</enum>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Enable image stretching.&lt;br/&gt;Aspect ratio will be not preserved.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;EXPERIMENTAL!&lt;br/&gt;&lt;/span&gt;Enable auto-splitting of webtoons like &lt;span style=&quot; font-style:italic;&quot;&gt;Tower of God&lt;/span&gt; or &lt;span style=&quot; font-style:italic;&quot;&gt;Noblesse&lt;/span&gt;.&lt;br/&gt;Pages with a low width, high height and vertical panel flow.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Stretch images</string> <string>Webtoon mode</string>
</property> </property>
</widget> </widget>
</item> </item>

101
README.md
View File

@@ -24,9 +24,6 @@ You can find the latest released binary at the following links:
- **OS X:** [http://kcc.vulturis.eu/OSX/](http://kcc.vulturis.eu/OSX/) - **OS X:** [http://kcc.vulturis.eu/OSX/](http://kcc.vulturis.eu/OSX/)
- **Linux:** Just download sourcecode and launch: `python kcc.py` - **Linux:** Just download sourcecode and launch: `python kcc.py`
_It has been reported by a couple of users that version 2.10 crashing on OSX at start. We don't know if that issue still exist in version 3.0.
If it happens to you please append your message to [Issue #52](https://github.com/ciromattia/kcc/issues/52)._
## INPUT FORMATS ## INPUT FORMATS
**KCC** can understand and convert, at the moment, the following file types: **KCC** can understand and convert, at the moment, the following file types:
- PNG, JPG, GIF, TIFF, BMP - PNG, JPG, GIF, TIFF, BMP
@@ -50,15 +47,16 @@ If it happens to you please append your message to [Issue #52](https://github.co
* Use high quality source files. **This little detail have a major impact on the final result.** * Use high quality source files. **This little detail have a major impact on the final result.**
* Read tooltip of _High/Ultra quality_ option. There are many important informations there. * Read tooltip of _High/Ultra quality_ option. There are many important informations there.
* When converting images smaller than device resolution remember to enable upscaling. * When converting images smaller than device resolution remember to enable upscaling.
* Panel View (auto zooming every part of page) can be disabled directly on Kindle. There is no KCC option to do that.
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
* Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers. * Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers.
* The first image found will be set as the comic's cover. * The first image found will be set as the comic's cover.
* All files/directories will be added to EPUB in alphabetical order. * All files/directories will be added to EPUB in alphabetical order.
* Output MOBI file should be uploaded via USB. Other methods (e.g. via Calibre) might corrupt it. * Output MOBI file should be uploaded via USB. Other methods (e.g. via Calibre) might corrupt it.
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
### GUI ### GUI
Should be pretty self-explanatory. Should be pretty self-explanatory. All options have detailed informations in tooltips.
After completed conversion you should find ready file alongside the original input file (same directory). After completed conversion you should find ready file alongside the original input file (same directory).
### Standalone `comic2ebook.py` usage: ### Standalone `comic2ebook.py` usage:
@@ -67,30 +65,61 @@ After completed conversion you should find ready file alongside the original inp
Usage: comic2ebook.py [options] comic_file|comic_folder Usage: comic2ebook.py [options] comic_file|comic_folder
Options: Options:
--version show program's version number and exit MAIN:
-h, --help show this help message and exit -p PROFILE, --profile=PROFILE
-p PROFILE, --profile=PROFILE Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFA) [Default=KHD]
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) [Default=KHD] -q QUALITY, --quality=QUALITY
-t TITLE, --title=TITLE Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
Comic title [Default=filename] -m, --manga-style Manga style (Right-to-left reading and splitting)
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
--quality=QUALITY Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0] EXPERIMENTAL:
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB -w, --webtoon Webtoon processing mode
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False] OUTPUT SETTINGS:
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto] -o OUTPUT, --output=OUTPUT
--upscale Resize images smaller than device's resolution [Default=False] Output generated file to specified directory or file
--stretch Stretch images to device's resolution [Default=False] -t TITLE, --title=TITLE
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False] Comic title [Default=filename or directory name]
--rotate Rotate landscape pages instead of splitting them [Default=False] --cbz-output Outputs a CBZ archive and does not generate EPUB
--nosplitrotate Disable splitting and rotation [Default=False] --batchsplit Split output into multiple files
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
-o OUTPUT, --output=OUTPUT PROCESSING:
Output generated file (EPUB or CBZ) to specified directory or file --blackborders Use black borders instead of white ones
--forcecolor Do not convert images to grayscale [Default=False] --forcecolor Don't convert images to grayscale
--customwidth=WIDTH Replace screen width provided by device profile [Default=0] --forcepng Create PNG files instead JPEG (For non-Kindle devices)
--customheight=HEIGHT Replace screen height provided by device profile [Default=0] --gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
-v, --verbose Verbose output [Default=False] --nocutpagenumbers Don't try to cut page numbering on images
--noprocessing Don't apply image preprocessing
--nosplitrotate Disable splitting and rotation
--rotate Rotate landscape pages instead of splitting them
--stretch Stretch images to device's resolution
--upscale Resize images smaller than device's resolution
CUSTOM PROFILE:
--customwidth=CUSTOMWIDTH
Replace screen width provided by device profile
--customheight=CUSTOMHEIGHT
Replace screen height provided by device profile
OTHER:
-v, --verbose Verbose output
-h, --help Show this help message and exit
```
### Standalone `comic2panel.py` usage:
```
Usage: comic2panel.py [options] comic_folder
Options:
MANDATORY:
-y HEIGHT, --height=HEIGHT
Height of the target device screen
-i, --in-place Overwrite source directory
OTHER:
-d, --debug Create debug file for every splitted image
-h, --help Show this help message and exit
``` ```
## CREDITS ## CREDITS
@@ -213,8 +242,20 @@ The app relies and includes the following scripts/binaries:
* Added support for custom width/height * Added support for custom width/height
* Added option to disable color conversion * Added option to disable color conversion
####3.1:
* Added profile: Kindle for Android
* Add file/directory dialogs now support multiselect
* Many small fixes and tweaks
####3.2:
* Too big EPUB files are now splitted before conversion to MOBI
* Added experimental parser of manga webtoons
* Improved error handling
####3.2.1:
* Hotfixed crash occurring on OS with Russian locale
## KNOWN ISSUES ## KNOWN ISSUES
* _Add directory_ dialog allow to select multiple directories but they are not added to job list. [QT bug.](https://bugreports.qt-project.org/browse/QTBUG-21372)
* Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations. * Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations.
## COPYRIGHT ## COPYRIGHT

4
kcc.py
View File

@@ -16,8 +16,8 @@
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
#
__version__ = '3.0' __version__ = '3.2.1'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '3.0' __version__ = '3.2.1'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@@ -27,12 +27,14 @@ import sys
import shutil import shutil
import traceback import traceback
import urllib2 import urllib2
import time
import comic2ebook import comic2ebook
import kindlestrip import kindlestrip
from image import ProfileData from image import ProfileData
from subprocess import call, Popen, STDOUT, PIPE from subprocess import call, Popen, STDOUT, PIPE
from PyQt4 import QtGui, QtCore from PyQt4 import QtGui, QtCore
from xml.dom.minidom import parse from xml.dom.minidom import parse
from HTMLParser import HTMLParser
class Icons: class Icons:
@@ -57,6 +59,19 @@ class Icons:
self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
class HTMLStripper(HTMLParser):
def __init__(self):
HTMLParser.__init__(self)
self.reset()
self.fed = []
def handle_data(self, d):
self.fed.append(d)
def get_data(self):
return ''.join(self.fed)
# noinspection PyBroadException # noinspection PyBroadException
class VersionThread(QtCore.QThread): class VersionThread(QtCore.QThread):
def __init__(self, parent): def __init__(self, parent):
@@ -74,7 +89,8 @@ class VersionThread(QtCore.QThread):
return return
latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml() latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
if tuple(map(int, (latestVersion.split(".")))) > tuple(map(int, (__version__.split(".")))): if tuple(map(int, (latestVersion.split(".")))) > tuple(map(int, (__version__.split(".")))):
self.emit(QtCore.SIGNAL("addMessage"), 'New version is available!', 'warning') self.emit(QtCore.SIGNAL("addMessage"), '<a href="http://kcc.vulturis.eu/">'
'<b>New version is available!</b></a>', 'warning')
# noinspection PyBroadException # noinspection PyBroadException
@@ -86,6 +102,16 @@ class WorkerThread(QtCore.QThread):
def __del__(self): def __del__(self):
self.wait() self.wait()
def sync(self):
self.conversionAlive = self.parent.conversionAlive
def clean(self):
self.parent.needClean = True
self.emit(QtCore.SIGNAL("hideProgressBar"))
self.emit(QtCore.SIGNAL("addMessage"), '<b>Conversion interrupted.</b>', 'error')
self.emit(QtCore.SIGNAL("modeConvert"), True)
# noinspection PyUnboundLocalVariable
def run(self): def run(self):
self.emit(QtCore.SIGNAL("modeConvert"), False) self.emit(QtCore.SIGNAL("modeConvert"), False)
profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())] profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())]
@@ -102,31 +128,40 @@ class WorkerThread(QtCore.QThread):
if self.parent.currentMode > 1: if self.parent.currentMode > 1:
if GUI.ProcessingBox.isChecked(): if GUI.ProcessingBox.isChecked():
argv.append("--noprocessing") argv.append("--noprocessing")
if GUI.UpscaleBox.isChecked() and not GUI.StretchBox.isChecked():
argv.append("--upscale")
if GUI.NoRotateBox.isChecked(): if GUI.NoRotateBox.isChecked():
argv.append("--nosplitrotate") argv.append("--nosplitrotate")
if GUI.BorderBox.isChecked(): if GUI.BorderBox.isChecked():
argv.append("--blackborders") argv.append("--blackborders")
if GUI.StretchBox.isChecked(): if GUI.UpscaleBox.checkState() == 1:
argv.append("--stretch") argv.append("--stretch")
elif GUI.UpscaleBox.checkState() == 2:
argv.append("--upscale")
if GUI.NoDitheringBox.isChecked(): if GUI.NoDitheringBox.isChecked():
argv.append("--forcepng") argv.append("--forcepng")
if GUI.WebtoonBox.isChecked():
argv.append("--webtoon")
if float(self.parent.GammaValue) > 0.09: if float(self.parent.GammaValue) > 0.09:
argv.append("--gamma=" + self.parent.GammaValue) argv.append("--gamma=" + self.parent.GammaValue)
if str(GUI.FormatBox.currentText()) == 'CBZ': if str(GUI.FormatBox.currentText()) == 'CBZ':
argv.append("--cbz-output") argv.append("--cbz-output")
if str(GUI.FormatBox.currentText()) == 'MOBI':
argv.append("--batchsplit")
if self.parent.currentMode > 2: if self.parent.currentMode > 2:
argv.append("--customwidth=" + str(GUI.customWidth.text())) argv.append("--customwidth=" + str(GUI.customWidth.text()))
argv.append("--customheight=" + str(GUI.customHeight.text())) argv.append("--customheight=" + str(GUI.customHeight.text()))
if GUI.ColorBox.isChecked(): if GUI.ColorBox.isChecked():
argv.append("--forcecolor") argv.append("--forcecolor")
for i in range(GUI.JobList.count()): for i in range(GUI.JobList.count()):
currentJobs.append(str(GUI.JobList.item(i).text())) if GUI.JobList.item(i).icon().isNull():
currentJobs.append(str(GUI.JobList.item(i).text()))
GUI.JobList.clear() GUI.JobList.clear()
for job in currentJobs: for job in currentJobs:
time.sleep(0.5)
if not self.conversionAlive:
self.clean()
return
self.errors = False self.errors = False
self.emit(QtCore.SIGNAL("addMessage"), 'Source: ' + job, 'info') self.emit(QtCore.SIGNAL("addMessage"), '<b>Source:</b> ' + job, 'info')
if str(GUI.FormatBox.currentText()) == 'CBZ': if str(GUI.FormatBox.currentText()) == 'CBZ':
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file...', 'info') self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file...', 'info')
else: else:
@@ -136,30 +171,80 @@ class WorkerThread(QtCore.QThread):
try: try:
outputPath = comic2ebook.main(jobargv, self) outputPath = comic2ebook.main(jobargv, self)
self.emit(QtCore.SIGNAL("hideProgressBar")) self.emit(QtCore.SIGNAL("hideProgressBar"))
except UserWarning as warn:
if not self.conversionAlive:
self.clean()
return
else:
self.errors = True
self.emit(QtCore.SIGNAL("addMessage"), str(warn), 'warning')
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create output file!', 'warning')
except Exception as err: except Exception as err:
self.errors = True self.errors = True
type_, value_, traceback_ = sys.exc_info() type_, value_, traceback_ = sys.exc_info()
self.emit(QtCore.SIGNAL("showDialog"), "Error during conversion %s:\n\n%s\n\nTraceback:\n%s" self.emit(QtCore.SIGNAL("showDialog"), "Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err), traceback.format_tb(traceback_))) % (jobargv[-1], str(err), traceback.format_tb(traceback_)))
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create EPUB!', 'error') self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create EPUB!', 'error')
if not self.conversionAlive:
for item in outputPath:
if os.path.exists(item):
os.remove(item)
self.clean()
return
if not self.errors: if not self.errors:
if str(GUI.FormatBox.currentText()) == 'CBZ': if str(GUI.FormatBox.currentText()) == 'CBZ':
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file... Done!', 'info', True) self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file... Done!', 'info', True)
else: else:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True) self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True)
if str(GUI.FormatBox.currentText()) == 'MOBI': if str(GUI.FormatBox.currentText()) == 'MOBI':
if not os.path.getsize(outputPath) > 314572800: tomeNumber = 0
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info') for item in outputPath:
tomeNumber += 1
if len(outputPath) > 1:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber)
+ '/' + str(len(outputPath)) + ')...', 'info')
else:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
self.emit(QtCore.SIGNAL("progressBarTick"), 1) self.emit(QtCore.SIGNAL("progressBarTick"), 1)
try: try:
retcode = call('kindlegen -verbose "' + outputPath + '"', shell=True) self.kindlegenErrorCode = 0
if os.path.getsize(item) < 367001600:
output = Popen('kindlegen -locale en "' + item + '"', stdout=PIPE, stderr=STDOUT, shell=True)
for line in output.stdout:
# ERROR: Generic error
if "Error(" in line:
self.kindlegenErrorCode = 1
self.kindlegenError = line
# ERROR: EPUB too big
if ":E23026:" in line:
self.kindlegenErrorCode = 23026
if self.kindlegenErrorCode > 0:
break
else:
# ERROR: EPUB too big
self.kindlegenErrorCode = 23026
except: except:
# ERROR: Unknown generic error
self.kindlegenErrorCode = 1
continue continue
if retcode == 0: if not self.conversionAlive:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True) for item in outputPath:
if os.path.exists(item):
os.remove(item)
if os.path.exists(item.replace('.epub', '.mobi')):
os.remove(item.replace('.epub', '.mobi'))
self.clean()
return
if self.kindlegenErrorCode == 0:
if len(outputPath) > 1:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber) + '/'
+ str(len(outputPath)) + ')... Done!', 'info',
True)
else:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True)
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header...', 'info') self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header...', 'info')
os.remove(outputPath) os.remove(item)
mobiPath = outputPath.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
shutil.move(mobiPath, mobiPath + '_tostrip') shutil.move(mobiPath, mobiPath + '_tostrip')
try: try:
kindlestrip.main((mobiPath + '_tostrip', mobiPath)) kindlestrip.main((mobiPath + '_tostrip', mobiPath))
@@ -175,62 +260,80 @@ class WorkerThread(QtCore.QThread):
self.emit(QtCore.SIGNAL("addMessage"), self.emit(QtCore.SIGNAL("addMessage"),
'MOBI file will work correctly but it will be highly oversized.', 'warning') 'MOBI file will work correctly but it will be highly oversized.', 'warning')
else: else:
os.remove(outputPath) epubSize = (os.path.getsize(item))/1024/1024
if os.path.exists(outputPath.replace('.epub', '.mobi')): os.remove(item)
os.remove(outputPath.replace('.epub', '.mobi')) if os.path.exists(item.replace('.epub', '.mobi')):
os.remove(item.replace('.epub', '.mobi'))
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error') self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
self.emit(QtCore.SIGNAL("addMessage"), 'Try converting a smaller batch.', 'error') if self.kindlegenErrorCode == 1 and self.kindlegenError:
else: self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
excess = (os.path.getsize(outputPath) - 314572800)/1024/1024 if self.kindlegenErrorCode == 23026:
os.remove(outputPath) self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file is too big for KindleGen!', 'error') 'error')
self.emit(QtCore.SIGNAL("addMessage"), 'Limit exceeded by ' + str(excess) + self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
' MB. Try converting a smaller batch.', 'error') ' Supported size: ~300MB.', 'error')
self.emit(QtCore.SIGNAL("hideProgressBar")) self.emit(QtCore.SIGNAL("hideProgressBar"))
self.parent.needClean = True self.parent.needClean = True
self.emit(QtCore.SIGNAL("addMessage"), 'All jobs completed.', 'info') self.emit(QtCore.SIGNAL("addMessage"), '<b>All jobs completed.</b>', 'info')
self.emit(QtCore.SIGNAL("modeConvert"), True) self.emit(QtCore.SIGNAL("modeConvert"), True)
# noinspection PyBroadException # noinspection PyBroadException
class Ui_KCC(object): class Ui_KCC(object):
def selectDir(self): def selectDir(self):
# Dialog allow to select multiple directories but we can't parse that. QT Bug.
if self.needClean: if self.needClean:
self.needClean = False self.needClean = False
GUI.JobList.clear() GUI.JobList.clear()
dname = QtGui.QFileDialog.getExistingDirectory(MainWindow, 'Select directory', self.lastPath) # Dirty, dirty way but OS native QFileDialogs don't support directory multiselect
dirDialog = QtGui.QFileDialog(MainWindow, 'Select directory', self.lastPath)
dirDialog.setFileMode(dirDialog.Directory)
dirDialog.setOption(dirDialog.ShowDirsOnly, True)
dirDialog.setOption(dirDialog.DontUseNativeDialog, True)
l = dirDialog.findChild(QtGui.QListView, "listView")
t = dirDialog.findChild(QtGui.QTreeView)
if l:
l.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
if t:
t.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
if dirDialog.exec_() == 1:
dnames = dirDialog.selectedFiles()
else:
dnames = ""
# Lame UTF-8 security measure # Lame UTF-8 security measure
try: for dname in dnames:
str(dname) try:
except Exception: str(dname)
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.", except Exception:
QtGui.QMessageBox.Ok) QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
return QtGui.QMessageBox.Ok)
if str(dname) != "": return
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir)) if str(dname) != "":
GUI.JobList.addItem(dname) if sys.platform == 'win32':
dname = dname.replace('/', '\\')
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
GUI.JobList.addItem(dname)
def selectFile(self): def selectFile(self):
if self.needClean: if self.needClean:
self.needClean = False self.needClean = False
GUI.JobList.clear() GUI.JobList.clear()
if self.UnRAR: if self.UnRAR:
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath, fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
'*.cbz *.cbr *.zip *.rar *.pdf') '*.cbz *.cbr *.zip *.rar *.pdf')
else: else:
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath, fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
'*.cbz *.zip *.pdf') '*.cbz *.zip *.pdf')
# Lame UTF-8 security measure # Lame UTF-8 security measure
try: for fname in fnames:
str(fname) try:
except Exception: str(fname)
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.", except Exception:
QtGui.QMessageBox.Ok) QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
return QtGui.QMessageBox.Ok)
if str(fname) != "": return
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir)) if str(fname) != "":
GUI.JobList.addItem(fname) self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
GUI.JobList.addItem(fname)
def clearJobs(self): def clearJobs(self):
GUI.JobList.clear() GUI.JobList.clear()
@@ -268,15 +371,21 @@ class Ui_KCC(object):
GUI.OptionsExpert.setEnabled(False) GUI.OptionsExpert.setEnabled(False)
GUI.MangaBox.setEnabled(True) GUI.MangaBox.setEnabled(True)
def modeExpert(self): def modeExpert(self, KFA=False):
self.modeAdvanced() self.modeAdvanced()
self.currentMode = 3 self.currentMode = 3
MainWindow.setMinimumSize(QtCore.QSize(420, 380)) MainWindow.setMinimumSize(QtCore.QSize(420, 380))
MainWindow.setMaximumSize(QtCore.QSize(420, 380)) MainWindow.setMaximumSize(QtCore.QSize(420, 380))
MainWindow.resize(420, 380) MainWindow.resize(420, 380)
GUI.OptionsExpert.setEnabled(True) GUI.OptionsExpert.setEnabled(True)
GUI.MangaBox.setCheckState(0) if KFA:
GUI.MangaBox.setEnabled(False) GUI.ColorBox.setCheckState(2)
GUI.FormatBox.setCurrentIndex(0)
GUI.FormatBox.setEnabled(False)
else:
GUI.FormatBox.setEnabled(True)
GUI.MangaBox.setCheckState(0)
GUI.MangaBox.setEnabled(False)
def modeConvert(self, enable): def modeConvert(self, enable):
if self.currentMode != 3: if self.currentMode != 3:
@@ -286,19 +395,33 @@ class Ui_KCC(object):
GUI.ClearButton.setEnabled(enable) GUI.ClearButton.setEnabled(enable)
GUI.FileButton.setEnabled(enable) GUI.FileButton.setEnabled(enable)
GUI.DeviceBox.setEnabled(enable) GUI.DeviceBox.setEnabled(enable)
GUI.ConvertButton.setEnabled(enable)
GUI.FormatBox.setEnabled(enable) GUI.FormatBox.setEnabled(enable)
GUI.OptionsBasic.setEnabled(enable) GUI.OptionsBasic.setEnabled(enable)
GUI.OptionsAdvanced.setEnabled(enable) GUI.OptionsAdvanced.setEnabled(enable)
GUI.OptionsAdvancedGamma.setEnabled(enable) GUI.OptionsAdvancedGamma.setEnabled(enable)
GUI.OptionsExpert.setEnabled(enable) GUI.OptionsExpert.setEnabled(enable)
if enable: if enable:
self.conversionAlive = False
self.worker.sync()
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
GUI.ConvertButton.setIcon(icon)
GUI.ConvertButton.setText('Convert')
GUI.ConvertButton.setEnabled(True)
if self.currentMode == 1: if self.currentMode == 1:
self.modeBasic() self.modeBasic()
elif self.currentMode == 2: elif self.currentMode == 2:
self.modeAdvanced() self.modeAdvanced()
elif self.currentMode == 3: elif self.currentMode == 3:
self.modeExpert() self.modeExpert()
else:
self.conversionAlive = True
self.worker.sync()
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
GUI.ConvertButton.setIcon(icon)
GUI.ConvertButton.setText('Abort')
GUI.ConvertButton.setEnabled(True)
def changeGamma(self, value): def changeGamma(self, value):
value = float(value) value = float(value)
@@ -309,30 +432,70 @@ class Ui_KCC(object):
GUI.GammaLabel.setText('Gamma: ' + str(value)) GUI.GammaLabel.setText('Gamma: ' + str(value))
self.GammaValue = value self.GammaValue = value
def changeDevice(self, value, start=False): def toggleWebtoonBox(self, value):
if value == 11 and (start or self.currentMode != 3): if value:
GUI.NoRotateBox.setEnabled(False)
GUI.NoRotateBox.setChecked(True)
GUI.QualityBox.setEnabled(False)
GUI.QualityBox.setChecked(False)
GUI.BorderBox.setEnabled(False)
GUI.BorderBox.setChecked(False)
self.addMessage('If images are color setting <i>Gamma</i> to 1.0 is recommended.', 'info')
else:
GUI.NoRotateBox.setEnabled(True)
GUI.QualityBox.setEnabled(True)
GUI.BorderBox.setEnabled(True)
def toggleNoSplitRotate(self, value):
if value:
GUI.RotateBox.setEnabled(False)
GUI.RotateBox.setChecked(False)
else:
GUI.RotateBox.setEnabled(True)
def changeDevice(self, value):
if value == 12:
GUI.BasicModeButton.setEnabled(False) GUI.BasicModeButton.setEnabled(False)
GUI.AdvModeButton.setEnabled(False) GUI.AdvModeButton.setEnabled(False)
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
'List of supported Non-Kindle devices</a>', 'info')
self.modeExpert() self.modeExpert()
elif value == 11:
GUI.BasicModeButton.setEnabled(False)
GUI.AdvModeButton.setEnabled(False)
self.modeExpert(True)
elif self.currentMode == 3: elif self.currentMode == 3:
GUI.BasicModeButton.setEnabled(True) GUI.BasicModeButton.setEnabled(True)
GUI.AdvModeButton.setEnabled(True) GUI.AdvModeButton.setEnabled(True)
self.modeBasic() self.modeBasic()
if value in [0, 1, 5, 6, 7, 8, 9, 11]: if value in [0, 1, 5, 6, 7, 8, 9, 12]:
GUI.QualityBox.setCheckState(0) GUI.QualityBox.setCheckState(0)
GUI.QualityBox.setEnabled(False) GUI.QualityBox.setEnabled(False)
else: else:
GUI.QualityBox.setEnabled(True) GUI.QualityBox.setEnabled(True)
def stripTags(self, html):
s = HTMLStripper()
s.feed(html)
return s.get_data()
def addMessage(self, message, icon=None, replace=False): def addMessage(self, message, icon=None, replace=False):
if icon: if icon:
icon = eval('self.icons.' + icon) icon = eval('self.icons.' + icon)
item = QtGui.QListWidgetItem(icon, message) item = QtGui.QListWidgetItem(icon, ' ' + self.stripTags(message))
else: else:
item = QtGui.QListWidgetItem(message) item = QtGui.QListWidgetItem(' ' + self.stripTags(message))
if replace: if replace:
GUI.JobList.takeItem(GUI.JobList.count()-1) GUI.JobList.takeItem(GUI.JobList.count()-1)
label = QtGui.QLabel(message)
label.setOpenExternalLinks(True)
if sys.platform == 'darwin':
font = QtGui.QFont()
font.setPointSize(11)
label.setFont(font)
item.setTextColor(QtGui.QColor("white"))
GUI.JobList.addItem(item) GUI.JobList.addItem(item)
GUI.JobList.setItemWidget(item, label)
GUI.JobList.scrollToBottom() GUI.JobList.scrollToBottom()
def showDialog(self, message): def showDialog(self, message):
@@ -340,7 +503,6 @@ class Ui_KCC(object):
def updateProgressbar(self, new=False, status=False): def updateProgressbar(self, new=False, status=False):
if new == "status": if new == "status":
pass
GUI.ProgressBar.setFormat(status) GUI.ProgressBar.setFormat(status)
elif new: elif new:
GUI.ProgressBar.setMaximum(new - 1) GUI.ProgressBar.setMaximum(new - 1)
@@ -350,25 +512,38 @@ class Ui_KCC(object):
GUI.ProgressBar.setValue(GUI.ProgressBar.value() + 1) GUI.ProgressBar.setValue(GUI.ProgressBar.value() + 1)
def convertStart(self): def convertStart(self):
if self.needClean: if self.conversionAlive:
self.needClean = False GUI.ConvertButton.setEnabled(False)
GUI.JobList.clear() self.addMessage('Process will be interrupted. Please wait.', 'warning')
if GUI.JobList.count() == 0: self.conversionAlive = False
self.addMessage('No files selected! Please choose files to convert.', 'error') self.worker.sync()
self.needClean = True else:
return if self.needClean:
if self.currentMode > 2 and (str(GUI.customWidth.text()) == '' or str(GUI.customHeight.text()) == ''): self.needClean = False
GUI.JobList.clear() GUI.JobList.clear()
self.addMessage('Target resolution is not set!', 'error') if GUI.JobList.count() == 0:
self.needClean = True self.addMessage('No files selected! Please choose files to convert.', 'error')
return self.needClean = True
self.worker.start() return
if self.currentMode > 2 and (str(GUI.customWidth.text()) == '' or str(GUI.customHeight.text()) == ''):
GUI.JobList.clear()
self.addMessage('Target resolution is not set!', 'error')
self.needClean = True
return
self.worker.start()
def hideProgressBar(self): def hideProgressBar(self):
GUI.ProgressBar.hide() GUI.ProgressBar.hide()
# noinspection PyUnusedLocal
def saveSettings(self, event): def saveSettings(self, event):
if self.conversionAlive:
GUI.ConvertButton.setEnabled(False)
self.addMessage('Process will be interrupted. Please wait.', 'warning')
self.conversionAlive = False
self.worker.sync()
event.ignore()
if not GUI.ConvertButton.isEnabled():
event.ignore()
self.settings.setValue('lastPath', self.lastPath) self.settings.setValue('lastPath', self.lastPath)
self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex()) self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex())
self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex()) self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex())
@@ -380,7 +555,7 @@ class Ui_KCC(object):
'UpscaleBox': GUI.UpscaleBox.checkState(), 'UpscaleBox': GUI.UpscaleBox.checkState(),
'NoRotateBox': GUI.NoRotateBox.checkState(), 'NoRotateBox': GUI.NoRotateBox.checkState(),
'BorderBox': GUI.BorderBox.checkState(), 'BorderBox': GUI.BorderBox.checkState(),
'StretchBox': GUI.StretchBox.checkState(), 'WebtoonBox': GUI.WebtoonBox.checkState(),
'NoDitheringBox': GUI.NoDitheringBox.checkState(), 'NoDitheringBox': GUI.NoDitheringBox.checkState(),
'ColorBox': GUI.ColorBox.checkState(), 'ColorBox': GUI.ColorBox.checkState(),
'customWidth': GUI.customWidth.text(), 'customWidth': GUI.customWidth.text(),
@@ -403,30 +578,36 @@ class Ui_KCC(object):
self.options = self.options.toPyObject() self.options = self.options.toPyObject()
self.worker = WorkerThread(self) self.worker = WorkerThread(self)
self.versionCheck = VersionThread(self) self.versionCheck = VersionThread(self)
self.conversionAlive = False
self.needClean = True self.needClean = True
self.addMessage('Welcome!', 'info') self.addMessage('<b>Welcome!</b>', 'info')
self.addMessage('Remember: all options have additional informations in tooltips.', 'info') self.addMessage('<b>Remember:</b> All options have additional informations in tooltips.', 'info')
if call('kindlegen', stdout=PIPE, stderr=STDOUT, shell=True) == 0: if call('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
self.KindleGen = True self.KindleGen = True
formats = ['MOBI', 'EPUB', 'CBZ'] formats = ['MOBI', 'EPUB', 'CBZ']
versionCheck = Popen('kindlegen', stdout=PIPE, stderr=STDOUT, shell=True) versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True)
for line in versionCheck.stdout: for line in versionCheck.stdout:
if "Amazon kindlegen" in line: if "Amazon kindlegen" in line:
versionCheck = line.split('V')[1].split(' ')[0] versionCheck = line.split('V')[1].split(' ')[0]
if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))): if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))):
self.addMessage('Your kindlegen is outdated! Creating MOBI might fail.' self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
' Please update kindlegen from Amazon\'s website.', 'warning') '1000765211">kindlegen</a> is outdated! Creating MOBI might fail.'
' Please update <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'1000765211">kindlegen</a> from Amazon\'s website.', 'warning')
break break
else: else:
self.KindleGen = False self.KindleGen = False
formats = ['EPUB', 'CBZ'] formats = ['EPUB', 'CBZ']
self.addMessage('Cannot find kindlegen in PATH! MOBI creation will be disabled.', 'warning') self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
if call('unrar', stdout=PIPE, stderr=STDOUT, shell=True) == 0: '1000765211">kindlegen</a> in PATH! MOBI creation will be disabled.', 'warning')
rarExitCode = call('unrar', stdout=PIPE, stderr=STDOUT, shell=True)
if rarExitCode == 0 or rarExitCode == 7:
self.UnRAR = True self.UnRAR = True
else: else:
self.UnRAR = False self.UnRAR = False
self.addMessage('Cannot find UnRAR! Processing of CBR/RAR files will be disabled.', 'warning') self.addMessage('Cannot find <a href="http://www.rarlab.com/rar_add.htm">UnRAR</a>!'
' Processing of CBR/RAR files will be disabled.', 'warning')
GUI.BasicModeButton.clicked.connect(self.modeBasic) GUI.BasicModeButton.clicked.connect(self.modeBasic)
GUI.AdvModeButton.clicked.connect(self.modeAdvanced) GUI.AdvModeButton.clicked.connect(self.modeAdvanced)
@@ -435,6 +616,8 @@ class Ui_KCC(object):
GUI.FileButton.clicked.connect(self.selectFile) GUI.FileButton.clicked.connect(self.selectFile)
GUI.ConvertButton.clicked.connect(self.convertStart) GUI.ConvertButton.clicked.connect(self.convertStart)
GUI.GammaSlider.valueChanged.connect(self.changeGamma) GUI.GammaSlider.valueChanged.connect(self.changeGamma)
GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
GUI.WebtoonBox.stateChanged.connect(self.toggleWebtoonBox)
GUI.DeviceBox.activated.connect(self.changeDevice) GUI.DeviceBox.activated.connect(self.changeDevice)
KCC.connect(self.worker, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar) KCC.connect(self.worker, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar)
KCC.connect(self.worker, QtCore.SIGNAL("modeConvert"), self.modeConvert) KCC.connect(self.worker, QtCore.SIGNAL("modeConvert"), self.modeConvert)
@@ -467,6 +650,8 @@ class Ui_KCC(object):
elif str(option) == "GammaSlider": elif str(option) == "GammaSlider":
GUI.GammaSlider.setValue(int(self.options[option])) GUI.GammaSlider.setValue(int(self.options[option]))
self.changeGamma(int(self.options[option])) self.changeGamma(int(self.options[option]))
elif str(option) == "StretchBox" or str(option) == "WebstripBox":
pass
else: else:
eval('GUI.' + str(option)).setCheckState(self.options[option]) eval('GUI.' + str(option)).setCheckState(self.options[option])
if self.currentMode == 1: if self.currentMode == 1:
@@ -477,4 +662,5 @@ class Ui_KCC(object):
self.modeExpert() self.modeExpert()
self.versionCheck.start() self.versionCheck.start()
self.hideProgressBar() self.hideProgressBar()
self.changeDevice(self.lastDevice, True) self.changeDevice(self.lastDevice)
self.worker.sync()

View File

@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'KCC.ui' # Form implementation generated from reading ui file 'KCC.ui'
# #
# Created: Fri Jun 21 18:23:19 2013 # Created: Wed Aug 14 08:39:46 2013
# by: PyQt4 UI code generator 4.10.1 # by: PyQt4 UI code generator 4.10.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -47,6 +47,7 @@ class Ui_KCC(object):
self.OptionsAdvanced.setFont(font) self.OptionsAdvanced.setFont(font)
self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced")) self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced"))
self.gridLayout = QtGui.QGridLayout(self.OptionsAdvanced) self.gridLayout = QtGui.QGridLayout(self.OptionsAdvanced)
self.gridLayout.setContentsMargins(9, -1, -1, -1)
self.gridLayout.setObjectName(_fromUtf8("gridLayout")) self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced) self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus) self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus)
@@ -54,12 +55,13 @@ class Ui_KCC(object):
self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1) self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1)
self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced) self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus) self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.UpscaleBox.setTristate(True)
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox")) self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1) self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced) self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus) self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.StretchBox.setObjectName(_fromUtf8("StretchBox")) self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1) self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced) self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus) self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox")) self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox"))
@@ -262,10 +264,10 @@ class Ui_KCC(object):
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None)) KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", None)) self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", None))
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None)) self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image upscaling.<br/>Aspect ratio will be preserved.</p></body></html>", None)) self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None)) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image stretching.<br/>Aspect ratio will be not preserved.</p></body></html>", None)) self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
self.StretchBox.setText(_translate("KCC", "Stretch images", None)) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p>Create PNG files instead JPEG.<br/><span style=\" font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None)) self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p>Create PNG files instead JPEG.<br/><span style=\" font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None)) self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
self.BorderBox.setToolTip(_translate("KCC", "Fill space around images with black color.", None)) self.BorderBox.setToolTip(_translate("KCC", "Fill space around images with black color.", None))

View File

@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file 'KCC-OSX.ui' # Form implementation generated from reading ui file 'KCC-OSX.ui'
# #
# Created: Fri Jun 21 18:23:35 2013 # Created: Wed Aug 14 08:39:45 2013
# by: PyQt4 UI code generator 4.10.1 # by: PyQt4 UI code generator 4.10.2
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -60,15 +60,16 @@ class Ui_KCC(object):
font.setPointSize(11) font.setPointSize(11)
self.UpscaleBox.setFont(font) self.UpscaleBox.setFont(font)
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus) self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.UpscaleBox.setTristate(True)
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox")) self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1) self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced) self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(11) font.setPointSize(11)
self.StretchBox.setFont(font) self.WebtoonBox.setFont(font)
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus) self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.StretchBox.setObjectName(_fromUtf8("StretchBox")) self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1) self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced) self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
font = QtGui.QFont() font = QtGui.QFont()
font.setPointSize(11) font.setPointSize(11)
@@ -321,10 +322,10 @@ class Ui_KCC(object):
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None)) KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Disable image optimizations.</span></p></body></html>", None)) self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Disable image optimizations.</span></p></body></html>", None))
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None)) self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image upscaling.<br/>Aspect ratio will be preserved.</span></p></body></html>", None)) self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None)) self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image stretching.<br/>Aspect ratio will be not preserved.</span></p></body></html>", None)) self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
self.StretchBox.setText(_translate("KCC", "Stretch images", None)) self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Create PNG files instead JPEG.<br/></span><span style=\" font-size:12pt; font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None)) self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Create PNG files instead JPEG.<br/></span><span style=\" font-size:12pt; font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None)) self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
self.BorderBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Fill space around images with black color.</span></p></body></html>", None)) self.BorderBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Fill space around images with black color.</span></p></body></html>", None))

View File

@@ -1,4 +1,4 @@
__version__ = '3.0' __version__ = '3.2.1'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__version__ = '3.0' __version__ = '3.2.1'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>' __copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'
@@ -28,17 +28,14 @@ import tempfile
import re import re
import stat import stat
import string import string
from shutil import move from shutil import move, copyfile, copytree, rmtree, make_archive
from shutil import copyfile from optparse import OptionParser, OptionGroup
from shutil import copytree
from shutil import rmtree
from shutil import make_archive
from optparse import OptionParser
from multiprocessing import Pool, Queue, freeze_support from multiprocessing import Pool, Queue, freeze_support
try: try:
from PyQt4 import QtCore from PyQt4 import QtCore
except ImportError: except ImportError:
QtCore = None QtCore = None
import comic2panel
import image import image
import cbxarchive import cbxarchive
import pdfjpgextract import pdfjpgextract
@@ -326,16 +323,17 @@ def getImageFileName(imgfile):
def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5): def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5):
img.cropWhiteSpace(10.0) if not options.webtoon:
if options.cutpagenumbers: img.cropWhiteSpace(10.0)
if options.cutpagenumbers and not options.webtoon:
img.cutPageNumber() img.cutPageNumber()
img.optimizeImage(options.gamma)
if overrideQuality != 5: if overrideQuality != 5:
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
options.landscapemode, overrideQuality) options.landscapemode, overrideQuality)
else: else:
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight, img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
options.landscapemode, options.quality) options.landscapemode, options.quality)
img.optimizeImage(options.gamma)
if options.forcepng and not options.forcecolor: if options.forcepng and not options.forcecolor:
img.quantizeImage() img.quantizeImage()
@@ -361,16 +359,20 @@ def dirImgProcess(path):
while not splitpages.ready(): while not splitpages.ready():
# noinspection PyBroadException # noinspection PyBroadException
try: try:
queue.get(True, 1) queue.get(True, 5)
except: except:
pass pass
if not GUI.conversionAlive:
pool.terminate()
rmtree(os.path.join(path, '..', '..'))
raise UserWarning("Conversion interrupted.")
GUI.emit(QtCore.SIGNAL("progressBarTick")) GUI.emit(QtCore.SIGNAL("progressBarTick"))
pool.join() pool.join()
queue.close() queue.close()
try: try:
splitpages = splitpages.get() splitpages = splitpages.get()
except: except:
rmtree(path) rmtree(os.path.join(path, '..', '..'))
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1])) raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
splitpages = filter(None, splitpages) splitpages = filter(None, splitpages)
splitpages.sort() splitpages.sort()
@@ -379,6 +381,9 @@ def dirImgProcess(path):
splitCount += 1 splitCount += 1
pagenumbermodifier += 1 pagenumbermodifier += 1
pagenumbermodifier += 1 pagenumbermodifier += 1
else:
rmtree(os.path.join(path, '..', '..'))
raise UserWarning("Source directory is empty.")
def fileImgProcess_init(queue, options): def fileImgProcess_init(queue, options):
@@ -448,7 +453,6 @@ def genEpubStruct(path):
chapterlist = [] chapterlist = []
cover = None cover = None
_, deviceres, _, _, panelviewsize = options.profileData _, deviceres, _, _, panelviewsize = options.profileData
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
os.mkdir(os.path.join(path, 'OEBPS', 'Text')) os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w') f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT. # DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
@@ -594,8 +598,9 @@ def genEpubStruct(path):
def getWorkFolder(afile): def getWorkFolder(afile):
workdir = tempfile.mkdtemp()
if os.path.isdir(afile): if os.path.isdir(afile):
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.join(os.path.splitext(afile)[0], '..'))
try: try:
os.rmdir(workdir) # needed for copytree() fails if dst already exists os.rmdir(workdir) # needed for copytree() fails if dst already exists
fullPath = os.path.join(workdir, 'OEBPS', 'Images') fullPath = os.path.join(workdir, 'OEBPS', 'Images')
@@ -607,8 +612,13 @@ def getWorkFolder(afile):
raise raise
elif afile.lower().endswith('.pdf'): elif afile.lower().endswith('.pdf'):
pdf = pdfjpgextract.PdfJpgExtract(afile) pdf = pdfjpgextract.PdfJpgExtract(afile)
path = pdf.extract() path, njpg = pdf.extract()
if njpg == 0:
rmtree(path)
raise UserWarning("Failed to extract images.")
else: else:
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.dirname(afile))
cbx = cbxarchive.CBxArchive(afile) cbx = cbxarchive.CBxArchive(afile)
if cbx.isCbxFile(): if cbx.isCbxFile():
try: try:
@@ -647,13 +657,16 @@ def sanitizeTree(filetree):
splitname = os.path.splitext(name) splitname = os.path.splitext(name)
slugified = slugify(splitname[0]) slugified = slugify(splitname[0])
while os.path.exists(os.path.join(root, slugified + splitname[1])): while os.path.exists(os.path.join(root, slugified + splitname[1])):
slugified += "1" slugified += "A"
os.rename(os.path.join(root, name), os.path.join(root, slugified + splitname[1])) os.rename(os.path.join(root, name), os.path.join(root, slugified + splitname[1]))
for name in dirs: for name in dirs:
if name.startswith('.'): if name.startswith('.'):
os.remove(os.path.join(root, name)) os.remove(os.path.join(root, name))
else: else:
os.rename(os.path.join(root, name), os.path.join(root, slugify(name))) slugified = slugify(name)
while os.path.exists(os.path.join(root, slugified)):
slugified += "A"
os.rename(os.path.join(root, name), os.path.join(root, slugified))
def sanitizeTreeBeforeConversion(filetree): def sanitizeTreeBeforeConversion(filetree):
@@ -667,6 +680,158 @@ def sanitizeTreeBeforeConversion(filetree):
os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC) os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
def getDirectorySize(start_path='.'):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
total_size += os.path.getsize(fp)
return total_size
def createNewTome(parentPath):
tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-')
#tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-', parentPath)
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
os.makedirs(tomePath)
return tomePath, tomePathRoot
def walkLevel(some_dir, level=1):
some_dir = some_dir.rstrip(os.path.sep)
assert os.path.isdir(some_dir)
num_sep = some_dir.count(os.path.sep)
for root, dirs, files in os.walk(some_dir):
yield root, dirs, files
num_sep_this = root.count(os.path.sep)
if num_sep + level <= num_sep_this:
del dirs[:]
def splitDirectory(path, mode, parentPath):
output = []
currentSize = 0
currentTarget = path
if mode == 0:
for root, dirs, files in walkLevel(path, 0):
for name in files:
size = os.path.getsize(os.path.join(root, name))
if currentSize + size > 262144000:
currentTarget, pathRoot = createNewTome(parentPath)
output.append(pathRoot)
currentSize = size
else:
currentSize += size
if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name))
elif mode == 1:
for root, dirs, files in walkLevel(path, 0):
for name in dirs:
size = getDirectorySize(os.path.join(root, name))
if currentSize + size > 262144000:
currentTarget, pathRoot = createNewTome(parentPath)
output.append(pathRoot)
currentSize = size
else:
currentSize += size
if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name))
elif mode == 2:
firstTome = True
for root, dirs, files in walkLevel(path, 0):
for name in dirs:
size = getDirectorySize(os.path.join(root, name))
currentSize = 0
if size > 262144000:
if not firstTome:
currentTarget, pathRoot = createNewTome(parentPath)
output.append(pathRoot)
else:
firstTome = False
for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0):
for nameInside in dirsInside:
size = getDirectorySize(os.path.join(rootInside, nameInside))
if currentSize + size > 262144000:
currentTarget, pathRoot = createNewTome(parentPath)
output.append(pathRoot)
currentSize = size
else:
currentSize += size
if path != currentTarget:
move(os.path.join(rootInside, nameInside), os.path.join(currentTarget, nameInside))
else:
if not firstTome:
currentTarget, pathRoot = createNewTome(parentPath)
output.append(pathRoot)
move(os.path.join(root, name), os.path.join(currentTarget, name))
else:
firstTome = False
return output
# noinspection PyUnboundLocalVariable
def preSplitDirectory(path):
if getDirectorySize(os.path.join(path, 'OEBPS', 'Images')) > 262144000:
# Detect directory stucture
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0):
subdirectoryNumber = len(dirs)
filesNumber = len(files)
if subdirectoryNumber == 0:
# No subdirectories
mode = 0
else:
if filesNumber > 0:
print '\nWARNING: Automatic output splitting failed.'
if GUI:
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning')
GUI.emit(QtCore.SIGNAL("addMessage"), '')
return [path]
detectedSubSubdirectories = False
detectedFilesInSubdirectories = False
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1):
if root != os.path.join(path, 'OEBPS', 'Images'):
if len(dirs) != 0:
detectedSubSubdirectories = True
elif len(dirs) == 0 and detectedSubSubdirectories:
print '\nWARNING: Automatic output splitting failed.'
if GUI:
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning')
GUI.emit(QtCore.SIGNAL("addMessage"), '')
return [path]
if len(files) != 0:
detectedFilesInSubdirectories = True
if detectedSubSubdirectories:
# Two levels of subdirectories
mode = 2
else:
# One level of subdirectories
mode = 1
if detectedFilesInSubdirectories and detectedSubSubdirectories:
print '\nWARNING: Automatic output splitting failed.'
if GUI:
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning')
GUI.emit(QtCore.SIGNAL("addMessage"), '')
return [path]
# Split directories
split = splitDirectory(os.path.join(path, 'OEBPS', 'Images'), mode, os.path.join(path, '..'))
path = [path]
for tome in split:
path.append(tome)
return path
else:
# No splitting is necessary
return [path]
def Copyright(): def Copyright():
print ('comic2ebook v%(__version__)s. ' print ('comic2ebook v%(__version__)s. '
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals()) 'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
@@ -679,48 +844,64 @@ def Usage():
def main(argv=None, qtGUI=None): def main(argv=None, qtGUI=None):
global parser, options, epub_path, splitCount, GUI global parser, options, epub_path, splitCount, GUI
usage = "Usage: %prog [options] comic_file|comic_folder" parser = OptionParser(usage="Usage: %prog [options] comic_file|comic_folder", add_help_option=False)
parser = OptionParser(usage=usage, version=__version__) mainOptions = OptionGroup(parser, "MAIN")
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD", experimentalOptions = OptionGroup(parser, "EXPERIMENTAL")
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) " processingOptions = OptionGroup(parser, "PROCESSING")
"[Default=KHD]") outputOptions = OptionGroup(parser, "OUTPUT SETTINGS")
parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle", customProfileOptions = OptionGroup(parser, "CUSTOM PROFILE")
help="Comic title [Default=filename]") otherOptions = OptionGroup(parser, "OTHER")
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
help="Manga style (Right-to-left reading and splitting) [Default=False]") help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD,"
parser.add_option("--quality", type="int", dest="quality", default="0", " KFHD8, KFA) [Default=KHD]")
help="Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]") mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
parser.add_option("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False, help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
help="Outputs a CBZ archive and does not generate EPUB") mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True, help="Manga style (Right-to-left reading and splitting)")
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]") outputOptions.add_option("-o", "--output", action="store", dest="output", default=None,
parser.add_option("--forcepng", action="store_true", dest="forcepng", default=False, help="Output generated file to specified directory or file")
help="Create PNG files instead JPEG (For non-Kindle devices) [Default=False]") outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
parser.add_option("--gamma", type="float", dest="gamma", default="0.0", help="Comic title [Default=filename or directory name]")
help="Apply gamma correction to linearize the image [Default=Auto]") outputOptions.add_option("--cbz-output", action="store_true", dest="cbzoutput", default=False,
parser.add_option("--upscale", action="store_true", dest="upscale", default=False, help="Outputs a CBZ archive and does not generate EPUB")
help="Resize images smaller than device's resolution [Default=False]") outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
parser.add_option("--stretch", action="store_true", dest="stretch", default=False, help="Split output into multiple files"),
help="Stretch images to device's resolution [Default=False]") experimentalOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False, help="Webtoon processing mode"),
help="Use black borders instead of white ones when not stretching and ratio " processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
+ "is not like the device's one [Default=False]") help="Use black borders instead of white ones")
parser.add_option("--rotate", action="store_true", dest="rotate", default=False, processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
help="Rotate landscape pages instead of splitting them [Default=False]") help="Don't convert images to grayscale")
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False, processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Disable splitting and rotation [Default=False]") help="Create PNG files instead JPEG (For non-Kindle devices)")
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True, processingOptions.add_option("--gamma", type="float", dest="gamma", default="0.0",
help="Do not try to cut page numbering on images [Default=True]") help="Apply gamma correction to linearize the image [Default=Auto]")
parser.add_option("-o", "--output", action="store", dest="output", default=None, processingOptions.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
help="Output generated file (EPUB or CBZ) to specified directory or file") help="Don't try to cut page numbering on images")
parser.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False, processingOptions.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
help="Do not convert images to grayscale [Default=False]") help="Don't apply image preprocessing")
parser.add_option("--customwidth", type="int", dest="customwidth", default=0, processingOptions.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
help="Replace screen width provided by device profile [Default=0]") help="Disable splitting and rotation")
parser.add_option("--customheight", type="int", dest="customheight", default=0, processingOptions.add_option("--rotate", action="store_true", dest="rotate", default=False,
help="Replace screen height provided by device profile [Default=0]") help="Rotate landscape pages instead of splitting them")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, processingOptions.add_option("--stretch", action="store_true", dest="stretch", default=False,
help="Verbose output [Default=False]") help="Stretch images to device's resolution")
processingOptions.add_option("--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution")
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile")
customProfileOptions.add_option("--customheight", type="int", dest="customheight", default=0,
help="Replace screen height provided by device profile")
otherOptions.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
help="Verbose output")
otherOptions.add_option("-h", "--help", action="help",
help="Show this help message and exit")
parser.add_option_group(mainOptions)
parser.add_option_group(experimentalOptions)
parser.add_option_group(outputOptions)
parser.add_option_group(processingOptions)
parser.add_option_group(customProfileOptions)
parser.add_option_group(otherOptions)
options, args = parser.parse_args(argv) options, args = parser.parse_args(argv)
checkOptions() checkOptions()
if qtGUI: if qtGUI:
@@ -732,33 +913,57 @@ def main(argv=None, qtGUI=None):
parser.print_help() parser.print_help()
return return
path = getWorkFolder(args[0]) path = getWorkFolder(args[0])
if options.title == 'defaulttitle': if options.webtoon:
options.title = os.path.splitext(os.path.basename(args[0]))[0] if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Splitting images')
if options.customheight > 0:
comic2panel.main(['-y ' + str(options.customheight), '-i', path], qtGUI)
else:
comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI)
splitCount = 0 splitCount = 0
if options.imgproc: if options.imgproc:
print "Processing images..." print "\nProcessing images..."
if GUI: if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images') GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images')
dirImgProcess(path + "/OEBPS/Images/") dirImgProcess(path + "/OEBPS/Images/")
if GUI: if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1) GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
if options.cbzoutput: sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
# if CBZ output wanted, compress all images and return filepath if options.batchsplit:
print "\nCreating CBZ file..." tomes = preSplitDirectory(path)
filepath = getOutputFilename(args[0], options.output, '.cbz')
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
else: else:
print "\nCreating EPUB structure..." tomes = [path]
genEpubStruct(path) filepath = []
# actually zip the ePub tomeNumber = 0
filepath = getOutputFilename(args[0], options.output, '.epub') for tome in tomes:
make_archive(path + '_comic', 'zip', path) if len(tomes) > 1:
move(path + '_comic.zip', filepath) tomeNumber += 1
rmtree(path) options.title = os.path.splitext(os.path.basename(args[0]))[0] + ' ' + str(tomeNumber)
elif options.title == 'defaulttitle':
options.title = os.path.splitext(os.path.basename(args[0]))[0]
if options.cbzoutput:
# if CBZ output wanted, compress all images and return filepath
print "\nCreating CBZ file..."
if len(tomes) > 1:
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ''))
make_archive(tome + '_comic', 'zip', tome + '/OEBPS/Images')
else:
print "\nCreating EPUB structure..."
genEpubStruct(tome)
# actually zip the ePub
if len(tomes) > 1:
filepath.append(getOutputFilename(args[0], options.output, '.epub', ' ' + str(tomeNumber)))
else:
filepath.append(getOutputFilename(args[0], options.output, '.epub', ''))
make_archive(tome + '_comic', 'zip', tome)
move(tome + '_comic.zip', filepath[-1])
rmtree(tome)
return filepath return filepath
def getOutputFilename(srcpath, wantedname, ext): def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
if not ext.startswith('.'): if not ext.startswith('.'):
ext = '.' + ext ext = '.' + ext
if wantedname is not None: if wantedname is not None:
@@ -767,19 +972,23 @@ def getOutputFilename(srcpath, wantedname, ext):
elif os.path.isdir(srcpath): elif os.path.isdir(srcpath):
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
else: else:
filename = os.path.abspath(options.output) + "/" \ filename = os.path.abspath(options.output) + "/" + os.path.basename(os.path.splitext(srcpath)[0]) + ext
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
elif os.path.isdir(srcpath): elif os.path.isdir(srcpath):
filename = srcpath + ext filename = srcpath + tomeNumber + ext
else: else:
filename = os.path.splitext(srcpath)[0] + ext filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
if os.path.isfile(filename): if os.path.isfile(filename):
filename = os.path.splitext(filename)[0] + '_kcc' + ext filename = os.path.splitext(filename)[0] + '_kcc' + tomeNumber + ext
return filename return filename
def checkOptions(): def checkOptions():
global options global options
# Webtoon mode mandatory options
if options.webtoon:
options.nosplitrotate = True
options.black_borders = False
options.quality = 0
# Landscape mode is only supported by Kindle Touch and Paperwhite. # Landscape mode is only supported by Kindle Touch and Paperwhite.
if options.profile == 'K4T' or options.profile == 'KHD': if options.profile == 'K4T' or options.profile == 'KHD':
options.landscapemode = True options.landscapemode = True
@@ -816,6 +1025,10 @@ def checkOptions():
options.landscapemode = False options.landscapemode = False
options.panelview = False options.panelview = False
options.quality = 0 options.quality = 0
# Kindle for Android profile require target resolution.
if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0):
print "ERROR: Kindle for Android profile require --customwidth and --customheight options!"
sys.exit(1)
# Override profile data # Override profile data
if options.customwidth != 0 or options.customheight != 0: if options.customwidth != 0 or options.customheight != 0:
X = image.ProfileData.Profiles[options.profile][1][0] X = image.ProfileData.Profiles[options.profile][1][0]
@@ -831,11 +1044,6 @@ def checkOptions():
options.profileData = image.ProfileData.Profiles[options.profile] options.profileData = image.ProfileData.Profiles[options.profile]
def getEpubPath():
global epub_path
return epub_path
if __name__ == "__main__": if __name__ == "__main__":
freeze_support() freeze_support()
Copyright() Copyright()

315
kcc/comic2panel.py Normal file
View File

@@ -0,0 +1,315 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# 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.
#
__version__ = '3.2.1'
__license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en'
import os
import sys
from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool, Queue, freeze_support
try:
# noinspection PyUnresolvedReferences,PyPackageRequirements
from PIL import Image, ImageStat
except ImportError:
print "ERROR: Pillow is not installed!"
exit(1)
try:
from PyQt4 import QtCore
except ImportError:
QtCore = None
def getImageFileName(imgfile):
filename = os.path.splitext(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
def getImageHistogram(image):
histogram = image.histogram()
RBGW = []
for i in range(256):
RBGW.append(histogram[i] + histogram[256 + i] + histogram[512 + i])
white = 0
black = 0
for i in range(245, 256):
white += RBGW[i]
for i in range(11):
black += RBGW[i]
if white > black:
return False
else:
return True
def getImageFill(image):
imageSize = image.size
imageT = image.crop((0, 0, imageSize[0], 1))
imageB = image.crop((0, imageSize[1]-1, imageSize[0], imageSize[1]))
fill = 0
fill += getImageHistogram(imageT)
fill += getImageHistogram(imageB)
if fill == 2:
return 'KCCFB'
elif fill == 0:
return 'KCCFW'
else:
imageL = image.crop((0, 0, 1, imageSize[1]))
imageR = image.crop((imageSize[0]-1, 0, imageSize[0], imageSize[1]))
fill += getImageHistogram(imageL)
fill += getImageHistogram(imageR)
if fill >= 2:
return 'KCCFB'
else:
return 'KCCFW'
def sanitizePanelSize(panel, options):
newPanels = []
if panel[2] > 8 * options.height:
diff = (panel[2] / 8)
newPanels.append([panel[0], panel[1] - diff*7, diff])
newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff])
newPanels.append([panel[1] - diff*6, panel[1] - diff*5, diff])
newPanels.append([panel[1] - diff*5, panel[1] - diff*4, diff])
newPanels.append([panel[1] - diff*4, panel[1] - diff*3, diff])
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 4 * options.height:
diff = (panel[2] / 4)
newPanels.append([panel[0], panel[1] - diff*3, diff])
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 2 * options.height:
newPanels.append([panel[0], panel[1] - (panel[2] / 2), (panel[2] / 2)])
newPanels.append([panel[1] - (panel[2] / 2), panel[1], (panel[2] / 2)])
else:
newPanels = [panel]
return newPanels
def splitImage_init(queue, options):
splitImage.queue = queue
splitImage.options = options
# noinspection PyUnresolvedReferences
def splitImage(work):
path = work[0]
name = work[1]
options = splitImage.options
# Harcoded options
threshold = 1.0
delta = 15
print ".",
splitImage.queue.put(".")
fileExpanded = os.path.splitext(name)
filePath = os.path.join(path, name)
# Detect corrupted files
try:
image = Image.open(filePath)
except IOError:
raise RuntimeError('Cannot read image file %s' % filePath)
try:
image = Image.open(filePath)
image.verify()
except:
raise RuntimeError('Image file %s is corrupted' % filePath)
try:
image = Image.open(filePath)
image.load()
except:
raise RuntimeError('Image file %s is corrupted' % filePath)
image = Image.open(filePath)
image = image.convert('RGB')
widthImg, heightImg = image.size
if heightImg > options.height:
if options.debug:
from PIL import ImageDraw
debugImage = Image.open(filePath)
draw = ImageDraw.Draw(debugImage)
# Find panels
y1 = 0
y2 = 15
panels = []
while y2 < heightImg:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg:
y2 += delta
y2 -= delta
y1Temp = y2
y1 = y2 + delta
y2 = y1 + delta
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg:
y1 += delta
y2 += delta
if y1 + delta >= heightImg:
y1 = heightImg - 1
y2Temp = y1
if options.debug:
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0))
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0))
panelHeight = y2Temp - y1Temp
if panelHeight > delta:
# Panels that can't be cut nicely will be forcefully splitted
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], options)
for panel in panelsCleaned:
panels.append(panel)
if options.debug:
# noinspection PyUnboundLocalVariable
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG')
# Create virtual pages
pages = []
currentPage = []
pageLeft = options.height
panelNumber = 0
for panel in panels:
if pageLeft - panel[2] > 0:
pageLeft -= panel[2]
currentPage.append(panelNumber)
panelNumber += 1
else:
if len(currentPage) > 0:
pages.append(currentPage)
pageLeft = options.height - panel[2]
currentPage = [panelNumber]
panelNumber += 1
if len(currentPage) > 0:
pages.append(currentPage)
# Create pages
pageNumber = 1
for page in pages:
pageHeight = 0
targetHeight = 0
for panel in page:
pageHeight += panels[panel][2]
if pageHeight > delta:
newPage = Image.new('RGB', (widthImg, pageHeight))
for panel in page:
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]])
newPage.paste(panelImg, (0, targetHeight))
targetHeight += panels[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' +
str(pageNumber) + '-' + getImageFill(newPage) + '.png'), 'PNG')
pageNumber += 1
os.remove(filePath)
def Copyright():
print ('comic2panel v%(__version__)s. '
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
# noinspection PyBroadException
def main(argv=None, qtGUI=None):
global options
parser = OptionParser(usage="Usage: %prog [options] comic_folder", add_help_option=False)
mainOptions = OptionGroup(parser, "MANDATORY")
otherOptions = OptionGroup(parser, "OTHER")
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0,
help="Height of the target device screen")
mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False,
help="Overwrite source directory")
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
help="Create debug file for every splitted image")
otherOptions.add_option("-h", "--help", action="help",
help="Show this help message and exit")
parser.add_option_group(mainOptions)
parser.add_option_group(otherOptions)
options, args = parser.parse_args(argv)
if qtGUI:
GUI = qtGUI
else:
GUI = None
if len(args) != 1:
parser.print_help()
return
if options.height > 0:
options.sourceDir = args[0]
options.targetDir = args[0] + "-Splitted"
print "\nSplitting images..."
if os.path.isdir(options.sourceDir):
rmtree(options.targetDir, True)
copytree(options.sourceDir, options.targetDir)
work = []
pagenumber = 0
queue = Queue()
pool = Pool(None, splitImage_init, [queue, options])
for root, dirs, files in os.walk(options.targetDir, False):
for name in files:
if getImageFileName(name) is not None:
pagenumber += 1
work.append([root, name])
else:
os.remove(os.path.join(root, name))
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), pagenumber)
if len(work) > 0:
workers = pool.map_async(func=splitImage, iterable=work)
pool.close()
if GUI:
while not workers.ready():
# noinspection PyBroadException
try:
queue.get(True, 5)
except:
pass
GUI.emit(QtCore.SIGNAL("progressBarTick"))
pool.join()
queue.close()
try:
workers.get()
except:
rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
if options.inPlace:
rmtree(options.sourceDir, True)
move(options.targetDir, options.sourceDir)
else:
rmtree(options.targetDir)
raise UserWarning("Source directory is empty.")
else:
raise UserWarning("Provided path is not a directory.")
else:
raise UserWarning("Target height is not set.")
if __name__ == "__main__":
freeze_support()
Copyright()
main(sys.argv[1:])
sys.exit(0)

View File

@@ -21,7 +21,7 @@ __docformat__ = 'restructuredtext en'
import os import os
try: try:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences,PyPackageRequirements
from PIL import Image, ImageOps, ImageStat, ImageChops from PIL import Image, ImageOps, ImageStat, ImageChops
except ImportError: except ImportError:
print "ERROR: Pillow is not installed!" print "ERROR: Pillow is not installed!"
@@ -85,6 +85,7 @@ class ProfileData:
'KF': ("Kindle Fire", (600, 1024), Palette16, 1.0, (900, 1536)), 'KF': ("Kindle Fire", (600, 1024), Palette16, 1.0, (900, 1536)),
'KFHD': ("Kindle Fire HD 7\"", (800, 1280), Palette16, 1.0, (1200, 1920)), 'KFHD': ("Kindle Fire HD 7\"", (800, 1280), Palette16, 1.0, (1200, 1920)),
'KFHD8': ("Kindle Fire HD 8.9\"", (1200, 1920), Palette16, 1.0, (1800, 2880)), 'KFHD8': ("Kindle Fire HD 8.9\"", (1200, 1920), Palette16, 1.0, (1800, 2880)),
'KFA': ("Kindle for Android", (0, 0), Palette16, 1.0, (0, 0)),
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)), 'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
} }
@@ -100,6 +101,7 @@ class ProfileData:
"Kindle Fire": 'KF', "Kindle Fire": 'KF',
"Kindle Fire HD 7\"": 'KFHD', "Kindle Fire HD 7\"": 'KFHD',
"Kindle Fire HD 8.9\"": 'KFHD8', "Kindle Fire HD 8.9\"": 'KFHD8',
"Kindle for Android": 'KFA',
"Other": 'OTHER' "Other": 'OTHER'
} }
@@ -113,19 +115,26 @@ class ComicPage:
# Detect corrupted files - Phase 2 # Detect corrupted files - Phase 2
try: try:
self.origFileName = source self.origFileName = source
self.filename = os.path.basename(self.origFileName)
self.image = Image.open(source) self.image = Image.open(source)
except IOError: except IOError:
raise RuntimeError('Cannot read image file %s' % source) raise RuntimeError('Cannot read image file %s' % source)
# Detect corrupted files - Phase 3 # Detect corrupted files - Phase 3
try: try:
self.image = Image.open(source)
self.image.verify() self.image.verify()
except: except:
raise RuntimeError('Image file %s is corrupted' % source) raise RuntimeError('Image file %s is corrupted' % source)
# Detect corrupted files - Phase 4
try:
self.image = Image.open(source)
self.image.load()
except:
raise RuntimeError('Image file %s is corrupted' % source)
self.image = Image.open(source) self.image = Image.open(source)
self.image = self.image.convert('RGB') self.image = self.image.convert('RGB')
def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None): def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None):
filename = os.path.basename(self.origFileName)
try: try:
if not color: if not color:
self.image = self.image.convert('L') # convert to grayscale self.image = self.image.convert('L') # convert to grayscale
@@ -134,13 +143,13 @@ class ComicPage:
else: else:
suffix = "" suffix = ""
if wipe: if wipe:
os.remove(os.path.join(targetdir, filename)) os.remove(os.path.join(targetdir, self.filename))
else: else:
suffix += "_kcchq" suffix += "_kcchq"
if forcepng: if forcepng:
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".png"), "PNG") self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), "PNG")
else: else:
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".jpg"), "JPEG") self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".jpg"), "JPEG")
except IOError as e: except IOError as e:
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e)) raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
@@ -165,10 +174,15 @@ class ComicPage:
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, qualityMode=0): landscapeMode=False, qualityMode=0):
method = Image.ANTIALIAS method = Image.ANTIALIAS
if black_borders: if '-KCCFW' in str(self.filename):
fill = 'white'
elif '-KCCFB' in str(self.filename):
fill = 'black' fill = 'black'
else: else:
fill = 'white' if black_borders:
fill = 'black'
else:
fill = 'white'
if qualityMode == 0: if qualityMode == 0:
size = (self.size[0], self.size[1]) size = (self.size[0], self.size[1])
else: else:
@@ -225,7 +239,7 @@ class ComicPage:
# source is portrait and target is landscape, so split by the height # source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, height / 2) leftbox = (0, 0, width, height / 2)
rightbox = (0, height / 2, width, height) rightbox = (0, height / 2, width, height)
filename = os.path.splitext(os.path.basename(self.origFileName)) filename = os.path.splitext(self.filename)
fileone = targetdir + '/' + filename[0] + '_kcca' + filename[1] fileone = targetdir + '/' + filename[0] + '_kcca' + filename[1]
filetwo = targetdir + '/' + filename[0] + '_kccb' + filename[1] filetwo = targetdir + '/' + filename[0] + '_kccb' + filename[1]
try: try:

View File

@@ -29,7 +29,7 @@ class PdfJpgExtract:
def __init__(self, origFileName): def __init__(self, origFileName):
self.origFileName = origFileName self.origFileName = origFileName
self.filename = os.path.splitext(origFileName) self.filename = os.path.splitext(origFileName)
self.path = self.filename[0] self.path = self.filename[0] + "-KCC-TMP"
def getPath(self): def getPath(self):
return self.path return self.path
@@ -70,4 +70,4 @@ class PdfJpgExtract:
njpg += 1 njpg += 1
i = iend i = iend
return self.path return self.path, njpg

View File

@@ -10,7 +10,7 @@ Usage (Windows):
from sys import platform from sys import platform
NAME = "KindleComicConverter" NAME = "KindleComicConverter"
VERSION = "3.0" VERSION = "3.2.1"
MAIN = "kcc.py" MAIN = "kcc.py"
if platform == "darwin": if platform == "darwin":