mirror of
https://github.com/ciromattia/kcc
synced 2026-04-16 05:58:52 +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*
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
UnRAR.exe
|
||||
|
||||
13
KCC-OSX.ui
13
KCC-OSX.ui
@@ -88,15 +88,18 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<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 name="text">
|
||||
<string>Upscale images</string>
|
||||
<string>Stretch/Upscale</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="StretchBox">
|
||||
<widget class="QCheckBox" name="WebtoonBox">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
@@ -106,10 +109,10 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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 name="text">
|
||||
<string>Stretch images</string>
|
||||
<string>Webtoon mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
16
KCC.ui
16
KCC.ui
@@ -59,6 +59,9 @@
|
||||
</font>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<property name="leftMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="ProcessingBox">
|
||||
<property name="focusPolicy">
|
||||
@@ -78,23 +81,26 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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 name="text">
|
||||
<string>Upscale images</string>
|
||||
<string>Stretch/Upscale</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="StretchBox">
|
||||
<widget class="QCheckBox" name="WebtoonBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><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 name="text">
|
||||
<string>Stretch images</string>
|
||||
<string>Webtoon mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</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/)
|
||||
- **Linux:** Just download sourcecode and launch: `python kcc.py`
|
||||
|
||||
_It has been reported by a couple of users that version 2.10 crashing on OSX at start. We don't know if that issue still exist in version 3.0.
|
||||
If it happens to you please append your message to [Issue #52](https://github.com/ciromattia/kcc/issues/52)._
|
||||
|
||||
## INPUT FORMATS
|
||||
**KCC** can understand and convert, at the moment, the following file types:
|
||||
- PNG, JPG, GIF, TIFF, BMP
|
||||
@@ -50,15 +47,16 @@ If it happens to you please append your message to [Issue #52](https://github.co
|
||||
* Use high quality source files. **This little detail have a major impact on the final result.**
|
||||
* Read tooltip of _High/Ultra quality_ option. There are many important informations there.
|
||||
* When converting images smaller than device resolution remember to enable upscaling.
|
||||
* Panel View (auto zooming every part of page) can be disabled directly on Kindle. There is no KCC option to do that.
|
||||
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
|
||||
* Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers.
|
||||
* The first image found will be set as the comic's cover.
|
||||
* All files/directories will be added to EPUB in alphabetical order.
|
||||
* Output MOBI file should be uploaded via USB. Other methods (e.g. via Calibre) might corrupt it.
|
||||
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
|
||||
|
||||
### GUI
|
||||
|
||||
Should be pretty self-explanatory.
|
||||
Should be pretty self-explanatory. All options have detailed informations in tooltips.
|
||||
After completed conversion you should find ready file alongside the original input file (same directory).
|
||||
|
||||
### Standalone `comic2ebook.py` usage:
|
||||
@@ -67,30 +65,61 @@ After completed conversion you should find ready file alongside the original inp
|
||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) [Default=KHD]
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
|
||||
--quality=QUALITY Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
|
||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
||||
--upscale Resize images smaller than device's resolution [Default=False]
|
||||
--stretch Stretch images to device's resolution [Default=False]
|
||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output generated file (EPUB or CBZ) to specified directory or file
|
||||
--forcecolor Do not convert images to grayscale [Default=False]
|
||||
--customwidth=WIDTH Replace screen width provided by device profile [Default=0]
|
||||
--customheight=HEIGHT Replace screen height provided by device profile [Default=0]
|
||||
-v, --verbose Verbose output [Default=False]
|
||||
MAIN:
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFA) [Default=KHD]
|
||||
-q QUALITY, --quality=QUALITY
|
||||
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting)
|
||||
|
||||
EXPERIMENTAL:
|
||||
-w, --webtoon Webtoon processing mode
|
||||
|
||||
OUTPUT SETTINGS:
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output generated file to specified directory or file
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
--cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
--batchsplit Split output into multiple files
|
||||
|
||||
PROCESSING:
|
||||
--blackborders Use black borders instead of white ones
|
||||
--forcecolor Don't convert images to grayscale
|
||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices)
|
||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
||||
--nocutpagenumbers Don't try to cut page numbering on images
|
||||
--noprocessing Don't apply image preprocessing
|
||||
--nosplitrotate Disable splitting and rotation
|
||||
--rotate Rotate landscape pages instead of splitting them
|
||||
--stretch Stretch images to device's resolution
|
||||
--upscale Resize images smaller than device's resolution
|
||||
|
||||
CUSTOM PROFILE:
|
||||
--customwidth=CUSTOMWIDTH
|
||||
Replace screen width provided by device profile
|
||||
--customheight=CUSTOMHEIGHT
|
||||
Replace screen height provided by device profile
|
||||
|
||||
OTHER:
|
||||
-v, --verbose Verbose output
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
### Standalone `comic2panel.py` usage:
|
||||
|
||||
```
|
||||
Usage: comic2panel.py [options] comic_folder
|
||||
|
||||
Options:
|
||||
MANDATORY:
|
||||
-y HEIGHT, --height=HEIGHT
|
||||
Height of the target device screen
|
||||
-i, --in-place Overwrite source directory
|
||||
|
||||
OTHER:
|
||||
-d, --debug Create debug file for every splitted image
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
## CREDITS
|
||||
@@ -213,8 +242,20 @@ The app relies and includes the following scripts/binaries:
|
||||
* Added support for custom width/height
|
||||
* Added option to disable color conversion
|
||||
|
||||
####3.1:
|
||||
* Added profile: Kindle for Android
|
||||
* Add file/directory dialogs now support multiselect
|
||||
* Many small fixes and tweaks
|
||||
|
||||
####3.2:
|
||||
* Too big EPUB files are now splitted before conversion to MOBI
|
||||
* Added experimental parser of manga webtoons
|
||||
* Improved error handling
|
||||
|
||||
####3.2.1:
|
||||
* Hotfixed crash occurring on OS with Russian locale
|
||||
|
||||
## KNOWN ISSUES
|
||||
* _Add directory_ dialog allow to select multiple directories but they are not added to job list. [QT bug.](https://bugreports.qt-project.org/browse/QTBUG-21372)
|
||||
* Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations.
|
||||
|
||||
## COPYRIGHT
|
||||
|
||||
4
kcc.py
4
kcc.py
@@ -16,8 +16,8 @@
|
||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '3.0'
|
||||
|
||||
__version__ = '3.2.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__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
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '3.0'
|
||||
__version__ = '3.2.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -27,12 +27,14 @@ import sys
|
||||
import shutil
|
||||
import traceback
|
||||
import urllib2
|
||||
import time
|
||||
import comic2ebook
|
||||
import kindlestrip
|
||||
from image import ProfileData
|
||||
from subprocess import call, Popen, STDOUT, PIPE
|
||||
from PyQt4 import QtGui, QtCore
|
||||
from xml.dom.minidom import parse
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
|
||||
class Icons:
|
||||
@@ -57,6 +59,19 @@ class Icons:
|
||||
self.error.addPixmap(QtGui.QPixmap(":/Status/icons/error.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
|
||||
|
||||
class HTMLStripper(HTMLParser):
|
||||
def __init__(self):
|
||||
HTMLParser.__init__(self)
|
||||
self.reset()
|
||||
self.fed = []
|
||||
|
||||
def handle_data(self, d):
|
||||
self.fed.append(d)
|
||||
|
||||
def get_data(self):
|
||||
return ''.join(self.fed)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
class VersionThread(QtCore.QThread):
|
||||
def __init__(self, parent):
|
||||
@@ -74,7 +89,8 @@ class VersionThread(QtCore.QThread):
|
||||
return
|
||||
latestVersion = XML.childNodes[0].getElementsByTagName('latest')[0].childNodes[0].toxml()
|
||||
if tuple(map(int, (latestVersion.split(".")))) > tuple(map(int, (__version__.split(".")))):
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'New version is available!', 'warning')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), '<a href="http://kcc.vulturis.eu/">'
|
||||
'<b>New version is available!</b></a>', 'warning')
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
@@ -86,6 +102,16 @@ class WorkerThread(QtCore.QThread):
|
||||
def __del__(self):
|
||||
self.wait()
|
||||
|
||||
def sync(self):
|
||||
self.conversionAlive = self.parent.conversionAlive
|
||||
|
||||
def clean(self):
|
||||
self.parent.needClean = True
|
||||
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>Conversion interrupted.</b>', 'error')
|
||||
self.emit(QtCore.SIGNAL("modeConvert"), True)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def run(self):
|
||||
self.emit(QtCore.SIGNAL("modeConvert"), False)
|
||||
profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())]
|
||||
@@ -102,31 +128,40 @@ class WorkerThread(QtCore.QThread):
|
||||
if self.parent.currentMode > 1:
|
||||
if GUI.ProcessingBox.isChecked():
|
||||
argv.append("--noprocessing")
|
||||
if GUI.UpscaleBox.isChecked() and not GUI.StretchBox.isChecked():
|
||||
argv.append("--upscale")
|
||||
if GUI.NoRotateBox.isChecked():
|
||||
argv.append("--nosplitrotate")
|
||||
if GUI.BorderBox.isChecked():
|
||||
argv.append("--blackborders")
|
||||
if GUI.StretchBox.isChecked():
|
||||
if GUI.UpscaleBox.checkState() == 1:
|
||||
argv.append("--stretch")
|
||||
elif GUI.UpscaleBox.checkState() == 2:
|
||||
argv.append("--upscale")
|
||||
if GUI.NoDitheringBox.isChecked():
|
||||
argv.append("--forcepng")
|
||||
if GUI.WebtoonBox.isChecked():
|
||||
argv.append("--webtoon")
|
||||
if float(self.parent.GammaValue) > 0.09:
|
||||
argv.append("--gamma=" + self.parent.GammaValue)
|
||||
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
||||
argv.append("--cbz-output")
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
argv.append("--batchsplit")
|
||||
if self.parent.currentMode > 2:
|
||||
argv.append("--customwidth=" + str(GUI.customWidth.text()))
|
||||
argv.append("--customheight=" + str(GUI.customHeight.text()))
|
||||
if GUI.ColorBox.isChecked():
|
||||
argv.append("--forcecolor")
|
||||
for i in range(GUI.JobList.count()):
|
||||
currentJobs.append(str(GUI.JobList.item(i).text()))
|
||||
if GUI.JobList.item(i).icon().isNull():
|
||||
currentJobs.append(str(GUI.JobList.item(i).text()))
|
||||
GUI.JobList.clear()
|
||||
for job in currentJobs:
|
||||
time.sleep(0.5)
|
||||
if not self.conversionAlive:
|
||||
self.clean()
|
||||
return
|
||||
self.errors = False
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Source: ' + job, 'info')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>Source:</b> ' + job, 'info')
|
||||
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file...', 'info')
|
||||
else:
|
||||
@@ -136,30 +171,80 @@ class WorkerThread(QtCore.QThread):
|
||||
try:
|
||||
outputPath = comic2ebook.main(jobargv, self)
|
||||
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
||||
except UserWarning as warn:
|
||||
if not self.conversionAlive:
|
||||
self.clean()
|
||||
return
|
||||
else:
|
||||
self.errors = True
|
||||
self.emit(QtCore.SIGNAL("addMessage"), str(warn), 'warning')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create output file!', 'warning')
|
||||
except Exception as err:
|
||||
self.errors = True
|
||||
type_, value_, traceback_ = sys.exc_info()
|
||||
self.emit(QtCore.SIGNAL("showDialog"), "Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||
% (jobargv[-1], str(err), traceback.format_tb(traceback_)))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create EPUB!', 'error')
|
||||
if not self.conversionAlive:
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
self.clean()
|
||||
return
|
||||
if not self.errors:
|
||||
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating CBZ file... Done!', 'info', True)
|
||||
else:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True)
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
if not os.path.getsize(outputPath) > 314572800:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
||||
tomeNumber = 0
|
||||
for item in outputPath:
|
||||
tomeNumber += 1
|
||||
if len(outputPath) > 1:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber)
|
||||
+ '/' + str(len(outputPath)) + ')...', 'info')
|
||||
else:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
||||
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||
try:
|
||||
retcode = call('kindlegen -verbose "' + outputPath + '"', shell=True)
|
||||
self.kindlegenErrorCode = 0
|
||||
if os.path.getsize(item) < 367001600:
|
||||
output = Popen('kindlegen -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:
|
||||
# ERROR: Unknown generic error
|
||||
self.kindlegenErrorCode = 1
|
||||
continue
|
||||
if retcode == 0:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True)
|
||||
if not self.conversionAlive:
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
if os.path.exists(item.replace('.epub', '.mobi')):
|
||||
os.remove(item.replace('.epub', '.mobi'))
|
||||
self.clean()
|
||||
return
|
||||
if self.kindlegenErrorCode == 0:
|
||||
if len(outputPath) > 1:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber) + '/'
|
||||
+ str(len(outputPath)) + ')... Done!', 'info',
|
||||
True)
|
||||
else:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file... Done!', 'info', True)
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header...', 'info')
|
||||
os.remove(outputPath)
|
||||
mobiPath = outputPath.replace('.epub', '.mobi')
|
||||
os.remove(item)
|
||||
mobiPath = item.replace('.epub', '.mobi')
|
||||
shutil.move(mobiPath, mobiPath + '_tostrip')
|
||||
try:
|
||||
kindlestrip.main((mobiPath + '_tostrip', mobiPath))
|
||||
@@ -175,62 +260,80 @@ class WorkerThread(QtCore.QThread):
|
||||
self.emit(QtCore.SIGNAL("addMessage"),
|
||||
'MOBI file will work correctly but it will be highly oversized.', 'warning')
|
||||
else:
|
||||
os.remove(outputPath)
|
||||
if os.path.exists(outputPath.replace('.epub', '.mobi')):
|
||||
os.remove(outputPath.replace('.epub', '.mobi'))
|
||||
epubSize = (os.path.getsize(item))/1024/1024
|
||||
os.remove(item)
|
||||
if os.path.exists(item.replace('.epub', '.mobi')):
|
||||
os.remove(item.replace('.epub', '.mobi'))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Try converting a smaller batch.', 'error')
|
||||
else:
|
||||
excess = (os.path.getsize(outputPath) - 314572800)/1024/1024
|
||||
os.remove(outputPath)
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file is too big for KindleGen!', 'error')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Limit exceeded by ' + str(excess) +
|
||||
' MB. Try converting a smaller batch.', 'error')
|
||||
if self.kindlegenErrorCode == 1 and self.kindlegenError:
|
||||
self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
|
||||
if self.kindlegenErrorCode == 23026:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
|
||||
'error')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
|
||||
' Supported size: ~300MB.', 'error')
|
||||
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
||||
self.parent.needClean = True
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'All jobs completed.', 'info')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>All jobs completed.</b>', 'info')
|
||||
self.emit(QtCore.SIGNAL("modeConvert"), True)
|
||||
|
||||
|
||||
# noinspection PyBroadException
|
||||
class Ui_KCC(object):
|
||||
def selectDir(self):
|
||||
# Dialog allow to select multiple directories but we can't parse that. QT Bug.
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.JobList.clear()
|
||||
dname = QtGui.QFileDialog.getExistingDirectory(MainWindow, 'Select directory', self.lastPath)
|
||||
# Dirty, dirty way but OS native QFileDialogs don't support directory multiselect
|
||||
dirDialog = QtGui.QFileDialog(MainWindow, 'Select directory', self.lastPath)
|
||||
dirDialog.setFileMode(dirDialog.Directory)
|
||||
dirDialog.setOption(dirDialog.ShowDirsOnly, True)
|
||||
dirDialog.setOption(dirDialog.DontUseNativeDialog, True)
|
||||
l = dirDialog.findChild(QtGui.QListView, "listView")
|
||||
t = dirDialog.findChild(QtGui.QTreeView)
|
||||
if l:
|
||||
l.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
if t:
|
||||
t.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
if dirDialog.exec_() == 1:
|
||||
dnames = dirDialog.selectedFiles()
|
||||
else:
|
||||
dnames = ""
|
||||
# Lame UTF-8 security measure
|
||||
try:
|
||||
str(dname)
|
||||
except Exception:
|
||||
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
||||
QtGui.QMessageBox.Ok)
|
||||
return
|
||||
if str(dname) != "":
|
||||
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
|
||||
GUI.JobList.addItem(dname)
|
||||
for dname in dnames:
|
||||
try:
|
||||
str(dname)
|
||||
except Exception:
|
||||
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
||||
QtGui.QMessageBox.Ok)
|
||||
return
|
||||
if str(dname) != "":
|
||||
if sys.platform == 'win32':
|
||||
dname = dname.replace('/', '\\')
|
||||
self.lastPath = os.path.abspath(os.path.join(str(dname), os.pardir))
|
||||
GUI.JobList.addItem(dname)
|
||||
|
||||
def selectFile(self):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.JobList.clear()
|
||||
if self.UnRAR:
|
||||
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
|
||||
'*.cbz *.cbr *.zip *.rar *.pdf')
|
||||
fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
|
||||
'*.cbz *.cbr *.zip *.rar *.pdf')
|
||||
else:
|
||||
fname = QtGui.QFileDialog.getOpenFileName(MainWindow, 'Select file', self.lastPath,
|
||||
'*.cbz *.zip *.pdf')
|
||||
fnames = QtGui.QFileDialog.getOpenFileNames(MainWindow, 'Select file', self.lastPath,
|
||||
'*.cbz *.zip *.pdf')
|
||||
# Lame UTF-8 security measure
|
||||
try:
|
||||
str(fname)
|
||||
except Exception:
|
||||
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
||||
QtGui.QMessageBox.Ok)
|
||||
return
|
||||
if str(fname) != "":
|
||||
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
|
||||
GUI.JobList.addItem(fname)
|
||||
for fname in fnames:
|
||||
try:
|
||||
str(fname)
|
||||
except Exception:
|
||||
QtGui.QMessageBox.critical(MainWindow, 'KCC Error', "Path cannot contain non-ASCII characters.",
|
||||
QtGui.QMessageBox.Ok)
|
||||
return
|
||||
if str(fname) != "":
|
||||
self.lastPath = os.path.abspath(os.path.join(str(fname), os.pardir))
|
||||
GUI.JobList.addItem(fname)
|
||||
|
||||
def clearJobs(self):
|
||||
GUI.JobList.clear()
|
||||
@@ -268,15 +371,21 @@ class Ui_KCC(object):
|
||||
GUI.OptionsExpert.setEnabled(False)
|
||||
GUI.MangaBox.setEnabled(True)
|
||||
|
||||
def modeExpert(self):
|
||||
def modeExpert(self, KFA=False):
|
||||
self.modeAdvanced()
|
||||
self.currentMode = 3
|
||||
MainWindow.setMinimumSize(QtCore.QSize(420, 380))
|
||||
MainWindow.setMaximumSize(QtCore.QSize(420, 380))
|
||||
MainWindow.resize(420, 380)
|
||||
GUI.OptionsExpert.setEnabled(True)
|
||||
GUI.MangaBox.setCheckState(0)
|
||||
GUI.MangaBox.setEnabled(False)
|
||||
if KFA:
|
||||
GUI.ColorBox.setCheckState(2)
|
||||
GUI.FormatBox.setCurrentIndex(0)
|
||||
GUI.FormatBox.setEnabled(False)
|
||||
else:
|
||||
GUI.FormatBox.setEnabled(True)
|
||||
GUI.MangaBox.setCheckState(0)
|
||||
GUI.MangaBox.setEnabled(False)
|
||||
|
||||
def modeConvert(self, enable):
|
||||
if self.currentMode != 3:
|
||||
@@ -286,19 +395,33 @@ class Ui_KCC(object):
|
||||
GUI.ClearButton.setEnabled(enable)
|
||||
GUI.FileButton.setEnabled(enable)
|
||||
GUI.DeviceBox.setEnabled(enable)
|
||||
GUI.ConvertButton.setEnabled(enable)
|
||||
GUI.FormatBox.setEnabled(enable)
|
||||
GUI.OptionsBasic.setEnabled(enable)
|
||||
GUI.OptionsAdvanced.setEnabled(enable)
|
||||
GUI.OptionsAdvancedGamma.setEnabled(enable)
|
||||
GUI.OptionsExpert.setEnabled(enable)
|
||||
if enable:
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/Other/icons/convert.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
GUI.ConvertButton.setIcon(icon)
|
||||
GUI.ConvertButton.setText('Convert')
|
||||
GUI.ConvertButton.setEnabled(True)
|
||||
if self.currentMode == 1:
|
||||
self.modeBasic()
|
||||
elif self.currentMode == 2:
|
||||
self.modeAdvanced()
|
||||
elif self.currentMode == 3:
|
||||
self.modeExpert()
|
||||
else:
|
||||
self.conversionAlive = True
|
||||
self.worker.sync()
|
||||
icon = QtGui.QIcon()
|
||||
icon.addPixmap(QtGui.QPixmap(":/Other/icons/clear.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
|
||||
GUI.ConvertButton.setIcon(icon)
|
||||
GUI.ConvertButton.setText('Abort')
|
||||
GUI.ConvertButton.setEnabled(True)
|
||||
|
||||
def changeGamma(self, value):
|
||||
value = float(value)
|
||||
@@ -309,30 +432,70 @@ class Ui_KCC(object):
|
||||
GUI.GammaLabel.setText('Gamma: ' + str(value))
|
||||
self.GammaValue = value
|
||||
|
||||
def changeDevice(self, value, start=False):
|
||||
if value == 11 and (start or self.currentMode != 3):
|
||||
def toggleWebtoonBox(self, value):
|
||||
if value:
|
||||
GUI.NoRotateBox.setEnabled(False)
|
||||
GUI.NoRotateBox.setChecked(True)
|
||||
GUI.QualityBox.setEnabled(False)
|
||||
GUI.QualityBox.setChecked(False)
|
||||
GUI.BorderBox.setEnabled(False)
|
||||
GUI.BorderBox.setChecked(False)
|
||||
self.addMessage('If images are color setting <i>Gamma</i> to 1.0 is recommended.', 'info')
|
||||
else:
|
||||
GUI.NoRotateBox.setEnabled(True)
|
||||
GUI.QualityBox.setEnabled(True)
|
||||
GUI.BorderBox.setEnabled(True)
|
||||
|
||||
def toggleNoSplitRotate(self, value):
|
||||
if value:
|
||||
GUI.RotateBox.setEnabled(False)
|
||||
GUI.RotateBox.setChecked(False)
|
||||
else:
|
||||
GUI.RotateBox.setEnabled(True)
|
||||
|
||||
def changeDevice(self, value):
|
||||
if value == 12:
|
||||
GUI.BasicModeButton.setEnabled(False)
|
||||
GUI.AdvModeButton.setEnabled(False)
|
||||
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
|
||||
'List of supported Non-Kindle devices</a>', 'info')
|
||||
self.modeExpert()
|
||||
elif value == 11:
|
||||
GUI.BasicModeButton.setEnabled(False)
|
||||
GUI.AdvModeButton.setEnabled(False)
|
||||
self.modeExpert(True)
|
||||
elif self.currentMode == 3:
|
||||
GUI.BasicModeButton.setEnabled(True)
|
||||
GUI.AdvModeButton.setEnabled(True)
|
||||
self.modeBasic()
|
||||
if value in [0, 1, 5, 6, 7, 8, 9, 11]:
|
||||
if value in [0, 1, 5, 6, 7, 8, 9, 12]:
|
||||
GUI.QualityBox.setCheckState(0)
|
||||
GUI.QualityBox.setEnabled(False)
|
||||
else:
|
||||
GUI.QualityBox.setEnabled(True)
|
||||
|
||||
def stripTags(self, html):
|
||||
s = HTMLStripper()
|
||||
s.feed(html)
|
||||
return s.get_data()
|
||||
|
||||
def addMessage(self, message, icon=None, replace=False):
|
||||
if icon:
|
||||
icon = eval('self.icons.' + icon)
|
||||
item = QtGui.QListWidgetItem(icon, message)
|
||||
item = QtGui.QListWidgetItem(icon, ' ' + self.stripTags(message))
|
||||
else:
|
||||
item = QtGui.QListWidgetItem(message)
|
||||
item = QtGui.QListWidgetItem(' ' + self.stripTags(message))
|
||||
if replace:
|
||||
GUI.JobList.takeItem(GUI.JobList.count()-1)
|
||||
label = QtGui.QLabel(message)
|
||||
label.setOpenExternalLinks(True)
|
||||
if sys.platform == 'darwin':
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
label.setFont(font)
|
||||
item.setTextColor(QtGui.QColor("white"))
|
||||
GUI.JobList.addItem(item)
|
||||
GUI.JobList.setItemWidget(item, label)
|
||||
GUI.JobList.scrollToBottom()
|
||||
|
||||
def showDialog(self, message):
|
||||
@@ -340,7 +503,6 @@ class Ui_KCC(object):
|
||||
|
||||
def updateProgressbar(self, new=False, status=False):
|
||||
if new == "status":
|
||||
pass
|
||||
GUI.ProgressBar.setFormat(status)
|
||||
elif new:
|
||||
GUI.ProgressBar.setMaximum(new - 1)
|
||||
@@ -350,25 +512,38 @@ class Ui_KCC(object):
|
||||
GUI.ProgressBar.setValue(GUI.ProgressBar.value() + 1)
|
||||
|
||||
def convertStart(self):
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.JobList.clear()
|
||||
if GUI.JobList.count() == 0:
|
||||
self.addMessage('No files selected! Please choose files to convert.', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
if self.currentMode > 2 and (str(GUI.customWidth.text()) == '' or str(GUI.customHeight.text()) == ''):
|
||||
GUI.JobList.clear()
|
||||
self.addMessage('Target resolution is not set!', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
self.worker.start()
|
||||
if self.conversionAlive:
|
||||
GUI.ConvertButton.setEnabled(False)
|
||||
self.addMessage('Process will be interrupted. Please wait.', 'warning')
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
else:
|
||||
if self.needClean:
|
||||
self.needClean = False
|
||||
GUI.JobList.clear()
|
||||
if GUI.JobList.count() == 0:
|
||||
self.addMessage('No files selected! Please choose files to convert.', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
if self.currentMode > 2 and (str(GUI.customWidth.text()) == '' or str(GUI.customHeight.text()) == ''):
|
||||
GUI.JobList.clear()
|
||||
self.addMessage('Target resolution is not set!', 'error')
|
||||
self.needClean = True
|
||||
return
|
||||
self.worker.start()
|
||||
|
||||
def hideProgressBar(self):
|
||||
GUI.ProgressBar.hide()
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def saveSettings(self, event):
|
||||
if self.conversionAlive:
|
||||
GUI.ConvertButton.setEnabled(False)
|
||||
self.addMessage('Process will be interrupted. Please wait.', 'warning')
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
event.ignore()
|
||||
if not GUI.ConvertButton.isEnabled():
|
||||
event.ignore()
|
||||
self.settings.setValue('lastPath', self.lastPath)
|
||||
self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex())
|
||||
self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex())
|
||||
@@ -380,7 +555,7 @@ class Ui_KCC(object):
|
||||
'UpscaleBox': GUI.UpscaleBox.checkState(),
|
||||
'NoRotateBox': GUI.NoRotateBox.checkState(),
|
||||
'BorderBox': GUI.BorderBox.checkState(),
|
||||
'StretchBox': GUI.StretchBox.checkState(),
|
||||
'WebtoonBox': GUI.WebtoonBox.checkState(),
|
||||
'NoDitheringBox': GUI.NoDitheringBox.checkState(),
|
||||
'ColorBox': GUI.ColorBox.checkState(),
|
||||
'customWidth': GUI.customWidth.text(),
|
||||
@@ -403,30 +578,36 @@ class Ui_KCC(object):
|
||||
self.options = self.options.toPyObject()
|
||||
self.worker = WorkerThread(self)
|
||||
self.versionCheck = VersionThread(self)
|
||||
self.conversionAlive = False
|
||||
self.needClean = True
|
||||
|
||||
self.addMessage('Welcome!', 'info')
|
||||
self.addMessage('Remember: all options have additional informations in tooltips.', 'info')
|
||||
if call('kindlegen', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
|
||||
self.addMessage('<b>Welcome!</b>', 'info')
|
||||
self.addMessage('<b>Remember:</b> All options have additional informations in tooltips.', 'info')
|
||||
if call('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
|
||||
self.KindleGen = True
|
||||
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:
|
||||
if "Amazon kindlegen" in line:
|
||||
versionCheck = line.split('V')[1].split(' ')[0]
|
||||
if tuple(map(int, (versionCheck.split(".")))) < tuple(map(int, ('2.9'.split(".")))):
|
||||
self.addMessage('Your kindlegen is outdated! Creating MOBI might fail.'
|
||||
' Please update kindlegen from Amazon\'s website.', 'warning')
|
||||
self.addMessage('Your <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
|
||||
'1000765211">kindlegen</a> is outdated! Creating MOBI might fail.'
|
||||
' Please update <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
|
||||
'1000765211">kindlegen</a> from Amazon\'s website.', 'warning')
|
||||
break
|
||||
else:
|
||||
self.KindleGen = False
|
||||
formats = ['EPUB', 'CBZ']
|
||||
self.addMessage('Cannot find kindlegen in PATH! MOBI creation will be disabled.', 'warning')
|
||||
if call('unrar', stdout=PIPE, stderr=STDOUT, shell=True) == 0:
|
||||
self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId='
|
||||
'1000765211">kindlegen</a> in PATH! MOBI creation will be disabled.', 'warning')
|
||||
rarExitCode = call('unrar', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
if rarExitCode == 0 or rarExitCode == 7:
|
||||
self.UnRAR = True
|
||||
else:
|
||||
self.UnRAR = False
|
||||
self.addMessage('Cannot find UnRAR! Processing of CBR/RAR files will be disabled.', 'warning')
|
||||
self.addMessage('Cannot find <a href="http://www.rarlab.com/rar_add.htm">UnRAR</a>!'
|
||||
' Processing of CBR/RAR files will be disabled.', 'warning')
|
||||
|
||||
GUI.BasicModeButton.clicked.connect(self.modeBasic)
|
||||
GUI.AdvModeButton.clicked.connect(self.modeAdvanced)
|
||||
@@ -435,6 +616,8 @@ class Ui_KCC(object):
|
||||
GUI.FileButton.clicked.connect(self.selectFile)
|
||||
GUI.ConvertButton.clicked.connect(self.convertStart)
|
||||
GUI.GammaSlider.valueChanged.connect(self.changeGamma)
|
||||
GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
|
||||
GUI.WebtoonBox.stateChanged.connect(self.toggleWebtoonBox)
|
||||
GUI.DeviceBox.activated.connect(self.changeDevice)
|
||||
KCC.connect(self.worker, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar)
|
||||
KCC.connect(self.worker, QtCore.SIGNAL("modeConvert"), self.modeConvert)
|
||||
@@ -467,6 +650,8 @@ class Ui_KCC(object):
|
||||
elif str(option) == "GammaSlider":
|
||||
GUI.GammaSlider.setValue(int(self.options[option]))
|
||||
self.changeGamma(int(self.options[option]))
|
||||
elif str(option) == "StretchBox" or str(option) == "WebstripBox":
|
||||
pass
|
||||
else:
|
||||
eval('GUI.' + str(option)).setCheckState(self.options[option])
|
||||
if self.currentMode == 1:
|
||||
@@ -477,4 +662,5 @@ class Ui_KCC(object):
|
||||
self.modeExpert()
|
||||
self.versionCheck.start()
|
||||
self.hideProgressBar()
|
||||
self.changeDevice(self.lastDevice, True)
|
||||
self.changeDevice(self.lastDevice)
|
||||
self.worker.sync()
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC.ui'
|
||||
#
|
||||
# Created: Fri Jun 21 18:23:19 2013
|
||||
# by: PyQt4 UI code generator 4.10.1
|
||||
# Created: Wed Aug 14 08:39:46 2013
|
||||
# by: PyQt4 UI code generator 4.10.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -47,6 +47,7 @@ class Ui_KCC(object):
|
||||
self.OptionsAdvanced.setFont(font)
|
||||
self.OptionsAdvanced.setObjectName(_fromUtf8("OptionsAdvanced"))
|
||||
self.gridLayout = QtGui.QGridLayout(self.OptionsAdvanced)
|
||||
self.gridLayout.setContentsMargins(9, -1, -1, -1)
|
||||
self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
|
||||
self.ProcessingBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.ProcessingBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
@@ -54,12 +55,13 @@ class Ui_KCC(object):
|
||||
self.gridLayout.addWidget(self.ProcessingBox, 1, 0, 1, 1)
|
||||
self.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.UpscaleBox.setTristate(True)
|
||||
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
|
||||
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
|
||||
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
|
||||
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
|
||||
self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
|
||||
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
|
||||
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox"))
|
||||
@@ -262,10 +264,10 @@ class Ui_KCC(object):
|
||||
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
|
||||
self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", None))
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image upscaling.<br/>Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None))
|
||||
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image stretching.<br/>Aspect ratio will be not preserved.</p></body></html>", None))
|
||||
self.StretchBox.setText(_translate("KCC", "Stretch images", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p>Create PNG files instead JPEG.<br/><span style=\" font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
|
||||
self.BorderBox.setToolTip(_translate("KCC", "Fill space around images with black color.", None))
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC-OSX.ui'
|
||||
#
|
||||
# Created: Fri Jun 21 18:23:35 2013
|
||||
# by: PyQt4 UI code generator 4.10.1
|
||||
# Created: Wed Aug 14 08:39:45 2013
|
||||
# by: PyQt4 UI code generator 4.10.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -60,15 +60,16 @@ class Ui_KCC(object):
|
||||
font.setPointSize(11)
|
||||
self.UpscaleBox.setFont(font)
|
||||
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.UpscaleBox.setTristate(True)
|
||||
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
|
||||
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
|
||||
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
self.StretchBox.setFont(font)
|
||||
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
|
||||
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
|
||||
self.WebtoonBox.setFont(font)
|
||||
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
|
||||
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
|
||||
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
@@ -321,10 +322,10 @@ class Ui_KCC(object):
|
||||
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
|
||||
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Disable image optimizations.</span></p></body></html>", None))
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image upscaling.<br/>Aspect ratio will be preserved.</span></p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None))
|
||||
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image stretching.<br/>Aspect ratio will be not preserved.</span></p></body></html>", None))
|
||||
self.StretchBox.setText(_translate("KCC", "Stretch images", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Create PNG files instead JPEG.<br/></span><span style=\" font-size:12pt; font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
|
||||
self.BorderBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Fill space around images with black color.</span></p></body></html>", None))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '3.0'
|
||||
__version__ = '3.2.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -17,7 +17,7 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '3.0'
|
||||
__version__ = '3.2.1'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -28,17 +28,14 @@ import tempfile
|
||||
import re
|
||||
import stat
|
||||
import string
|
||||
from shutil import move
|
||||
from shutil import copyfile
|
||||
from shutil import copytree
|
||||
from shutil import rmtree
|
||||
from shutil import make_archive
|
||||
from optparse import OptionParser
|
||||
from shutil import move, copyfile, copytree, rmtree, make_archive
|
||||
from optparse import OptionParser, OptionGroup
|
||||
from multiprocessing import Pool, Queue, freeze_support
|
||||
try:
|
||||
from PyQt4 import QtCore
|
||||
except ImportError:
|
||||
QtCore = None
|
||||
import comic2panel
|
||||
import image
|
||||
import cbxarchive
|
||||
import pdfjpgextract
|
||||
@@ -326,16 +323,17 @@ def getImageFileName(imgfile):
|
||||
|
||||
|
||||
def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5):
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers:
|
||||
if not options.webtoon:
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers and not options.webtoon:
|
||||
img.cutPageNumber()
|
||||
img.optimizeImage(options.gamma)
|
||||
if overrideQuality != 5:
|
||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
|
||||
options.landscapemode, overrideQuality)
|
||||
else:
|
||||
img.resizeImage(options.upscale, options.stretch, options.black_borders, isSplit, toRight,
|
||||
options.landscapemode, options.quality)
|
||||
img.optimizeImage(options.gamma)
|
||||
if options.forcepng and not options.forcecolor:
|
||||
img.quantizeImage()
|
||||
|
||||
@@ -361,16 +359,20 @@ def dirImgProcess(path):
|
||||
while not splitpages.ready():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
queue.get(True, 1)
|
||||
queue.get(True, 5)
|
||||
except:
|
||||
pass
|
||||
if not GUI.conversionAlive:
|
||||
pool.terminate()
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise UserWarning("Conversion interrupted.")
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"))
|
||||
pool.join()
|
||||
queue.close()
|
||||
try:
|
||||
splitpages = splitpages.get()
|
||||
except:
|
||||
rmtree(path)
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
|
||||
splitpages = filter(None, splitpages)
|
||||
splitpages.sort()
|
||||
@@ -379,6 +381,9 @@ def dirImgProcess(path):
|
||||
splitCount += 1
|
||||
pagenumbermodifier += 1
|
||||
pagenumbermodifier += 1
|
||||
else:
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise UserWarning("Source directory is empty.")
|
||||
|
||||
|
||||
def fileImgProcess_init(queue, options):
|
||||
@@ -448,7 +453,6 @@ def genEpubStruct(path):
|
||||
chapterlist = []
|
||||
cover = None
|
||||
_, deviceres, _, _, panelviewsize = options.profileData
|
||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
|
||||
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
|
||||
@@ -594,8 +598,9 @@ def genEpubStruct(path):
|
||||
|
||||
|
||||
def getWorkFolder(afile):
|
||||
workdir = tempfile.mkdtemp()
|
||||
if os.path.isdir(afile):
|
||||
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.join(os.path.splitext(afile)[0], '..'))
|
||||
try:
|
||||
os.rmdir(workdir) # needed for copytree() fails if dst already exists
|
||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||
@@ -607,8 +612,13 @@ def getWorkFolder(afile):
|
||||
raise
|
||||
elif afile.lower().endswith('.pdf'):
|
||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
||||
path = pdf.extract()
|
||||
path, njpg = pdf.extract()
|
||||
if njpg == 0:
|
||||
rmtree(path)
|
||||
raise UserWarning("Failed to extract images.")
|
||||
else:
|
||||
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.dirname(afile))
|
||||
cbx = cbxarchive.CBxArchive(afile)
|
||||
if cbx.isCbxFile():
|
||||
try:
|
||||
@@ -647,13 +657,16 @@ def sanitizeTree(filetree):
|
||||
splitname = os.path.splitext(name)
|
||||
slugified = slugify(splitname[0])
|
||||
while os.path.exists(os.path.join(root, slugified + splitname[1])):
|
||||
slugified += "1"
|
||||
slugified += "A"
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugified + splitname[1]))
|
||||
for name in dirs:
|
||||
if name.startswith('.'):
|
||||
os.remove(os.path.join(root, name))
|
||||
else:
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugify(name)))
|
||||
slugified = slugify(name)
|
||||
while os.path.exists(os.path.join(root, slugified)):
|
||||
slugified += "A"
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugified))
|
||||
|
||||
|
||||
def sanitizeTreeBeforeConversion(filetree):
|
||||
@@ -667,6 +680,158 @@ def sanitizeTreeBeforeConversion(filetree):
|
||||
os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
||||
|
||||
|
||||
def getDirectorySize(start_path='.'):
|
||||
total_size = 0
|
||||
for dirpath, dirnames, filenames in os.walk(start_path):
|
||||
for f in filenames:
|
||||
fp = os.path.join(dirpath, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
return total_size
|
||||
|
||||
|
||||
def createNewTome(parentPath):
|
||||
tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-', parentPath)
|
||||
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
|
||||
os.makedirs(tomePath)
|
||||
return tomePath, tomePathRoot
|
||||
|
||||
|
||||
def walkLevel(some_dir, level=1):
|
||||
some_dir = some_dir.rstrip(os.path.sep)
|
||||
assert os.path.isdir(some_dir)
|
||||
num_sep = some_dir.count(os.path.sep)
|
||||
for root, dirs, files in os.walk(some_dir):
|
||||
yield root, dirs, files
|
||||
num_sep_this = root.count(os.path.sep)
|
||||
if num_sep + level <= num_sep_this:
|
||||
del dirs[:]
|
||||
|
||||
|
||||
def splitDirectory(path, mode, parentPath):
|
||||
output = []
|
||||
currentSize = 0
|
||||
currentTarget = path
|
||||
if mode == 0:
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in files:
|
||||
size = os.path.getsize(os.path.join(root, name))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
elif mode == 1:
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in dirs:
|
||||
size = getDirectorySize(os.path.join(root, name))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
elif mode == 2:
|
||||
firstTome = True
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in dirs:
|
||||
size = getDirectorySize(os.path.join(root, name))
|
||||
currentSize = 0
|
||||
if size > 262144000:
|
||||
if not firstTome:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
else:
|
||||
firstTome = False
|
||||
for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0):
|
||||
for nameInside in dirsInside:
|
||||
size = getDirectorySize(os.path.join(rootInside, nameInside))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(rootInside, nameInside), os.path.join(currentTarget, nameInside))
|
||||
else:
|
||||
if not firstTome:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
else:
|
||||
firstTome = False
|
||||
return output
|
||||
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def preSplitDirectory(path):
|
||||
if getDirectorySize(os.path.join(path, 'OEBPS', 'Images')) > 262144000:
|
||||
# Detect directory stucture
|
||||
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0):
|
||||
subdirectoryNumber = len(dirs)
|
||||
filesNumber = len(files)
|
||||
if subdirectoryNumber == 0:
|
||||
# No subdirectories
|
||||
mode = 0
|
||||
else:
|
||||
if filesNumber > 0:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
detectedSubSubdirectories = False
|
||||
detectedFilesInSubdirectories = False
|
||||
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1):
|
||||
if root != os.path.join(path, 'OEBPS', 'Images'):
|
||||
if len(dirs) != 0:
|
||||
detectedSubSubdirectories = True
|
||||
elif len(dirs) == 0 and detectedSubSubdirectories:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
if len(files) != 0:
|
||||
detectedFilesInSubdirectories = True
|
||||
if detectedSubSubdirectories:
|
||||
# Two levels of subdirectories
|
||||
mode = 2
|
||||
else:
|
||||
# One level of subdirectories
|
||||
mode = 1
|
||||
if detectedFilesInSubdirectories and detectedSubSubdirectories:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
# Split directories
|
||||
split = splitDirectory(os.path.join(path, 'OEBPS', 'Images'), mode, os.path.join(path, '..'))
|
||||
path = [path]
|
||||
for tome in split:
|
||||
path.append(tome)
|
||||
return path
|
||||
else:
|
||||
# No splitting is necessary
|
||||
return [path]
|
||||
|
||||
|
||||
def Copyright():
|
||||
print ('comic2ebook v%(__version__)s. '
|
||||
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
|
||||
@@ -679,48 +844,64 @@ def Usage():
|
||||
|
||||
def main(argv=None, qtGUI=None):
|
||||
global parser, options, epub_path, splitCount, GUI
|
||||
usage = "Usage: %prog [options] comic_file|comic_folder"
|
||||
parser = OptionParser(usage=usage, version=__version__)
|
||||
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
|
||||
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8) "
|
||||
"[Default=KHD]")
|
||||
parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename]")
|
||||
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="Manga style (Right-to-left reading and splitting) [Default=False]")
|
||||
parser.add_option("--quality", type="int", dest="quality", default="0",
|
||||
help="Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]")
|
||||
parser.add_option("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
||||
help="Outputs a CBZ archive and does not generate EPUB")
|
||||
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
||||
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
|
||||
parser.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG (For non-Kindle devices) [Default=False]")
|
||||
parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||
parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
||||
help="Resize images smaller than device's resolution [Default=False]")
|
||||
parser.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
||||
help="Stretch images to device's resolution [Default=False]")
|
||||
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders instead of white ones when not stretching and ratio "
|
||||
+ "is not like the device's one [Default=False]")
|
||||
parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
||||
help="Rotate landscape pages instead of splitting them [Default=False]")
|
||||
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
||||
help="Disable splitting and rotation [Default=False]")
|
||||
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Do not try to cut page numbering on images [Default=True]")
|
||||
parser.add_option("-o", "--output", action="store", dest="output", default=None,
|
||||
help="Output generated file (EPUB or CBZ) to specified directory or file")
|
||||
parser.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||
help="Do not convert images to grayscale [Default=False]")
|
||||
parser.add_option("--customwidth", type="int", dest="customwidth", default=0,
|
||||
help="Replace screen width provided by device profile [Default=0]")
|
||||
parser.add_option("--customheight", type="int", dest="customheight", default=0,
|
||||
help="Replace screen height provided by device profile [Default=0]")
|
||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output [Default=False]")
|
||||
parser = OptionParser(usage="Usage: %prog [options] comic_file|comic_folder", add_help_option=False)
|
||||
mainOptions = OptionGroup(parser, "MAIN")
|
||||
experimentalOptions = OptionGroup(parser, "EXPERIMENTAL")
|
||||
processingOptions = OptionGroup(parser, "PROCESSING")
|
||||
outputOptions = OptionGroup(parser, "OUTPUT SETTINGS")
|
||||
customProfileOptions = OptionGroup(parser, "CUSTOM PROFILE")
|
||||
otherOptions = OptionGroup(parser, "OTHER")
|
||||
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
|
||||
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD,"
|
||||
" KFHD8, KFA) [Default=KHD]")
|
||||
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
|
||||
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
|
||||
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="Manga style (Right-to-left reading and splitting)")
|
||||
outputOptions.add_option("-o", "--output", action="store", dest="output", default=None,
|
||||
help="Output generated file to specified directory or file")
|
||||
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename or directory name]")
|
||||
outputOptions.add_option("--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
||||
help="Outputs a CBZ archive and does not generate EPUB")
|
||||
outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
|
||||
help="Split output into multiple files"),
|
||||
experimentalOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
|
||||
help="Webtoon processing mode"),
|
||||
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders instead of white ones")
|
||||
processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||
help="Don't convert images to grayscale")
|
||||
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG (For non-Kindle devices)")
|
||||
processingOptions.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||
processingOptions.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Don't try to cut page numbering on images")
|
||||
processingOptions.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
||||
help="Don't apply image preprocessing")
|
||||
processingOptions.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
||||
help="Disable splitting and rotation")
|
||||
processingOptions.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
||||
help="Rotate landscape pages instead of splitting them")
|
||||
processingOptions.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
||||
help="Stretch images to device's resolution")
|
||||
processingOptions.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
||||
help="Resize images smaller than device's resolution")
|
||||
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
|
||||
help="Replace screen width provided by device profile")
|
||||
customProfileOptions.add_option("--customheight", type="int", dest="customheight", default=0,
|
||||
help="Replace screen height provided by device profile")
|
||||
otherOptions.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output")
|
||||
otherOptions.add_option("-h", "--help", action="help",
|
||||
help="Show this help message and exit")
|
||||
parser.add_option_group(mainOptions)
|
||||
parser.add_option_group(experimentalOptions)
|
||||
parser.add_option_group(outputOptions)
|
||||
parser.add_option_group(processingOptions)
|
||||
parser.add_option_group(customProfileOptions)
|
||||
parser.add_option_group(otherOptions)
|
||||
options, args = parser.parse_args(argv)
|
||||
checkOptions()
|
||||
if qtGUI:
|
||||
@@ -732,33 +913,57 @@ def main(argv=None, qtGUI=None):
|
||||
parser.print_help()
|
||||
return
|
||||
path = getWorkFolder(args[0])
|
||||
if options.title == 'defaulttitle':
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
if options.webtoon:
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Splitting images')
|
||||
if options.customheight > 0:
|
||||
comic2panel.main(['-y ' + str(options.customheight), '-i', path], qtGUI)
|
||||
else:
|
||||
comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI)
|
||||
splitCount = 0
|
||||
if options.imgproc:
|
||||
print "Processing images..."
|
||||
print "\nProcessing images..."
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images')
|
||||
dirImgProcess(path + "/OEBPS/Images/")
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||
if options.cbzoutput:
|
||||
# if CBZ output wanted, compress all images and return filepath
|
||||
print "\nCreating CBZ file..."
|
||||
filepath = getOutputFilename(args[0], options.output, '.cbz')
|
||||
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
|
||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
if options.batchsplit:
|
||||
tomes = preSplitDirectory(path)
|
||||
else:
|
||||
print "\nCreating EPUB structure..."
|
||||
genEpubStruct(path)
|
||||
# actually zip the ePub
|
||||
filepath = getOutputFilename(args[0], options.output, '.epub')
|
||||
make_archive(path + '_comic', 'zip', path)
|
||||
move(path + '_comic.zip', filepath)
|
||||
rmtree(path)
|
||||
tomes = [path]
|
||||
filepath = []
|
||||
tomeNumber = 0
|
||||
for tome in tomes:
|
||||
if len(tomes) > 1:
|
||||
tomeNumber += 1
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0] + ' ' + str(tomeNumber)
|
||||
elif options.title == 'defaulttitle':
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
if options.cbzoutput:
|
||||
# if CBZ output wanted, compress all images and return filepath
|
||||
print "\nCreating CBZ file..."
|
||||
if len(tomes) > 1:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ''))
|
||||
make_archive(tome + '_comic', 'zip', tome + '/OEBPS/Images')
|
||||
else:
|
||||
print "\nCreating EPUB structure..."
|
||||
genEpubStruct(tome)
|
||||
# actually zip the ePub
|
||||
if len(tomes) > 1:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.epub', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.epub', ''))
|
||||
make_archive(tome + '_comic', 'zip', tome)
|
||||
move(tome + '_comic.zip', filepath[-1])
|
||||
rmtree(tome)
|
||||
return filepath
|
||||
|
||||
|
||||
def getOutputFilename(srcpath, wantedname, ext):
|
||||
def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
|
||||
if not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
if wantedname is not None:
|
||||
@@ -767,19 +972,23 @@ def getOutputFilename(srcpath, wantedname, ext):
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
|
||||
else:
|
||||
filename = os.path.abspath(options.output) + "/" \
|
||||
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = srcpath + ext
|
||||
filename = srcpath + tomeNumber + ext
|
||||
else:
|
||||
filename = os.path.splitext(srcpath)[0] + ext
|
||||
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
|
||||
if os.path.isfile(filename):
|
||||
filename = os.path.splitext(filename)[0] + '_kcc' + ext
|
||||
filename = os.path.splitext(filename)[0] + '_kcc' + tomeNumber + ext
|
||||
return filename
|
||||
|
||||
|
||||
def checkOptions():
|
||||
global options
|
||||
# Webtoon mode mandatory options
|
||||
if options.webtoon:
|
||||
options.nosplitrotate = True
|
||||
options.black_borders = False
|
||||
options.quality = 0
|
||||
# Landscape mode is only supported by Kindle Touch and Paperwhite.
|
||||
if options.profile == 'K4T' or options.profile == 'KHD':
|
||||
options.landscapemode = True
|
||||
@@ -816,6 +1025,10 @@ def checkOptions():
|
||||
options.landscapemode = False
|
||||
options.panelview = False
|
||||
options.quality = 0
|
||||
# Kindle for Android profile require target resolution.
|
||||
if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0):
|
||||
print "ERROR: Kindle for Android profile require --customwidth and --customheight options!"
|
||||
sys.exit(1)
|
||||
# Override profile data
|
||||
if options.customwidth != 0 or options.customheight != 0:
|
||||
X = image.ProfileData.Profiles[options.profile][1][0]
|
||||
@@ -831,11 +1044,6 @@ def checkOptions():
|
||||
options.profileData = image.ProfileData.Profiles[options.profile]
|
||||
|
||||
|
||||
def getEpubPath():
|
||||
global epub_path
|
||||
return epub_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
freeze_support()
|
||||
Copyright()
|
||||
|
||||
315
kcc/comic2panel.py
Normal file
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
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
except ImportError:
|
||||
print "ERROR: Pillow is not installed!"
|
||||
@@ -85,6 +85,7 @@ class ProfileData:
|
||||
'KF': ("Kindle Fire", (600, 1024), Palette16, 1.0, (900, 1536)),
|
||||
'KFHD': ("Kindle Fire HD 7\"", (800, 1280), Palette16, 1.0, (1200, 1920)),
|
||||
'KFHD8': ("Kindle Fire HD 8.9\"", (1200, 1920), Palette16, 1.0, (1800, 2880)),
|
||||
'KFA': ("Kindle for Android", (0, 0), Palette16, 1.0, (0, 0)),
|
||||
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
|
||||
}
|
||||
|
||||
@@ -100,6 +101,7 @@ class ProfileData:
|
||||
"Kindle Fire": 'KF',
|
||||
"Kindle Fire HD 7\"": 'KFHD',
|
||||
"Kindle Fire HD 8.9\"": 'KFHD8',
|
||||
"Kindle for Android": 'KFA',
|
||||
"Other": 'OTHER'
|
||||
}
|
||||
|
||||
@@ -113,19 +115,26 @@ class ComicPage:
|
||||
# Detect corrupted files - Phase 2
|
||||
try:
|
||||
self.origFileName = source
|
||||
self.filename = os.path.basename(self.origFileName)
|
||||
self.image = Image.open(source)
|
||||
except IOError:
|
||||
raise RuntimeError('Cannot read image file %s' % source)
|
||||
# Detect corrupted files - Phase 3
|
||||
try:
|
||||
self.image = Image.open(source)
|
||||
self.image.verify()
|
||||
except:
|
||||
raise RuntimeError('Image file %s is corrupted' % source)
|
||||
# Detect corrupted files - Phase 4
|
||||
try:
|
||||
self.image = Image.open(source)
|
||||
self.image.load()
|
||||
except:
|
||||
raise RuntimeError('Image file %s is corrupted' % source)
|
||||
self.image = Image.open(source)
|
||||
self.image = self.image.convert('RGB')
|
||||
|
||||
def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None):
|
||||
filename = os.path.basename(self.origFileName)
|
||||
try:
|
||||
if not color:
|
||||
self.image = self.image.convert('L') # convert to grayscale
|
||||
@@ -134,13 +143,13 @@ class ComicPage:
|
||||
else:
|
||||
suffix = ""
|
||||
if wipe:
|
||||
os.remove(os.path.join(targetdir, filename))
|
||||
os.remove(os.path.join(targetdir, self.filename))
|
||||
else:
|
||||
suffix += "_kcchq"
|
||||
if forcepng:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".png"), "PNG")
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), "PNG")
|
||||
else:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".jpg"), "JPEG")
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".jpg"), "JPEG")
|
||||
except IOError as e:
|
||||
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
|
||||
|
||||
@@ -165,10 +174,15 @@ class ComicPage:
|
||||
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False,
|
||||
landscapeMode=False, qualityMode=0):
|
||||
method = Image.ANTIALIAS
|
||||
if black_borders:
|
||||
if '-KCCFW' in str(self.filename):
|
||||
fill = 'white'
|
||||
elif '-KCCFB' in str(self.filename):
|
||||
fill = 'black'
|
||||
else:
|
||||
fill = 'white'
|
||||
if black_borders:
|
||||
fill = 'black'
|
||||
else:
|
||||
fill = 'white'
|
||||
if qualityMode == 0:
|
||||
size = (self.size[0], self.size[1])
|
||||
else:
|
||||
@@ -225,7 +239,7 @@ class ComicPage:
|
||||
# source is portrait and target is landscape, so split by the height
|
||||
leftbox = (0, 0, width, height / 2)
|
||||
rightbox = (0, height / 2, width, height)
|
||||
filename = os.path.splitext(os.path.basename(self.origFileName))
|
||||
filename = os.path.splitext(self.filename)
|
||||
fileone = targetdir + '/' + filename[0] + '_kcca' + filename[1]
|
||||
filetwo = targetdir + '/' + filename[0] + '_kccb' + filename[1]
|
||||
try:
|
||||
|
||||
@@ -29,7 +29,7 @@ class PdfJpgExtract:
|
||||
def __init__(self, origFileName):
|
||||
self.origFileName = origFileName
|
||||
self.filename = os.path.splitext(origFileName)
|
||||
self.path = self.filename[0]
|
||||
self.path = self.filename[0] + "-KCC-TMP"
|
||||
|
||||
def getPath(self):
|
||||
return self.path
|
||||
@@ -70,4 +70,4 @@ class PdfJpgExtract:
|
||||
|
||||
njpg += 1
|
||||
i = iend
|
||||
return self.path
|
||||
return self.path, njpg
|
||||
|
||||
Reference in New Issue
Block a user