1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-16 14:08:45 +00:00

Compare commits

..

53 Commits
3.0 ... 3.2

Author SHA1 Message Date
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 1016 additions and 242 deletions

1
.gitignore vendored
View File

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

View File

@@ -88,15 +88,18 @@
<enum>Qt::NoFocus</enum>
</property>
<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 name="text">
<string>Upscale images</string>
<string>Stretch/Upscale</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="StretchBox">
<widget class="QCheckBox" name="WebtoonBox">
<property name="font">
<font>
<pointsize>11</pointsize>
@@ -106,10 +109,10 @@
<enum>Qt::NoFocus</enum>
</property>
<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 name="text">
<string>Stretch images</string>
<string>Webtoon mode</string>
</property>
</widget>
</item>

16
KCC.ui
View File

@@ -59,6 +59,9 @@
</font>
</property>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>9</number>
</property>
<item row="1" column="0">
<widget class="QCheckBox" name="ProcessingBox">
<property name="focusPolicy">
@@ -78,23 +81,26 @@
<enum>Qt::NoFocus</enum>
</property>
<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 name="text">
<string>Upscale images</string>
<string>Stretch/Upscale</string>
</property>
<property name="tristate">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="StretchBox">
<widget class="QCheckBox" name="WebtoonBox">
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<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 name="text">
<string>Stretch images</string>
<string>Webtoon mode</string>
</property>
</widget>
</item>

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/)
- **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
**KCC** can understand and convert, at the moment, the following file types:
- 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.**
* Read tooltip of _High/Ultra quality_ option. There are many important informations there.
* 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.
* The first image found will be set as the comic's cover.
* 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.
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
### 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).
### 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
Options:
--version show program's version number and exit
-h, --help show this help message and exit
-p PROFILE, --profile=PROFILE
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) [Default=KHD]
-t TITLE, --title=TITLE
Comic title [Default=filename]
-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]
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
--upscale Resize images smaller than device's resolution [Default=False]
--stretch Stretch images to device's resolution [Default=False]
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
--rotate Rotate landscape pages instead of splitting them [Default=False]
--nosplitrotate Disable splitting and rotation [Default=False]
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
-o OUTPUT, --output=OUTPUT
Output generated file (EPUB or CBZ) to specified directory or file
--forcecolor Do not convert images to grayscale [Default=False]
--customwidth=WIDTH Replace screen width provided by device profile [Default=0]
--customheight=HEIGHT Replace screen height provided by device profile [Default=0]
-v, --verbose Verbose output [Default=False]
MAIN:
-p PROFILE, --profile=PROFILE
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFA) [Default=KHD]
-q QUALITY, --quality=QUALITY
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
-m, --manga-style Manga style (Right-to-left reading and splitting)
EXPERIMENTAL:
-w, --webtoon Webtoon processing mode
OUTPUT SETTINGS:
-o OUTPUT, --output=OUTPUT
Output generated file to specified directory or file
-t TITLE, --title=TITLE
Comic title [Default=filename or directory name]
--cbz-output Outputs a CBZ archive and does not generate EPUB
--batchsplit Split output into multiple files
PROCESSING:
--blackborders Use black borders instead of white ones
--forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG (For non-Kindle devices)
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
--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
@@ -213,8 +242,17 @@ The app relies and includes the following scripts/binaries:
* Added support for custom width/height
* 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
## 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.
## COPYRIGHT

4
kcc.py
View File

@@ -16,8 +16,8 @@
# 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.0'
__version__ = '3.2'
__license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en'

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
__version__ = '3.0'
__version__ = '3.2'
__license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en'
@@ -27,12 +27,14 @@ import sys
import shutil
import traceback
import urllib2
import time
import comic2ebook
import kindlestrip
from image import ProfileData
from subprocess import call, Popen, STDOUT, PIPE
from PyQt4 import QtGui, QtCore
from xml.dom.minidom import parse
from HTMLParser import HTMLParser
class Icons:
@@ -57,6 +59,19 @@ class Icons:
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
class VersionThread(QtCore.QThread):
def __init__(self, parent):
@@ -74,7 +89,8 @@ class VersionThread(QtCore.QThread):
return
latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
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
@@ -86,6 +102,16 @@ class WorkerThread(QtCore.QThread):
def __del__(self):
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):
self.emit(QtCore.SIGNAL("modeConvert"), False)
profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())]
@@ -102,31 +128,40 @@ class WorkerThread(QtCore.QThread):
if self.parent.currentMode > 1:
if GUI.ProcessingBox.isChecked():
argv.append("--noprocessing")
if GUI.UpscaleBox.isChecked() and not GUI.StretchBox.isChecked():
argv.append("--upscale")
if GUI.NoRotateBox.isChecked():
argv.append("--nosplitrotate")
if GUI.BorderBox.isChecked():
argv.append("--blackborders")
if GUI.StretchBox.isChecked():
if GUI.UpscaleBox.checkState() == 1:
argv.append("--stretch")
elif GUI.UpscaleBox.checkState() == 2:
argv.append("--upscale")
if GUI.NoDitheringBox.isChecked():
argv.append("--forcepng")
if GUI.WebtoonBox.isChecked():
argv.append("--webtoon")
if float(self.parent.GammaValue) > 0.09:
argv.append("--gamma=" + self.parent.GammaValue)
if str(GUI.FormatBox.currentText()) == 'CBZ':
argv.append("--cbz-output")
if str(GUI.FormatBox.currentText()) == 'MOBI':
argv.append("--batchsplit")
if self.parent.currentMode > 2:
argv.append("--customwidth=" + str(GUI.customWidth.text()))
argv.append("--customheight=" + str(GUI.customHeight.text()))
if GUI.ColorBox.isChecked():
argv.append("--forcecolor")
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()
for job in currentJobs:
time.sleep(0.5)
if not self.conversionAlive:
self.clean()
return
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':
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file...', 'info')
else:
@@ -136,30 +171,80 @@ class WorkerThread(QtCore.QThread):
try:
outputPath = comic2ebook.main(jobargv, self)
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:
self.errors = True
type_, value_, traceback_ = sys.exc_info()
self.emit(QtCore.SIGNAL("showDialog"), "Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err), traceback.format_tb(traceback_)))
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 str(GUI.FormatBox.currentText()) == 'CBZ':
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file... Done!', 'info', True)
else:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True)
if str(GUI.FormatBox.currentText()) == 'MOBI':
if not os.path.getsize(outputPath) > 314572800:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
tomeNumber = 0
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)
try:
retcode = call('kindlegen -verbose "' + outputPath + '"', shell=True)
self.kindlegenErrorCode = 0
if os.path.getsize(item) < 367001600:
output = Popen('kindlegen "' + 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:
# ERROR: Unknown generic error
self.kindlegenErrorCode = 1
continue
if retcode == 0:
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True)
if not self.conversionAlive:
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')
os.remove(outputPath)
mobiPath = outputPath.replace('.epub', '.mobi')
os.remove(item)
mobiPath = item.replace('.epub', '.mobi')
shutil.move(mobiPath, mobiPath + '_tostrip')
try:
kindlestrip.main((mobiPath + '_tostrip', mobiPath))
@@ -175,62 +260,80 @@ class WorkerThread(QtCore.QThread):
self.emit(QtCore.SIGNAL("addMessage"),
'MOBI file will work correctly but it will be highly oversized.', 'warning')
else:
os.remove(outputPath)
if os.path.exists(outputPath.replace('.epub', '.mobi')):
os.remove(outputPath.replace('.epub', '.mobi'))
epubSize = (os.path.getsize(item))/1024/1024
os.remove(item)
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"), 'Try converting a smaller batch.', 'error')
else:
excess = (os.path.getsize(outputPath) - 314572800)/1024/1024
os.remove(outputPath)
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file is too big for KindleGen!', 'error')
self.emit(QtCore.SIGNAL("addMessage"), 'Limit exceeded by ' + str(excess) +
' MB. Try converting a smaller batch.', 'error')
if self.kindlegenErrorCode == 1 and self.kindlegenError:
self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
if self.kindlegenErrorCode == 23026:
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
'error')
self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
' Supported size: ~300MB.', 'error')
self.emit(QtCore.SIGNAL("hideProgressBar"))
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)
# noinspection PyBroadException
class Ui_KCC(object):
def selectDir(self):
# Dialog allow to select multiple directories but we can't parse that. QT Bug.
if self.needClean:
self.needClean = False
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
try:
str(dname)
except Exception:
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
QtGui.QMessageBox.Ok)
return
if str(dname) != "":
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
GUI.JobList.addItem(dname)
for dname in dnames:
try:
str(dname)
except Exception:
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
QtGui.QMessageBox.Ok)
return
if str(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):
if self.needClean:
self.needClean = False
GUI.JobList.clear()
if self.UnRAR:
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
'*.cbz *.cbr *.zip *.rar *.pdf')
fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
'*.cbz *.cbr *.zip *.rar *.pdf')
else:
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
'*.cbz *.zip *.pdf')
fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
'*.cbz *.zip *.pdf')
# Lame UTF-8 security measure
try:
str(fname)
except Exception:
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
QtGui.QMessageBox.Ok)
return
if str(fname) != "":
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
GUI.JobList.addItem(fname)
for fname in fnames:
try:
str(fname)
except Exception:
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
QtGui.QMessageBox.Ok)
return
if str(fname) != "":
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
GUI.JobList.addItem(fname)
def clearJobs(self):
GUI.JobList.clear()
@@ -268,15 +371,21 @@ class Ui_KCC(object):
GUI.OptionsExpert.setEnabled(False)
GUI.MangaBox.setEnabled(True)
def modeExpert(self):
def modeExpert(self, KFA=False):
self.modeAdvanced()
self.currentMode = 3
MainWindow.setMinimumSize(QtCore.QSize(420, 380))
MainWindow.setMaximumSize(QtCore.QSize(420, 380))
MainWindow.resize(420, 380)
GUI.OptionsExpert.setEnabled(True)
GUI.MangaBox.setCheckState(0)
GUI.MangaBox.setEnabled(False)
if KFA:
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):
if self.currentMode != 3:
@@ -286,19 +395,33 @@ class Ui_KCC(object):
GUI.ClearButton.setEnabled(enable)
GUI.FileButton.setEnabled(enable)
GUI.DeviceBox.setEnabled(enable)
GUI.ConvertButton.setEnabled(enable)
GUI.FormatBox.setEnabled(enable)
GUI.OptionsBasic.setEnabled(enable)
GUI.OptionsAdvanced.setEnabled(enable)
GUI.OptionsAdvancedGamma.setEnabled(enable)
GUI.OptionsExpert.setEnabled(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:
self.modeBasic()
elif self.currentMode == 2:
self.modeAdvanced()
elif self.currentMode == 3:
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):
value = float(value)
@@ -309,30 +432,70 @@ class Ui_KCC(object):
GUI.GammaLabel.setText('Gamma: ' + str(value))
self.GammaValue = value
def changeDevice(self, value, start=False):
if value == 11 and (start or self.currentMode != 3):
def toggleWebtoonBox(self, value):
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.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()
elif value == 11:
GUI.BasicModeButton.setEnabled(False)
GUI.AdvModeButton.setEnabled(False)
self.modeExpert(True)
elif self.currentMode == 3:
GUI.BasicModeButton.setEnabled(True)
GUI.AdvModeButton.setEnabled(True)
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.setEnabled(False)
else:
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):
if icon:
icon = eval('self.icons.' + icon)
item = QtGui.QListWidgetItem(icon, message)
item = QtGui.QListWidgetItem(icon, ' ' + self.stripTags(message))
else:
item = QtGui.QListWidgetItem(message)
item = QtGui.QListWidgetItem(' ' + self.stripTags(message))
if replace:
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.setItemWidget(item, label)
GUI.JobList.scrollToBottom()
def showDialog(self, message):
@@ -340,7 +503,6 @@ class Ui_KCC(object):
def updateProgressbar(self, new=False, status=False):
if new == "status":
pass
GUI.ProgressBar.setFormat(status)
elif new:
GUI.ProgressBar.setMaximum(new - 1)
@@ -350,25 +512,38 @@ class Ui_KCC(object):
GUI.ProgressBar.setValue(GUI.ProgressBar.value() + 1)
def convertStart(self):
if self.needClean:
self.needClean = False
GUI.JobList.clear()
if GUI.JobList.count() == 0:
self.addMessage('No files selected! Please choose files to convert.', 'error')
self.needClean = True
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()
if self.conversionAlive:
GUI.ConvertButton.setEnabled(False)
self.addMessage('Process will be interrupted. Please wait.', 'warning')
self.conversionAlive = False
self.worker.sync()
else:
if self.needClean:
self.needClean = False
GUI.JobList.clear()
if GUI.JobList.count() == 0:
self.addMessage('No files selected! Please choose files to convert.', 'error')
self.needClean = True
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):
GUI.ProgressBar.hide()
# noinspection PyUnusedLocal
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('lastDevice', GUI.DeviceBox.currentIndex())
self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex())
@@ -380,7 +555,7 @@ class Ui_KCC(object):
'UpscaleBox': GUI.UpscaleBox.checkState(),
'NoRotateBox': GUI.NoRotateBox.checkState(),
'BorderBox': GUI.BorderBox.checkState(),
'StretchBox': GUI.StretchBox.checkState(),
'WebtoonBox': GUI.WebtoonBox.checkState(),
'NoDitheringBox': GUI.NoDitheringBox.checkState(),
'ColorBox': GUI.ColorBox.checkState(),
'customWidth': GUI.customWidth.text(),
@@ -403,10 +578,11 @@ class Ui_KCC(object):
self.options = self.options.toPyObject()
self.worker = WorkerThread(self)
self.versionCheck = VersionThread(self)
self.conversionAlive = False
self.needClean = True
self.addMessage('Welcome!', 'info')
self.addMessage('Remember: all options have additional informations in tooltips.', 'info')
self.addMessage('<b>Welcome!</b>', 'info')
self.addMessage('<b>Remember:</b> All options have additional informations in tooltips.', 'info')
if call('kindlegen', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
self.KindleGen = True
formats = ['MOBI', 'EPUB', 'CBZ']
@@ -415,18 +591,23 @@ class Ui_KCC(object):
if "Amazon kindlegen" in line:
versionCheck = line.split('V')[1].split(' ')[0]
if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))):
self.addMessage('Your kindlegen is outdated! Creating MOBI might fail.'
' Please update kindlegen from Amazon\'s website.', 'warning')
self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'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
else:
self.KindleGen = False
formats = ['EPUB', 'CBZ']
self.addMessage('Cannot find kindlegen in PATH! MOBI creation will be disabled.', 'warning')
if call('unrar', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
'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
else:
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.AdvModeButton.clicked.connect(self.modeAdvanced)
@@ -435,6 +616,8 @@ class Ui_KCC(object):
GUI.FileButton.clicked.connect(self.selectFile)
GUI.ConvertButton.clicked.connect(self.convertStart)
GUI.GammaSlider.valueChanged.connect(self.changeGamma)
GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
GUI.WebtoonBox.stateChanged.connect(self.toggleWebtoonBox)
GUI.DeviceBox.activated.connect(self.changeDevice)
KCC.connect(self.worker, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar)
KCC.connect(self.worker, QtCore.SIGNAL("modeConvert"), self.modeConvert)
@@ -467,6 +650,8 @@ class Ui_KCC(object):
elif str(option) == "GammaSlider":
GUI.GammaSlider.setValue(int(self.options[option]))
self.changeGamma(int(self.options[option]))
elif str(option) == "StretchBox" or str(option) == "WebstripBox":
pass
else:
eval('GUI.' + str(option)).setCheckState(self.options[option])
if self.currentMode == 1:
@@ -477,4 +662,5 @@ class Ui_KCC(object):
self.modeExpert()
self.versionCheck.start()
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'
#
# Created: Fri Jun 21 18:23:19 2013
# by: PyQt4 UI code generator 4.10.1
# Created: Wed Aug 14 08:39:46 2013
# by: PyQt4 UI code generator 4.10.2
#
# WARNING! All changes made in this file will be lost!
@@ -47,6 +47,7 @@ class Ui_KCC(object):
self.OptionsAdvanced.setFont(font)
self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced"))
self.gridLayout = QtGui.QGridLayout(self.OptionsAdvanced)
self.gridLayout.setContentsMargins(9, -1, -1, -1)
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus)
@@ -54,12 +55,13 @@ class Ui_KCC(object):
self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1)
self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.UpscaleBox.setTristate(True)
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox"))
@@ -262,10 +264,10 @@ class Ui_KCC(object):
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", 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.setText(_translate("KCC", "Upscale images", 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.StretchBox.setText(_translate("KCC", "Stretch images", 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", "Stretch/Upscale", 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.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.setText(_translate("KCC", "PNG output", 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'
#
# Created: Fri Jun 21 18:23:35 2013
# by: PyQt4 UI code generator 4.10.1
# Created: Wed Aug 14 08:39:45 2013
# by: PyQt4 UI code generator 4.10.2
#
# WARNING! All changes made in this file will be lost!
@@ -60,15 +60,16 @@ class Ui_KCC(object):
font.setPointSize(11)
self.UpscaleBox.setFont(font)
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.UpscaleBox.setTristate(True)
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
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.setPointSize(11)
self.StretchBox.setFont(font)
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
self.WebtoonBox.setFont(font)
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
font = QtGui.QFont()
font.setPointSize(11)
@@ -321,10 +322,10 @@ class Ui_KCC(object):
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.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.setText(_translate("KCC", "Upscale images", 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.StretchBox.setText(_translate("KCC", "Stretch images", 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", "Stretch/Upscale", 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.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.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))

View File

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

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
__version__ = '3.0'
__version__ = '3.2'
__license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en'
@@ -28,17 +28,14 @@ import tempfile
import re
import stat
import string
from shutil import move
from shutil import copyfile
from shutil import copytree
from shutil import rmtree
from shutil import make_archive
from optparse import OptionParser
from shutil import move, copyfile, copytree, rmtree, make_archive
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool, Queue, freeze_support
try:
from PyQt4 import QtCore
except ImportError:
QtCore = None
import comic2panel
import image
import cbxarchive
import pdfjpgextract
@@ -326,16 +323,17 @@ def getImageFileName(imgfile):
def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5):
img.cropWhiteSpace(10.0)
if options.cutpagenumbers:
if not options.webtoon:
img.cropWhiteSpace(10.0)
if options.cutpagenumbers and not options.webtoon:
img.cutPageNumber()
img.optimizeImage(options.gamma)
if overrideQuality != 5:
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
options.landscapemode, overrideQuality)
else:
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
options.landscapemode, options.quality)
img.optimizeImage(options.gamma)
if options.forcepng and not options.forcecolor:
img.quantizeImage()
@@ -361,16 +359,20 @@ def dirImgProcess(path):
while not splitpages.ready():
# noinspection PyBroadException
try:
queue.get(True, 1)
queue.get(True, 5)
except:
pass
if not GUI.conversionAlive:
pool.terminate()
rmtree(os.path.join(path, '..', '..'))
raise UserWarning("Conversion interrupted.")
GUI.emit(QtCore.SIGNAL("progressBarTick"))
pool.join()
queue.close()
try:
splitpages = splitpages.get()
except:
rmtree(path)
rmtree(os.path.join(path, '..', '..'))
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
splitpages = filter(None, splitpages)
splitpages.sort()
@@ -379,6 +381,9 @@ def dirImgProcess(path):
splitCount += 1
pagenumbermodifier += 1
pagenumbermodifier += 1
else:
rmtree(os.path.join(path, '..', '..'))
raise UserWarning("Source directory is empty.")
def fileImgProcess_init(queue, options):
@@ -448,7 +453,6 @@ def genEpubStruct(path):
chapterlist = []
cover = None
_, deviceres, _, _, panelviewsize = options.profileData
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
@@ -594,8 +598,9 @@ def genEpubStruct(path):
def getWorkFolder(afile):
workdir = tempfile.mkdtemp()
if os.path.isdir(afile):
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.join(os.path.splitext(afile)[0], '..'))
try:
os.rmdir(workdir) # needed for copytree() fails if dst already exists
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
@@ -607,8 +612,13 @@ def getWorkFolder(afile):
raise
elif afile.lower().endswith('.pdf'):
pdf = pdfjpgextract.PdfJpgExtract(afile)
path = pdf.extract()
path, njpg = pdf.extract()
if njpg == 0:
rmtree(path)
raise UserWarning("Failed to extract images.")
else:
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.dirname(afile))
cbx = cbxarchive.CBxArchive(afile)
if cbx.isCbxFile():
try:
@@ -647,13 +657,16 @@ def sanitizeTree(filetree):
splitname = os.path.splitext(name)
slugified = slugify(splitname[0])
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]))
for name in dirs:
if name.startswith('.'):
os.remove(os.path.join(root, name))
else:
os.rename(os.path.join(root, name), os.path.join(root, slugify(name)))
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):
@@ -667,6 +680,158 @@ def sanitizeTreeBeforeConversion(filetree):
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():
print ('comic2ebook v%(__version__)s. '
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
@@ -679,48 +844,64 @@ def Usage():
def main(argv=None, qtGUI=None):
global parser, options, epub_path, splitCount, GUI
usage = "Usage: %prog [options] comic_file|comic_folder"
parser = OptionParser(usage=usage, version=__version__)
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) "
"[Default=KHD]")
parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [Default=filename]")
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (Right-to-left reading and splitting) [Default=False]")
parser.add_option("--quality", type="int", dest="quality", default="0",
help="Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]")
parser.add_option("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
help="Outputs a CBZ archive and does not generate EPUB")
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
parser.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG (For non-Kindle devices) [Default=False]")
parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]")
parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution [Default=False]")
parser.add_option("--stretch", action="store_true", dest="stretch", default=False,
help="Stretch images to device's resolution [Default=False]")
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Use black borders instead of white ones when not stretching and ratio "
+ "is not like the device's one [Default=False]")
parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
help="Rotate landscape pages instead of splitting them [Default=False]")
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
help="Disable splitting and rotation [Default=False]")
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
help="Do not try to cut page numbering on images [Default=True]")
parser.add_option("-o", "--output", action="store", dest="output", default=None,
help="Output generated file (EPUB or CBZ) to specified directory or file")
parser.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
help="Do not convert images to grayscale [Default=False]")
parser.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile [Default=0]")
parser.add_option("--customheight", type="int", dest="customheight", default=0,
help="Replace screen height provided by device profile [Default=0]")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
help="Verbose output [Default=False]")
parser = OptionParser(usage="Usage: %prog [options] comic_file|comic_folder", add_help_option=False)
mainOptions = OptionGroup(parser, "MAIN")
experimentalOptions = OptionGroup(parser, "EXPERIMENTAL")
processingOptions = OptionGroup(parser, "PROCESSING")
outputOptions = OptionGroup(parser, "OUTPUT SETTINGS")
customProfileOptions = OptionGroup(parser, "CUSTOM PROFILE")
otherOptions = OptionGroup(parser, "OTHER")
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD,"
" KFHD8, KFA) [Default=KHD]")
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (Right-to-left reading and splitting)")
outputOptions.add_option("-o", "--output", action="store", dest="output", default=None,
help="Output generated file to specified directory or file")
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
help="Comic title [Default=filename or directory name]")
outputOptions.add_option("--cbz-output", action="store_true", dest="cbzoutput", default=False,
help="Outputs a CBZ archive and does not generate EPUB")
outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
help="Split output into multiple files"),
experimentalOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"),
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Use black borders instead of white ones")
processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG (For non-Kindle devices)")
processingOptions.add_option("--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]")
processingOptions.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
help="Don't try to cut page numbering on images")
processingOptions.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
help="Don't apply image preprocessing")
processingOptions.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
help="Disable splitting and rotation")
processingOptions.add_option("--rotate", action="store_true", dest="rotate", default=False,
help="Rotate landscape pages instead of splitting them")
processingOptions.add_option("--stretch", action="store_true", dest="stretch", 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)
checkOptions()
if qtGUI:
@@ -732,33 +913,57 @@ def main(argv=None, qtGUI=None):
parser.print_help()
return
path = getWorkFolder(args[0])
if options.title == 'defaulttitle':
options.title = os.path.splitext(os.path.basename(args[0]))[0]
if options.webtoon:
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
if options.imgproc:
print "Processing images..."
print "\nProcessing images..."
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images')
dirImgProcess(path + "/OEBPS/Images/")
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
if options.cbzoutput:
# if CBZ output wanted, compress all images and return filepath
print "\nCreating CBZ file..."
filepath = getOutputFilename(args[0], options.output, '.cbz')
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if options.batchsplit:
tomes = preSplitDirectory(path)
else:
print "\nCreating EPUB structure..."
genEpubStruct(path)
# actually zip the ePub
filepath = getOutputFilename(args[0], options.output, '.epub')
make_archive(path + '_comic', 'zip', path)
move(path + '_comic.zip', filepath)
rmtree(path)
tomes = [path]
filepath = []
tomeNumber = 0
for tome in tomes:
if len(tomes) > 1:
tomeNumber += 1
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
def getOutputFilename(srcpath, wantedname, ext):
def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
if not ext.startswith('.'):
ext = '.' + ext
if wantedname is not None:
@@ -767,19 +972,23 @@ def getOutputFilename(srcpath, wantedname, ext):
elif os.path.isdir(srcpath):
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
else:
filename = os.path.abspath(options.output) + "/" \
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
filename = os.path.abspath(options.output) + "/" + os.path.basename(os.path.splitext(srcpath)[0]) + ext
elif os.path.isdir(srcpath):
filename = srcpath + ext
filename = srcpath + tomeNumber + ext
else:
filename = os.path.splitext(srcpath)[0] + ext
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
if os.path.isfile(filename):
filename = os.path.splitext(filename)[0] + '_kcc' + ext
filename = os.path.splitext(filename)[0] + '_kcc' + tomeNumber + ext
return filename
def checkOptions():
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.
if options.profile == 'K4T' or options.profile == 'KHD':
options.landscapemode = True
@@ -816,6 +1025,10 @@ def checkOptions():
options.landscapemode = False
options.panelview = False
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
if options.customwidth != 0 or options.customheight != 0:
X = image.ProfileData.Profiles[options.profile][1][0]
@@ -831,11 +1044,6 @@ def checkOptions():
options.profileData = image.ProfileData.Profiles[options.profile]
def getEpubPath():
global epub_path
return epub_path
if __name__ == "__main__":
freeze_support()
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'
__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
try:
# noinspection PyUnresolvedReferences
# noinspection PyUnresolvedReferences,PyPackageRequirements
from PIL import Image, ImageOps, ImageStat, ImageChops
except ImportError:
print "ERROR: Pillow is not installed!"
@@ -85,6 +85,7 @@ class ProfileData:
'KF': ("Kindle Fire", (600, 1024), Palette16, 1.0, (900, 1536)),
'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)),
'KFA': ("Kindle for Android", (0, 0), Palette16, 1.0, (0, 0)),
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
}
@@ -100,6 +101,7 @@ class ProfileData:
"Kindle Fire": 'KF',
"Kindle Fire HD 7\"": 'KFHD',
"Kindle Fire HD 8.9\"": 'KFHD8',
"Kindle for Android": 'KFA',
"Other": 'OTHER'
}
@@ -113,19 +115,26 @@ class ComicPage:
# Detect corrupted files - Phase 2
try:
self.origFileName = source
self.filename = os.path.basename(self.origFileName)
self.image = Image.open(source)
except IOError:
raise RuntimeError('Cannot read image file %s' % source)
# Detect corrupted files - Phase 3
try:
self.image = Image.open(source)
self.image.verify()
except:
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 = self.image.convert('RGB')
def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None):
filename = os.path.basename(self.origFileName)
try:
if not color:
self.image = self.image.convert('L') # convert to grayscale
@@ -134,13 +143,13 @@ class ComicPage:
else:
suffix = ""
if wipe:
os.remove(os.path.join(targetdir, filename))
os.remove(os.path.join(targetdir, self.filename))
else:
suffix += "_kcchq"
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:
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:
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,
landscapeMode=False, qualityMode=0):
method = Image.ANTIALIAS
if black_borders:
if '-KCCFW' in str(self.filename):
fill = 'white'
elif '-KCCFB' in str(self.filename):
fill = 'black'
else:
fill = 'white'
if black_borders:
fill = 'black'
else:
fill = 'white'
if qualityMode == 0:
size = (self.size[0], self.size[1])
else:
@@ -225,7 +239,7 @@ class ComicPage:
# source is portrait and target is landscape, so split by the height
leftbox = (0, 0, width, height / 2)
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]
filetwo = targetdir + '/' + filename[0] + '_kccb' + filename[1]
try:

View File

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

View File

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