mirror of
https://github.com/ciromattia/kcc
synced 2026-04-16 22:18:51 +00:00
Compare commits
32 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
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>
|
||||||
|
|||||||
13
KCC.ui
13
KCC.ui
@@ -81,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>
|
||||||
|
|||||||
87
README.md
87
README.md
@@ -47,15 +47,16 @@ You can find the latest released binary at the following links:
|
|||||||
* 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:
|
||||||
@@ -64,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, KFA) [Default=KHD]
|
||||||
-t TITLE, --title=TITLE
|
-q QUALITY, --quality=QUALITY
|
||||||
Comic title [Default=filename]
|
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||||
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
|
-m, --manga-style Manga style (Right-to-left reading and splitting)
|
||||||
--quality=QUALITY Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
|
||||||
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
|
EXPERIMENTAL:
|
||||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
-w, --webtoon Webtoon processing mode
|
||||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
|
|
||||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
OUTPUT SETTINGS:
|
||||||
--upscale Resize images smaller than device's resolution [Default=False]
|
-o OUTPUT, --output=OUTPUT
|
||||||
--stretch Stretch images to device's resolution [Default=False]
|
Output generated file to specified directory or file
|
||||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
-t TITLE, --title=TITLE
|
||||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
Comic title [Default=filename or directory name]
|
||||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
--cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
--batchsplit Split output into multiple files
|
||||||
-o OUTPUT, --output=OUTPUT
|
|
||||||
Output generated file (EPUB or CBZ) to specified directory or file
|
PROCESSING:
|
||||||
--forcecolor Do not convert images to grayscale [Default=False]
|
--blackborders Use black borders instead of white ones
|
||||||
--customwidth=WIDTH Replace screen width provided by device profile [Default=0]
|
--forcecolor Don't convert images to grayscale
|
||||||
--customheight=HEIGHT Replace screen height provided by device profile [Default=0]
|
--forcepng Create PNG files instead JPEG (For non-Kindle devices)
|
||||||
-v, --verbose Verbose output [Default=False]
|
--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
|
## CREDITS
|
||||||
@@ -215,6 +247,11 @@ The app relies and includes the following scripts/binaries:
|
|||||||
* Add file/directory dialogs now support multiselect
|
* Add file/directory dialogs now support multiselect
|
||||||
* Many small fixes and tweaks
|
* Many small fixes and tweaks
|
||||||
|
|
||||||
|
####3.2:
|
||||||
|
* Too big EPUB files are now splitted before conversion to MOBI
|
||||||
|
* Added experimental parser of manga webtoons
|
||||||
|
* Improved error handling
|
||||||
|
|
||||||
## KNOWN ISSUES
|
## KNOWN ISSUES
|
||||||
* Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations.
|
* Removing SRCS headers sometimes fail in 32bit enviroments. Due to memory limitations.
|
||||||
|
|
||||||
|
|||||||
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.1'
|
__version__ = '3.2'
|
||||||
__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'
|
||||||
|
|||||||
186
kcc/KCC_gui.py
186
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.1'
|
__version__ = '3.2'
|
||||||
__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'
|
||||||
@@ -111,6 +111,7 @@ class WorkerThread(QtCore.QThread):
|
|||||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>Conversion interrupted.</b>', 'error')
|
self.emit(QtCore.SIGNAL("addMessage"), '<b>Conversion interrupted.</b>', 'error')
|
||||||
self.emit(QtCore.SIGNAL("modeConvert"), True)
|
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())]
|
||||||
@@ -127,27 +128,32 @@ 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():
|
|
||||||
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)
|
time.sleep(0.5)
|
||||||
@@ -180,7 +186,9 @@ class WorkerThread(QtCore.QThread):
|
|||||||
% (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:
|
if not self.conversionAlive:
|
||||||
os.remove(outputPath)
|
for item in outputPath:
|
||||||
|
if os.path.exists(item):
|
||||||
|
os.remove(item)
|
||||||
self.clean()
|
self.clean()
|
||||||
return
|
return
|
||||||
if not self.errors:
|
if not self.errors:
|
||||||
@@ -189,64 +197,81 @@ class WorkerThread(QtCore.QThread):
|
|||||||
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':
|
||||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
tomeNumber = 0
|
||||||
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
for item in outputPath:
|
||||||
try:
|
tomeNumber += 1
|
||||||
self.kindlegenErrorCode = 0
|
if len(outputPath) > 1:
|
||||||
if os.path.getsize(outputPath) < 367001600:
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber)
|
||||||
output = Popen('kindlegen "' + outputPath + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
+ '/' + str(len(outputPath)) + ')...', 'info')
|
||||||
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
|
|
||||||
else:
|
else:
|
||||||
# ERROR: EPUB too big
|
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
||||||
self.kindlegenErrorCode = 23026
|
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||||
except:
|
|
||||||
# ERROR: Unknown generic error
|
|
||||||
self.kindlegenErrorCode = 1
|
|
||||||
continue
|
|
||||||
if not self.conversionAlive:
|
|
||||||
os.remove(outputPath)
|
|
||||||
os.remove(outputPath.replace('.epub', '.mobi'))
|
|
||||||
self.clean()
|
|
||||||
return
|
|
||||||
if self.kindlegenErrorCode == 0:
|
|
||||||
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')
|
|
||||||
shutil.move(mobiPath, mobiPath + '_tostrip')
|
|
||||||
try:
|
try:
|
||||||
kindlestrip.main((mobiPath + '_tostrip', mobiPath))
|
self.kindlegenErrorCode = 0
|
||||||
except Exception:
|
if os.path.getsize(item) < 367001600:
|
||||||
self.errors = True
|
output = Popen('kindlegen "' + item + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||||
if not self.errors:
|
for line in output.stdout:
|
||||||
os.remove(mobiPath + '_tostrip')
|
# ERROR: Generic error
|
||||||
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header... Done!', 'info', True)
|
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 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(item)
|
||||||
|
mobiPath = item.replace('.epub', '.mobi')
|
||||||
|
shutil.move(mobiPath, mobiPath + '_tostrip')
|
||||||
|
try:
|
||||||
|
kindlestrip.main((mobiPath + '_tostrip', mobiPath))
|
||||||
|
except Exception:
|
||||||
|
self.errors = True
|
||||||
|
if not self.errors:
|
||||||
|
os.remove(mobiPath + '_tostrip')
|
||||||
|
self.emit(QtCore.SIGNAL("addMessage"), 'Removing SRCS header... Done!', 'info', True)
|
||||||
|
else:
|
||||||
|
shutil.move(mobiPath + '_tostrip', mobiPath)
|
||||||
|
self.emit(QtCore.SIGNAL("addMessage"),
|
||||||
|
'KindleStrip failed to remove SRCS header!', 'warning')
|
||||||
|
self.emit(QtCore.SIGNAL("addMessage"),
|
||||||
|
'MOBI file will work correctly but it will be highly oversized.', 'warning')
|
||||||
else:
|
else:
|
||||||
shutil.move(mobiPath + '_tostrip', mobiPath)
|
epubSize = (os.path.getsize(item))/1024/1024
|
||||||
self.emit(QtCore.SIGNAL("addMessage"),
|
os.remove(item)
|
||||||
'KindleStrip failed to remove SRCS header!', 'warning')
|
if os.path.exists(item.replace('.epub', '.mobi')):
|
||||||
self.emit(QtCore.SIGNAL("addMessage"),
|
os.remove(item.replace('.epub', '.mobi'))
|
||||||
'MOBI file will work correctly but it will be highly oversized.', 'warning')
|
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
||||||
else:
|
if self.kindlegenErrorCode == 1 and self.kindlegenError:
|
||||||
epubSize = (os.path.getsize(outputPath))/1024/1024
|
self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
|
||||||
os.remove(outputPath)
|
if self.kindlegenErrorCode == 23026:
|
||||||
if os.path.exists(outputPath.replace('.epub', '.mobi')):
|
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
|
||||||
os.remove(outputPath.replace('.epub', '.mobi'))
|
'error')
|
||||||
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
|
||||||
if self.kindlegenErrorCode == 1 and self.kindlegenError:
|
' Supported size: ~300MB.', 'error')
|
||||||
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.emit(QtCore.SIGNAL("hideProgressBar"))
|
||||||
self.parent.needClean = True
|
self.parent.needClean = True
|
||||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>All jobs completed.</b>', 'info')
|
self.emit(QtCore.SIGNAL("addMessage"), '<b>All jobs completed.</b>', 'info')
|
||||||
@@ -407,6 +432,20 @@ class Ui_KCC(object):
|
|||||||
GUI.GammaLabel.setText('Gamma: ' + str(value))
|
GUI.GammaLabel.setText('Gamma: ' + str(value))
|
||||||
self.GammaValue = value
|
self.GammaValue = value
|
||||||
|
|
||||||
|
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):
|
def toggleNoSplitRotate(self, value):
|
||||||
if value:
|
if value:
|
||||||
GUI.RotateBox.setEnabled(False)
|
GUI.RotateBox.setEnabled(False)
|
||||||
@@ -414,14 +453,6 @@ class Ui_KCC(object):
|
|||||||
else:
|
else:
|
||||||
GUI.RotateBox.setEnabled(True)
|
GUI.RotateBox.setEnabled(True)
|
||||||
|
|
||||||
def toggleUpscale(self, value):
|
|
||||||
if value:
|
|
||||||
GUI.StretchBox.setChecked(False)
|
|
||||||
|
|
||||||
def toggleStretch(self, value):
|
|
||||||
if value:
|
|
||||||
GUI.UpscaleBox.setChecked(False)
|
|
||||||
|
|
||||||
def changeDevice(self, value):
|
def changeDevice(self, value):
|
||||||
if value == 12:
|
if value == 12:
|
||||||
GUI.BasicModeButton.setEnabled(False)
|
GUI.BasicModeButton.setEnabled(False)
|
||||||
@@ -472,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)
|
||||||
@@ -505,8 +535,15 @@ class Ui_KCC(object):
|
|||||||
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())
|
||||||
@@ -518,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(),
|
||||||
@@ -580,8 +617,7 @@ class Ui_KCC(object):
|
|||||||
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.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
|
||||||
GUI.UpscaleBox.stateChanged.connect(self.toggleUpscale)
|
GUI.WebtoonBox.stateChanged.connect(self.toggleWebtoonBox)
|
||||||
GUI.StretchBox.stateChanged.connect(self.toggleStretch)
|
|
||||||
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)
|
||||||
@@ -614,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:
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
# Form implementation generated from reading ui file 'KCC.ui'
|
# Form implementation generated from reading ui file 'KCC.ui'
|
||||||
#
|
#
|
||||||
# Created: Thu Jul 04 15:30:15 2013
|
# Created: Wed Aug 14 08:39:46 2013
|
||||||
# by: PyQt4 UI code generator 4.10.2
|
# 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!
|
||||||
@@ -55,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"))
|
||||||
@@ -263,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.1'
|
__version__ = '3.2'
|
||||||
__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.1'
|
__version__ = '3.2'
|
||||||
__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,8 +323,9 @@ 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)
|
img.optimizeImage(options.gamma)
|
||||||
if overrideQuality != 5:
|
if overrideQuality != 5:
|
||||||
@@ -361,12 +359,12 @@ 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:
|
if not GUI.conversionAlive:
|
||||||
pool.terminate()
|
pool.terminate()
|
||||||
rmtree(path)
|
rmtree(os.path.join(path, '..', '..'))
|
||||||
raise UserWarning("Conversion interrupted.")
|
raise UserWarning("Conversion interrupted.")
|
||||||
GUI.emit(QtCore.SIGNAL("progressBarTick"))
|
GUI.emit(QtCore.SIGNAL("progressBarTick"))
|
||||||
pool.join()
|
pool.join()
|
||||||
@@ -374,7 +372,7 @@ def dirImgProcess(path):
|
|||||||
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()
|
||||||
@@ -384,7 +382,7 @@ def dirImgProcess(path):
|
|||||||
pagenumbermodifier += 1
|
pagenumbermodifier += 1
|
||||||
pagenumbermodifier += 1
|
pagenumbermodifier += 1
|
||||||
else:
|
else:
|
||||||
rmtree(path)
|
rmtree(os.path.join(path, '..', '..'))
|
||||||
raise UserWarning("Source directory is empty.")
|
raise UserWarning("Source directory is empty.")
|
||||||
|
|
||||||
|
|
||||||
@@ -455,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.
|
||||||
@@ -601,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')
|
||||||
@@ -614,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:
|
||||||
@@ -654,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):
|
||||||
@@ -674,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())
|
||||||
@@ -686,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")
|
||||||
" KFA) [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:
|
||||||
@@ -739,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:
|
||||||
@@ -774,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
|
||||||
@@ -842,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'
|
||||||
|
__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)
|
||||||
28
kcc/image.py
28
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!"
|
||||||
@@ -115,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
|
||||||
@@ -136,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))
|
||||||
|
|
||||||
@@ -167,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:
|
||||||
@@ -227,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