mirror of
https://github.com/ciromattia/kcc
synced 2026-04-17 14:38:47 +00:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c96de9cdf | ||
|
|
3a645abe6f | ||
|
|
27bd6f96e7 | ||
|
|
a98fac2e95 | ||
|
|
f645b65a9e | ||
|
|
6eaf8cc374 | ||
|
|
61c0b691ab | ||
|
|
394cefb2de | ||
|
|
30d6a55e3c | ||
|
|
e32018e8f6 | ||
|
|
6b002a8475 | ||
|
|
97e23c8f50 | ||
|
|
e558ffd807 | ||
|
|
71d158ca45 | ||
|
|
5f8e5e0be9 | ||
|
|
ff91eb1407 | ||
|
|
877a859ef4 | ||
|
|
f8b29cd967 | ||
|
|
3d2554c557 | ||
|
|
b7d7204d40 | ||
|
|
876d26d174 | ||
|
|
3ccb1a63aa | ||
|
|
c8bb9b4f5f | ||
|
|
723be29118 | ||
|
|
2865915cdf | ||
|
|
d3e0c2bb6e | ||
|
|
5e7ae73861 | ||
|
|
61206b2169 | ||
|
|
92e2a8913b | ||
|
|
ad827828d7 | ||
|
|
faf16084a3 | ||
|
|
38d2b55456 | ||
|
|
abcebc54e8 | ||
|
|
40cb963c99 | ||
|
|
9d267a6cc4 | ||
|
|
462f24149b | ||
|
|
4744b62f91 | ||
|
|
08244e7fdc | ||
|
|
f64fb1bee1 | ||
|
|
743c3b2466 | ||
|
|
aefa36fef8 | ||
|
|
d7f6503196 | ||
|
|
3375b8c898 | ||
|
|
52e5919ccd | ||
|
|
1c2e57adb6 | ||
|
|
2e99d6ee0a | ||
|
|
6744815f77 | ||
|
|
0ba44ab2d3 | ||
|
|
a6006450de | ||
|
|
c20e2ba451 | ||
|
|
7a0b387c1c | ||
|
|
fdfe5fbe39 | ||
|
|
35751efad5 | ||
|
|
7005c9e40a | ||
|
|
118cf25ff7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ dist
|
|||||||
kindlegen*
|
kindlegen*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
UnRAR.exe
|
||||||
|
|||||||
13
KCC-OSX.ui
13
KCC-OSX.ui
@@ -88,15 +88,18 @@
|
|||||||
<enum>Qt::NoFocus</enum>
|
<enum>Qt::NoFocus</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string><html><head/><body><p><span style=" font-size:12pt;">Enable image upscaling.<br/>Aspect ratio will be preserved.</span></p></body></html></string>
|
<string><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></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><html><head/><body><p><span style=" font-size:12pt;">Enable image stretching.<br/>Aspect ratio will be not preserved.</span></p></body></html></string>
|
<string><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></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
16
KCC.ui
@@ -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><html><head/><body><p>Enable image upscaling.<br/>Aspect ratio will be preserved.</p></body></html></string>
|
<string><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></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><html><head/><body><p>Enable image stretching.<br/>Aspect ratio will be not preserved.</p></body></html></string>
|
<string><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></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
101
README.md
@@ -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
4
kcc.py
@@ -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'
|
||||||
|
|||||||
354
kcc/KCC_gui.py
354
kcc/KCC_gui.py
@@ -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()
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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'
|
||||||
@@ -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
315
kcc/comic2panel.py
Normal 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)
|
||||||
30
kcc/image.py
30
kcc/image.py
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user