1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-16 22:18:51 +00:00

Compare commits

...

32 Commits
3.1 ... 3.2

Author SHA1 Message Date
Paweł Jastrzębski
27bd6f96e7 Webtoon mode improvements 2013-08-19 16:46:27 +02:00
Paweł Jastrzębski
a98fac2e95 Webtoon mode improvements 2013-08-17 16:03:36 +02:00
Paweł Jastrzębski
f645b65a9e Added volume auto-split 2013-08-17 12:01:19 +02:00
Paweł Jastrzębski
6eaf8cc374 comic2panel: Detection of corrupted files 2013-08-16 19:25:26 +02:00
Paweł Jastrzębski
61c0b691ab Webtoon mode improvements 2013-08-16 17:10:18 +02:00
Paweł Jastrzębski
394cefb2de Sanitize job input 2013-08-16 17:09:49 +02:00
Paweł Jastrzębski
30d6a55e3c Webtoon mode improvements 2013-08-16 13:28:27 +02:00
Paweł Jastrzębski
e32018e8f6 Improved cleanup (close #58) 2013-08-15 10:05:03 +02:00
Paweł Jastrzębski
6b002a8475 Webtoon mode improvements 2013-08-15 09:48:50 +02:00
Paweł Jastrzębski
97e23c8f50 Web...toon mode improvements 2013-08-14 09:30:31 +02:00
Paweł Jastrzębski
e558ffd807 Temporary disabling new location of tmp files 2013-08-13 13:33:16 +02:00
Paweł Jastrzębski
71d158ca45 Fixed tmp directories - Need more coffee. 2013-08-13 10:56:49 +02:00
Paweł Jastrzębski
5f8e5e0be9 Fixed tmp directories 2013-08-13 10:48:42 +02:00
Paweł Jastrzębski
ff91eb1407 Webstrip mode improvements 2013-08-13 10:30:53 +02:00
Paweł Jastrzębski
877a859ef4 Fixed abort bug 2013-08-13 09:33:46 +02:00
Paweł Jastrzębski
f8b29cd967 Webstrip mode improvements 2013-08-13 09:25:53 +02:00
Paweł Jastrzębski
3d2554c557 Fixed tmp directories 2013-08-12 16:32:40 +02:00
Paweł Jastrzębski
b7d7204d40 Updated README and version bump 2013-08-12 15:59:32 +02:00
Paweł Jastrzębski
876d26d174 GUI: Added webstrip support 2013-08-12 14:01:57 +02:00
Paweł Jastrzębski
3ccb1a63aa Moved location of temp files 2013-08-12 13:03:03 +02:00
Paweł Jastrzębski
c8bb9b4f5f comic2panel: GUI support and itegration with comic2ebook 2013-08-12 12:59:58 +02:00
Paweł Jastrzębski
723be29118 Updated README 2013-08-11 14:56:26 +02:00
Paweł Jastrzębski
2865915cdf comic2panel: Autodetect border color 2013-08-11 14:47:06 +02:00
Paweł Jastrzębski
d3e0c2bb6e Improved detection of corrupted files 2013-08-11 12:58:34 +02:00
Paweł Jastrzębski
5e7ae73861 Updated README 2013-08-09 15:55:39 +02:00
Paweł Jastrzębski
61206b2169 Help cleanup 2013-08-09 15:25:00 +02:00
Paweł Jastrzębski
92e2a8913b No size limit anymore. KCC now split EPUB files before handling them to KindleGen. 2013-08-08 20:21:05 +02:00
Paweł Jastrzębski
ad827828d7 comic2panel: Fixed processing of last panel 2013-08-05 15:47:56 +02:00
Paweł Jastrzębski
faf16084a3 comic2panel: Multiprocessing support 2013-08-05 15:40:28 +02:00
Paweł Jastrzębski
38d2b55456 Added higly experimental parser of webstrips 2013-08-03 16:17:26 +02:00
Paweł Jastrzębski
abcebc54e8 Updated README 2013-08-03 10:23:45 +02:00
Paweł Jastrzębski
40cb963c99 Improved handling of slugification conflicts 2013-08-02 10:52:51 +02:00
14 changed files with 837 additions and 229 deletions

1
.gitignore vendored
View File

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

View File

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

13
KCC.ui
View File

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

View File

@@ -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
View File

@@ -16,8 +16,8 @@
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER # OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
#
__version__ = '3.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'

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
__version__ = '3.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:

View File

@@ -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))

View File

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

View File

@@ -1,4 +1,4 @@
__version__ = '3.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'

View File

@@ -17,7 +17,7 @@
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
__version__ = '3.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
View File

@@ -0,0 +1,315 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
#
# Permission to use, copy, modify, and/or distribute this software for
# any purpose with or without fee is hereby granted, provided that the
# above copyright notice and this permission notice appear in all
# copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
#
__version__ = '3.2'
__license__ = 'ISC'
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
__docformat__ = 'restructuredtext en'
import os
import sys
from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool, Queue, freeze_support
try:
# noinspection PyUnresolvedReferences,PyPackageRequirements
from PIL import Image, ImageStat
except ImportError:
print "ERROR: Pillow is not installed!"
exit(1)
try:
from PyQt4 import QtCore
except ImportError:
QtCore = None
def getImageFileName(imgfile):
filename = os.path.splitext(imgfile)
if filename[0].startswith('.') or\
(filename[1].lower() != '.png' and
filename[1].lower() != '.jpg' and
filename[1].lower() != '.gif' and
filename[1].lower() != '.tif' and
filename[1].lower() != '.tiff' and
filename[1].lower() != '.bmp' and
filename[1].lower() != '.jpeg'):
return None
return filename
def getImageHistogram(image):
histogram = image.histogram()
RBGW = []
for i in range(256):
RBGW.append(histogram[i] + histogram[256 + i] + histogram[512 + i])
white = 0
black = 0
for i in range(245, 256):
white += RBGW[i]
for i in range(11):
black += RBGW[i]
if white > black:
return False
else:
return True
def getImageFill(image):
imageSize = image.size
imageT = image.crop((0, 0, imageSize[0], 1))
imageB = image.crop((0, imageSize[1]-1, imageSize[0], imageSize[1]))
fill = 0
fill += getImageHistogram(imageT)
fill += getImageHistogram(imageB)
if fill == 2:
return 'KCCFB'
elif fill == 0:
return 'KCCFW'
else:
imageL = image.crop((0, 0, 1, imageSize[1]))
imageR = image.crop((imageSize[0]-1, 0, imageSize[0], imageSize[1]))
fill += getImageHistogram(imageL)
fill += getImageHistogram(imageR)
if fill >= 2:
return 'KCCFB'
else:
return 'KCCFW'
def sanitizePanelSize(panel, options):
newPanels = []
if panel[2] > 8 * options.height:
diff = (panel[2] / 8)
newPanels.append([panel[0], panel[1] - diff*7, diff])
newPanels.append([panel[1] - diff*7, panel[1] - diff*6, diff])
newPanels.append([panel[1] - diff*6, panel[1] - diff*5, diff])
newPanels.append([panel[1] - diff*5, panel[1] - diff*4, diff])
newPanels.append([panel[1] - diff*4, panel[1] - diff*3, diff])
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 4 * options.height:
diff = (panel[2] / 4)
newPanels.append([panel[0], panel[1] - diff*3, diff])
newPanels.append([panel[1] - diff*3, panel[1] - diff*2, diff])
newPanels.append([panel[1] - diff*2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 2 * options.height:
newPanels.append([panel[0], panel[1] - (panel[2] / 2), (panel[2] / 2)])
newPanels.append([panel[1] - (panel[2] / 2), panel[1], (panel[2] / 2)])
else:
newPanels = [panel]
return newPanels
def splitImage_init(queue, options):
splitImage.queue = queue
splitImage.options = options
# noinspection PyUnresolvedReferences
def splitImage(work):
path = work[0]
name = work[1]
options = splitImage.options
# Harcoded options
threshold = 1.0
delta = 15
print ".",
splitImage.queue.put(".")
fileExpanded = os.path.splitext(name)
filePath = os.path.join(path, name)
# Detect corrupted files
try:
image = Image.open(filePath)
except IOError:
raise RuntimeError('Cannot read image file %s' % filePath)
try:
image = Image.open(filePath)
image.verify()
except:
raise RuntimeError('Image file %s is corrupted' % filePath)
try:
image = Image.open(filePath)
image.load()
except:
raise RuntimeError('Image file %s is corrupted' % filePath)
image = Image.open(filePath)
image = image.convert('RGB')
widthImg, heightImg = image.size
if heightImg > options.height:
if options.debug:
from PIL import ImageDraw
debugImage = Image.open(filePath)
draw = ImageDraw.Draw(debugImage)
# Find panels
y1 = 0
y2 = 15
panels = []
while y2 < heightImg:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg:
y2 += delta
y2 -= delta
y1Temp = y2
y1 = y2 + delta
y2 = y1 + delta
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg:
y1 += delta
y2 += delta
if y1 + delta >= heightImg:
y1 = heightImg - 1
y2Temp = y1
if options.debug:
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0))
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0))
panelHeight = y2Temp - y1Temp
if panelHeight > delta:
# Panels that can't be cut nicely will be forcefully splitted
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], options)
for panel in panelsCleaned:
panels.append(panel)
if options.debug:
# noinspection PyUnboundLocalVariable
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG')
# Create virtual pages
pages = []
currentPage = []
pageLeft = options.height
panelNumber = 0
for panel in panels:
if pageLeft - panel[2] > 0:
pageLeft -= panel[2]
currentPage.append(panelNumber)
panelNumber += 1
else:
if len(currentPage) > 0:
pages.append(currentPage)
pageLeft = options.height - panel[2]
currentPage = [panelNumber]
panelNumber += 1
if len(currentPage) > 0:
pages.append(currentPage)
# Create pages
pageNumber = 1
for page in pages:
pageHeight = 0
targetHeight = 0
for panel in page:
pageHeight += panels[panel][2]
if pageHeight > delta:
newPage = Image.new('RGB', (widthImg, pageHeight))
for panel in page:
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]])
newPage.paste(panelImg, (0, targetHeight))
targetHeight += panels[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' +
str(pageNumber) + '-' + getImageFill(newPage) + '.png'), 'PNG')
pageNumber += 1
os.remove(filePath)
def Copyright():
print ('comic2panel v%(__version__)s. '
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
# noinspection PyBroadException
def main(argv=None, qtGUI=None):
global options
parser = OptionParser(usage="Usage: %prog [options] comic_folder", add_help_option=False)
mainOptions = OptionGroup(parser, "MANDATORY")
otherOptions = OptionGroup(parser, "OTHER")
mainOptions.add_option("-y", "--height", type="int", dest="height", default=0,
help="Height of the target device screen")
mainOptions.add_option("-i", "--in-place", action="store_true", dest="inPlace", default=False,
help="Overwrite source directory")
otherOptions.add_option("-d", "--debug", action="store_true", dest="debug", default=False,
help="Create debug file for every splitted image")
otherOptions.add_option("-h", "--help", action="help",
help="Show this help message and exit")
parser.add_option_group(mainOptions)
parser.add_option_group(otherOptions)
options, args = parser.parse_args(argv)
if qtGUI:
GUI = qtGUI
else:
GUI = None
if len(args) != 1:
parser.print_help()
return
if options.height > 0:
options.sourceDir = args[0]
options.targetDir = args[0] + "-Splitted"
print "\nSplitting images..."
if os.path.isdir(options.sourceDir):
rmtree(options.targetDir, True)
copytree(options.sourceDir, options.targetDir)
work = []
pagenumber = 0
queue = Queue()
pool = Pool(None, splitImage_init, [queue, options])
for root, dirs, files in os.walk(options.targetDir, False):
for name in files:
if getImageFileName(name) is not None:
pagenumber += 1
work.append([root, name])
else:
os.remove(os.path.join(root, name))
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), pagenumber)
if len(work) > 0:
workers = pool.map_async(func=splitImage, iterable=work)
pool.close()
if GUI:
while not workers.ready():
# noinspection PyBroadException
try:
queue.get(True, 5)
except:
pass
GUI.emit(QtCore.SIGNAL("progressBarTick"))
pool.join()
queue.close()
try:
workers.get()
except:
rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
if GUI:
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
if options.inPlace:
rmtree(options.sourceDir, True)
move(options.targetDir, options.sourceDir)
else:
rmtree(options.targetDir)
raise UserWarning("Source directory is empty.")
else:
raise UserWarning("Provided path is not a directory.")
else:
raise UserWarning("Target height is not set.")
if __name__ == "__main__":
freeze_support()
Copyright()
main(sys.argv[1:])
sys.exit(0)

View File

@@ -21,7 +21,7 @@ __docformat__ = 'restructuredtext en'
import os 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:

View File

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

View File

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