mirror of
https://github.com/ciromattia/kcc
synced 2026-04-16 14:08:45 +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*
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
UnRAR.exe
|
||||
|
||||
13
KCC-OSX.ui
13
KCC-OSX.ui
@@ -88,15 +88,18 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-size:12pt;">Enable image upscaling.<br/>Aspect ratio will be preserved.</span></p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Upscale images</string>
|
||||
<string>Stretch/Upscale</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="StretchBox">
|
||||
<widget class="QCheckBox" name="WebtoonBox">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>11</pointsize>
|
||||
@@ -106,10 +109,10 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p><span style=" font-size:12pt;">Enable image stretching.<br/>Aspect ratio will be not preserved.</span></p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stretch images</string>
|
||||
<string>Webtoon mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
13
KCC.ui
13
KCC.ui
@@ -81,23 +81,26 @@
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable image upscaling.<br/>Aspect ratio will be preserved.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600; text-decoration: underline;">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=" font-weight:600; text-decoration: underline;">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=" font-weight:600; text-decoration: underline;">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Upscale images</string>
|
||||
<string>Stretch/Upscale</string>
|
||||
</property>
|
||||
<property name="tristate">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="StretchBox">
|
||||
<widget class="QCheckBox" name="WebtoonBox">
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Enable image stretching.<br/>Aspect ratio will be not preserved.</p></body></html></string>
|
||||
<string><html><head/><body><p><span style=" font-weight:600;">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=" font-style:italic;">Tower of God</span> or <span style=" font-style:italic;">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html></string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Stretch images</string>
|
||||
<string>Webtoon mode</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
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.**
|
||||
* Read tooltip of _High/Ultra quality_ option. There are many important informations there.
|
||||
* When converting images smaller than device resolution remember to enable upscaling.
|
||||
* Panel View (auto zooming every part of page) can be disabled directly on Kindle. There is no KCC option to do that.
|
||||
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
|
||||
* Check our [wiki](https://github.com/ciromattia/kcc/wiki/Other-devices) for a list of tested Non-Kindle E-Readers.
|
||||
* The first image found will be set as the comic's cover.
|
||||
* All files/directories will be added to EPUB in alphabetical order.
|
||||
* Output MOBI file should be uploaded via USB. Other methods (e.g. via Calibre) might corrupt it.
|
||||
* If you're converting color images and the end result is not satisfactory, experiment with gamma correction option (check 1.0 setting first).
|
||||
|
||||
### GUI
|
||||
|
||||
Should be pretty self-explanatory.
|
||||
Should be pretty self-explanatory. All options have detailed informations in tooltips.
|
||||
After completed conversion you should find ready file alongside the original input file (same directory).
|
||||
|
||||
### Standalone `comic2ebook.py` usage:
|
||||
@@ -64,30 +65,61 @@ After completed conversion you should find ready file alongside the original inp
|
||||
Usage: comic2ebook.py [options] comic_file|comic_folder
|
||||
|
||||
Options:
|
||||
--version show program's version number and exit
|
||||
-h, --help show this help message and exit
|
||||
-p PROFILE, --profile=PROFILE
|
||||
MAIN:
|
||||
-p PROFILE, --profile=PROFILE
|
||||
Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8, KFA) [Default=KHD]
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting) [Default=False]
|
||||
--quality=QUALITY Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||
-c, --cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
--noprocessing Do not apply image preprocessing (Page splitting and optimizations) [Default=True]
|
||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices) [Default=False]
|
||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
||||
--upscale Resize images smaller than device's resolution [Default=False]
|
||||
--stretch Stretch images to device's resolution [Default=False]
|
||||
--blackborders Use black borders instead of white ones when not stretching and ratio is not like the device's one [Default=False]
|
||||
--rotate Rotate landscape pages instead of splitting them [Default=False]
|
||||
--nosplitrotate Disable splitting and rotation [Default=False]
|
||||
--nocutpagenumbers Do not try to cut page numbering on images [Default=True]
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output generated file (EPUB or CBZ) to specified directory or file
|
||||
--forcecolor Do not convert images to grayscale [Default=False]
|
||||
--customwidth=WIDTH Replace screen width provided by device profile [Default=0]
|
||||
--customheight=HEIGHT Replace screen height provided by device profile [Default=0]
|
||||
-v, --verbose Verbose output [Default=False]
|
||||
-q QUALITY, --quality=QUALITY
|
||||
Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]
|
||||
-m, --manga-style Manga style (Right-to-left reading and splitting)
|
||||
|
||||
EXPERIMENTAL:
|
||||
-w, --webtoon Webtoon processing mode
|
||||
|
||||
OUTPUT SETTINGS:
|
||||
-o OUTPUT, --output=OUTPUT
|
||||
Output generated file to specified directory or file
|
||||
-t TITLE, --title=TITLE
|
||||
Comic title [Default=filename or directory name]
|
||||
--cbz-output Outputs a CBZ archive and does not generate EPUB
|
||||
--batchsplit Split output into multiple files
|
||||
|
||||
PROCESSING:
|
||||
--blackborders Use black borders instead of white ones
|
||||
--forcecolor Don't convert images to grayscale
|
||||
--forcepng Create PNG files instead JPEG (For non-Kindle devices)
|
||||
--gamma=GAMMA Apply gamma correction to linearize the image [Default=Auto]
|
||||
--nocutpagenumbers Don't try to cut page numbering on images
|
||||
--noprocessing Don't apply image preprocessing
|
||||
--nosplitrotate Disable splitting and rotation
|
||||
--rotate Rotate landscape pages instead of splitting them
|
||||
--stretch Stretch images to device's resolution
|
||||
--upscale Resize images smaller than device's resolution
|
||||
|
||||
CUSTOM PROFILE:
|
||||
--customwidth=CUSTOMWIDTH
|
||||
Replace screen width provided by device profile
|
||||
--customheight=CUSTOMHEIGHT
|
||||
Replace screen height provided by device profile
|
||||
|
||||
OTHER:
|
||||
-v, --verbose Verbose output
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
### Standalone `comic2panel.py` usage:
|
||||
|
||||
```
|
||||
Usage: comic2panel.py [options] comic_folder
|
||||
|
||||
Options:
|
||||
MANDATORY:
|
||||
-y HEIGHT, --height=HEIGHT
|
||||
Height of the target device screen
|
||||
-i, --in-place Overwrite source directory
|
||||
|
||||
OTHER:
|
||||
-d, --debug Create debug file for every splitted image
|
||||
-h, --help Show this help message and exit
|
||||
```
|
||||
|
||||
## CREDITS
|
||||
@@ -215,6 +247,11 @@ The app relies and includes the following scripts/binaries:
|
||||
* Add file/directory dialogs now support multiselect
|
||||
* Many small fixes and tweaks
|
||||
|
||||
####3.2:
|
||||
* Too big EPUB files are now splitted before conversion to MOBI
|
||||
* Added experimental parser of manga webtoons
|
||||
* Improved error handling
|
||||
|
||||
## KNOWN ISSUES
|
||||
* 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
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '3.1'
|
||||
|
||||
__version__ = '3.2'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__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
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.2'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__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("modeConvert"), True)
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def run(self):
|
||||
self.emit(QtCore.SIGNAL("modeConvert"), False)
|
||||
profile = ProfileData.ProfileLabels[str(GUI.DeviceBox.currentText())]
|
||||
@@ -127,27 +128,32 @@ class WorkerThread(QtCore.QThread):
|
||||
if self.parent.currentMode > 1:
|
||||
if GUI.ProcessingBox.isChecked():
|
||||
argv.append("--noprocessing")
|
||||
if GUI.UpscaleBox.isChecked():
|
||||
argv.append("--upscale")
|
||||
if GUI.NoRotateBox.isChecked():
|
||||
argv.append("--nosplitrotate")
|
||||
if GUI.BorderBox.isChecked():
|
||||
argv.append("--blackborders")
|
||||
if GUI.StretchBox.isChecked():
|
||||
if GUI.UpscaleBox.checkState() == 1:
|
||||
argv.append("--stretch")
|
||||
elif GUI.UpscaleBox.checkState() == 2:
|
||||
argv.append("--upscale")
|
||||
if GUI.NoDitheringBox.isChecked():
|
||||
argv.append("--forcepng")
|
||||
if GUI.WebtoonBox.isChecked():
|
||||
argv.append("--webtoon")
|
||||
if float(self.parent.GammaValue) > 0.09:
|
||||
argv.append("--gamma=" + self.parent.GammaValue)
|
||||
if str(GUI.FormatBox.currentText()) == 'CBZ':
|
||||
argv.append("--cbz-output")
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
argv.append("--batchsplit")
|
||||
if self.parent.currentMode > 2:
|
||||
argv.append("--customwidth=" + str(GUI.customWidth.text()))
|
||||
argv.append("--customheight=" + str(GUI.customHeight.text()))
|
||||
if GUI.ColorBox.isChecked():
|
||||
argv.append("--forcecolor")
|
||||
for i in range(GUI.JobList.count()):
|
||||
currentJobs.append(str(GUI.JobList.item(i).text()))
|
||||
if GUI.JobList.item(i).icon().isNull():
|
||||
currentJobs.append(str(GUI.JobList.item(i).text()))
|
||||
GUI.JobList.clear()
|
||||
for job in currentJobs:
|
||||
time.sleep(0.5)
|
||||
@@ -180,7 +186,9 @@ class WorkerThread(QtCore.QThread):
|
||||
% (jobargv[-1], str(err), traceback.format_tb(traceback_)))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KCC failed to create EPUB!', 'error')
|
||||
if not self.conversionAlive:
|
||||
os.remove(outputPath)
|
||||
for item in outputPath:
|
||||
if os.path.exists(item):
|
||||
os.remove(item)
|
||||
self.clean()
|
||||
return
|
||||
if not self.errors:
|
||||
@@ -189,64 +197,81 @@ class WorkerThread(QtCore.QThread):
|
||||
else:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating EPUB file... Done!', 'info', True)
|
||||
if str(GUI.FormatBox.currentText()) == 'MOBI':
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
||||
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||
try:
|
||||
self.kindlegenErrorCode = 0
|
||||
if os.path.getsize(outputPath) < 367001600:
|
||||
output = Popen('kindlegen "' + outputPath + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
for line in output.stdout:
|
||||
# ERROR: Generic error
|
||||
if "Error(" in line:
|
||||
self.kindlegenErrorCode = 1
|
||||
self.kindlegenError = line
|
||||
# ERROR: EPUB too big
|
||||
if ":E23026:" in line:
|
||||
self.kindlegenErrorCode = 23026
|
||||
tomeNumber = 0
|
||||
for item in outputPath:
|
||||
tomeNumber += 1
|
||||
if len(outputPath) > 1:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file (' + str(tomeNumber)
|
||||
+ '/' + str(len(outputPath)) + ')...', 'info')
|
||||
else:
|
||||
# ERROR: EPUB too big
|
||||
self.kindlegenErrorCode = 23026
|
||||
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')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Creating MOBI file...', 'info')
|
||||
self.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||
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)
|
||||
self.kindlegenErrorCode = 0
|
||||
if os.path.getsize(item) < 367001600:
|
||||
output = Popen('kindlegen "' + item + '"', stdout=PIPE, stderr=STDOUT, shell=True)
|
||||
for line in output.stdout:
|
||||
# ERROR: Generic error
|
||||
if "Error(" in line:
|
||||
self.kindlegenErrorCode = 1
|
||||
self.kindlegenError = line
|
||||
# ERROR: EPUB too big
|
||||
if ":E23026:" in line:
|
||||
self.kindlegenErrorCode = 23026
|
||||
if self.kindlegenErrorCode > 0:
|
||||
break
|
||||
else:
|
||||
# ERROR: EPUB too big
|
||||
self.kindlegenErrorCode = 23026
|
||||
except:
|
||||
# ERROR: Unknown generic error
|
||||
self.kindlegenErrorCode = 1
|
||||
continue
|
||||
if 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:
|
||||
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:
|
||||
epubSize = (os.path.getsize(outputPath))/1024/1024
|
||||
os.remove(outputPath)
|
||||
if os.path.exists(outputPath.replace('.epub', '.mobi')):
|
||||
os.remove(outputPath.replace('.epub', '.mobi'))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
||||
if self.kindlegenErrorCode == 1 and self.kindlegenError:
|
||||
self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
|
||||
if self.kindlegenErrorCode == 23026:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
|
||||
'error')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
|
||||
' Supported size: ~300MB.', 'error')
|
||||
epubSize = (os.path.getsize(item))/1024/1024
|
||||
os.remove(item)
|
||||
if os.path.exists(item.replace('.epub', '.mobi')):
|
||||
os.remove(item.replace('.epub', '.mobi'))
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'KindleGen failed to create MOBI!', 'error')
|
||||
if self.kindlegenErrorCode == 1 and self.kindlegenError:
|
||||
self.emit(QtCore.SIGNAL("showDialog"), "KindleGen error:\n\n" + self.kindlegenError)
|
||||
if self.kindlegenErrorCode == 23026:
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'Created EPUB file was too big.',
|
||||
'error')
|
||||
self.emit(QtCore.SIGNAL("addMessage"), 'EPUB file: ' + str(epubSize) + 'MB.'
|
||||
' Supported size: ~300MB.', 'error')
|
||||
self.emit(QtCore.SIGNAL("hideProgressBar"))
|
||||
self.parent.needClean = True
|
||||
self.emit(QtCore.SIGNAL("addMessage"), '<b>All jobs completed.</b>', 'info')
|
||||
@@ -407,6 +432,20 @@ class Ui_KCC(object):
|
||||
GUI.GammaLabel.setText('Gamma: ' + str(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):
|
||||
if value:
|
||||
GUI.RotateBox.setEnabled(False)
|
||||
@@ -414,14 +453,6 @@ class Ui_KCC(object):
|
||||
else:
|
||||
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):
|
||||
if value == 12:
|
||||
GUI.BasicModeButton.setEnabled(False)
|
||||
@@ -472,7 +503,6 @@ class Ui_KCC(object):
|
||||
|
||||
def updateProgressbar(self, new=False, status=False):
|
||||
if new == "status":
|
||||
pass
|
||||
GUI.ProgressBar.setFormat(status)
|
||||
elif new:
|
||||
GUI.ProgressBar.setMaximum(new - 1)
|
||||
@@ -505,8 +535,15 @@ class Ui_KCC(object):
|
||||
def hideProgressBar(self):
|
||||
GUI.ProgressBar.hide()
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def saveSettings(self, event):
|
||||
if self.conversionAlive:
|
||||
GUI.ConvertButton.setEnabled(False)
|
||||
self.addMessage('Process will be interrupted. Please wait.', 'warning')
|
||||
self.conversionAlive = False
|
||||
self.worker.sync()
|
||||
event.ignore()
|
||||
if not GUI.ConvertButton.isEnabled():
|
||||
event.ignore()
|
||||
self.settings.setValue('lastPath', self.lastPath)
|
||||
self.settings.setValue('lastDevice', GUI.DeviceBox.currentIndex())
|
||||
self.settings.setValue('currentFormat', GUI.FormatBox.currentIndex())
|
||||
@@ -518,7 +555,7 @@ class Ui_KCC(object):
|
||||
'UpscaleBox': GUI.UpscaleBox.checkState(),
|
||||
'NoRotateBox': GUI.NoRotateBox.checkState(),
|
||||
'BorderBox': GUI.BorderBox.checkState(),
|
||||
'StretchBox': GUI.StretchBox.checkState(),
|
||||
'WebtoonBox': GUI.WebtoonBox.checkState(),
|
||||
'NoDitheringBox': GUI.NoDitheringBox.checkState(),
|
||||
'ColorBox': GUI.ColorBox.checkState(),
|
||||
'customWidth': GUI.customWidth.text(),
|
||||
@@ -580,8 +617,7 @@ class Ui_KCC(object):
|
||||
GUI.ConvertButton.clicked.connect(self.convertStart)
|
||||
GUI.GammaSlider.valueChanged.connect(self.changeGamma)
|
||||
GUI.NoRotateBox.stateChanged.connect(self.toggleNoSplitRotate)
|
||||
GUI.UpscaleBox.stateChanged.connect(self.toggleUpscale)
|
||||
GUI.StretchBox.stateChanged.connect(self.toggleStretch)
|
||||
GUI.WebtoonBox.stateChanged.connect(self.toggleWebtoonBox)
|
||||
GUI.DeviceBox.activated.connect(self.changeDevice)
|
||||
KCC.connect(self.worker, QtCore.SIGNAL("progressBarTick"), self.updateProgressbar)
|
||||
KCC.connect(self.worker, QtCore.SIGNAL("modeConvert"), self.modeConvert)
|
||||
@@ -614,6 +650,8 @@ class Ui_KCC(object):
|
||||
elif str(option) == "GammaSlider":
|
||||
GUI.GammaSlider.setValue(int(self.options[option]))
|
||||
self.changeGamma(int(self.options[option]))
|
||||
elif str(option) == "StretchBox" or str(option) == "WebstripBox":
|
||||
pass
|
||||
else:
|
||||
eval('GUI.' + str(option)).setCheckState(self.options[option])
|
||||
if self.currentMode == 1:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
# 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
|
||||
#
|
||||
# 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.UpscaleBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.UpscaleBox.setTristate(True)
|
||||
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
|
||||
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
|
||||
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
|
||||
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
|
||||
self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
|
||||
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
|
||||
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.NoDitheringBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.NoDitheringBox.setObjectName(_fromUtf8("NoDitheringBox"))
|
||||
@@ -263,10 +264,10 @@ class Ui_KCC(object):
|
||||
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
|
||||
self.ProcessingBox.setToolTip(_translate("KCC", "Disable image optimizations.", None))
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image upscaling.<br/>Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None))
|
||||
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p>Enable image stretching.<br/>Aspect ratio will be not preserved.</p></body></html>", None))
|
||||
self.StretchBox.setText(_translate("KCC", "Stretch images", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p>Create PNG files instead JPEG.<br/><span style=\" font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
|
||||
self.BorderBox.setToolTip(_translate("KCC", "Fill space around images with black color.", None))
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'KCC-OSX.ui'
|
||||
#
|
||||
# Created: Fri Jun 21 18:23:35 2013
|
||||
# by: PyQt4 UI code generator 4.10.1
|
||||
# Created: Wed Aug 14 08:39:45 2013
|
||||
# by: PyQt4 UI code generator 4.10.2
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -60,15 +60,16 @@ class Ui_KCC(object):
|
||||
font.setPointSize(11)
|
||||
self.UpscaleBox.setFont(font)
|
||||
self.UpscaleBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.UpscaleBox.setTristate(True)
|
||||
self.UpscaleBox.setObjectName(_fromUtf8("UpscaleBox"))
|
||||
self.gridLayout.addWidget(self.UpscaleBox, 1, 1, 1, 1)
|
||||
self.StretchBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
self.WebtoonBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
self.StretchBox.setFont(font)
|
||||
self.StretchBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.StretchBox.setObjectName(_fromUtf8("StretchBox"))
|
||||
self.gridLayout.addWidget(self.StretchBox, 3, 1, 1, 1)
|
||||
self.WebtoonBox.setFont(font)
|
||||
self.WebtoonBox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.WebtoonBox.setObjectName(_fromUtf8("WebtoonBox"))
|
||||
self.gridLayout.addWidget(self.WebtoonBox, 3, 1, 1, 1)
|
||||
self.NoDitheringBox = QtGui.QCheckBox(self.OptionsAdvanced)
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(11)
|
||||
@@ -321,10 +322,10 @@ class Ui_KCC(object):
|
||||
KCC.setWindowTitle(_translate("KCC", "Kindle Comic Converter", None))
|
||||
self.ProcessingBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Disable image optimizations.</span></p></body></html>", None))
|
||||
self.ProcessingBox.setText(_translate("KCC", "No optimisation", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image upscaling.<br/>Aspect ratio will be preserved.</span></p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Upscale images", None))
|
||||
self.StretchBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Enable image stretching.<br/>Aspect ratio will be not preserved.</span></p></body></html>", None))
|
||||
self.StretchBox.setText(_translate("KCC", "Stretch images", None))
|
||||
self.UpscaleBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Nothing<br/></span>Images smaller than device resolution will not be resized.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Stretching<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be not preserved.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Upscaling<br/></span>Images smaller than device resolution will be resized. Aspect ratio will be preserved.</p></body></html>", None))
|
||||
self.UpscaleBox.setText(_translate("KCC", "Stretch/Upscale", None))
|
||||
self.WebtoonBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-weight:600;\">EXPERIMENTAL!<br/></span>Enable auto-splitting of webtoons like <span style=\" font-style:italic;\">Tower of God</span> or <span style=\" font-style:italic;\">Noblesse</span>.<br/>Pages with a low width, high height and vertical panel flow.</p></body></html>", None))
|
||||
self.WebtoonBox.setText(_translate("KCC", "Webtoon mode", None))
|
||||
self.NoDitheringBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Create PNG files instead JPEG.<br/></span><span style=\" font-size:12pt; font-weight:600;\">Only for non-Kindle devices!</span></p></body></html>", None))
|
||||
self.NoDitheringBox.setText(_translate("KCC", "PNG output", None))
|
||||
self.BorderBox.setToolTip(_translate("KCC", "<html><head/><body><p><span style=\" font-size:12pt;\">Fill space around images with black color.</span></p></body></html>", None))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.2'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -17,7 +17,7 @@
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '3.1'
|
||||
__version__ = '3.2'
|
||||
__license__ = 'ISC'
|
||||
__copyright__ = '2012-2013, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@vulturis.eu>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
@@ -28,17 +28,14 @@ import tempfile
|
||||
import re
|
||||
import stat
|
||||
import string
|
||||
from shutil import move
|
||||
from shutil import copyfile
|
||||
from shutil import copytree
|
||||
from shutil import rmtree
|
||||
from shutil import make_archive
|
||||
from optparse import OptionParser
|
||||
from shutil import move, copyfile, copytree, rmtree, make_archive
|
||||
from optparse import OptionParser, OptionGroup
|
||||
from multiprocessing import Pool, Queue, freeze_support
|
||||
try:
|
||||
from PyQt4 import QtCore
|
||||
except ImportError:
|
||||
QtCore = None
|
||||
import comic2panel
|
||||
import image
|
||||
import cbxarchive
|
||||
import pdfjpgextract
|
||||
@@ -326,8 +323,9 @@ def getImageFileName(imgfile):
|
||||
|
||||
|
||||
def applyImgOptimization(img, isSplit, toRight, options, overrideQuality=5):
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers:
|
||||
if not options.webtoon:
|
||||
img.cropWhiteSpace(10.0)
|
||||
if options.cutpagenumbers and not options.webtoon:
|
||||
img.cutPageNumber()
|
||||
img.optimizeImage(options.gamma)
|
||||
if overrideQuality != 5:
|
||||
@@ -361,12 +359,12 @@ def dirImgProcess(path):
|
||||
while not splitpages.ready():
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
queue.get(True, 1)
|
||||
queue.get(True, 5)
|
||||
except:
|
||||
pass
|
||||
if not GUI.conversionAlive:
|
||||
pool.terminate()
|
||||
rmtree(path)
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise UserWarning("Conversion interrupted.")
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"))
|
||||
pool.join()
|
||||
@@ -374,7 +372,7 @@ def dirImgProcess(path):
|
||||
try:
|
||||
splitpages = splitpages.get()
|
||||
except:
|
||||
rmtree(path)
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise RuntimeError("One of workers crashed. Cause: " + str(sys.exc_info()[1]))
|
||||
splitpages = filter(None, splitpages)
|
||||
splitpages.sort()
|
||||
@@ -384,7 +382,7 @@ def dirImgProcess(path):
|
||||
pagenumbermodifier += 1
|
||||
pagenumbermodifier += 1
|
||||
else:
|
||||
rmtree(path)
|
||||
rmtree(os.path.join(path, '..', '..'))
|
||||
raise UserWarning("Source directory is empty.")
|
||||
|
||||
|
||||
@@ -455,7 +453,6 @@ def genEpubStruct(path):
|
||||
chapterlist = []
|
||||
cover = None
|
||||
_, deviceres, _, _, panelviewsize = options.profileData
|
||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
os.mkdir(os.path.join(path, 'OEBPS', 'Text'))
|
||||
f = open(os.path.join(path, 'OEBPS', 'Text', 'style.css'), 'w')
|
||||
# DON'T COMPRESS CSS. KINDLE WILL FAIL TO PARSE IT.
|
||||
@@ -601,8 +598,9 @@ def genEpubStruct(path):
|
||||
|
||||
|
||||
def getWorkFolder(afile):
|
||||
workdir = tempfile.mkdtemp()
|
||||
if os.path.isdir(afile):
|
||||
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.join(os.path.splitext(afile)[0], '..'))
|
||||
try:
|
||||
os.rmdir(workdir) # needed for copytree() fails if dst already exists
|
||||
fullPath = os.path.join(workdir, 'OEBPS', 'Images')
|
||||
@@ -614,8 +612,13 @@ def getWorkFolder(afile):
|
||||
raise
|
||||
elif afile.lower().endswith('.pdf'):
|
||||
pdf = pdfjpgextract.PdfJpgExtract(afile)
|
||||
path = pdf.extract()
|
||||
path, njpg = pdf.extract()
|
||||
if njpg == 0:
|
||||
rmtree(path)
|
||||
raise UserWarning("Failed to extract images.")
|
||||
else:
|
||||
workdir = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#workdir = tempfile.mkdtemp('', 'KCC-TMP-', os.path.dirname(afile))
|
||||
cbx = cbxarchive.CBxArchive(afile)
|
||||
if cbx.isCbxFile():
|
||||
try:
|
||||
@@ -654,13 +657,16 @@ def sanitizeTree(filetree):
|
||||
splitname = os.path.splitext(name)
|
||||
slugified = slugify(splitname[0])
|
||||
while os.path.exists(os.path.join(root, slugified + splitname[1])):
|
||||
slugified += "1"
|
||||
slugified += "A"
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugified + splitname[1]))
|
||||
for name in dirs:
|
||||
if name.startswith('.'):
|
||||
os.remove(os.path.join(root, name))
|
||||
else:
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugify(name)))
|
||||
slugified = slugify(name)
|
||||
while os.path.exists(os.path.join(root, slugified)):
|
||||
slugified += "A"
|
||||
os.rename(os.path.join(root, name), os.path.join(root, slugified))
|
||||
|
||||
|
||||
def sanitizeTreeBeforeConversion(filetree):
|
||||
@@ -674,6 +680,158 @@ def sanitizeTreeBeforeConversion(filetree):
|
||||
os.chmod(os.path.join(root, name), stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
||||
|
||||
|
||||
def getDirectorySize(start_path='.'):
|
||||
total_size = 0
|
||||
for dirpath, dirnames, filenames in os.walk(start_path):
|
||||
for f in filenames:
|
||||
fp = os.path.join(dirpath, f)
|
||||
total_size += os.path.getsize(fp)
|
||||
return total_size
|
||||
|
||||
|
||||
def createNewTome(parentPath):
|
||||
tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-')
|
||||
#tomePathRoot = tempfile.mkdtemp('', 'KCC-TMP-', parentPath)
|
||||
tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images')
|
||||
os.makedirs(tomePath)
|
||||
return tomePath, tomePathRoot
|
||||
|
||||
|
||||
def walkLevel(some_dir, level=1):
|
||||
some_dir = some_dir.rstrip(os.path.sep)
|
||||
assert os.path.isdir(some_dir)
|
||||
num_sep = some_dir.count(os.path.sep)
|
||||
for root, dirs, files in os.walk(some_dir):
|
||||
yield root, dirs, files
|
||||
num_sep_this = root.count(os.path.sep)
|
||||
if num_sep + level <= num_sep_this:
|
||||
del dirs[:]
|
||||
|
||||
|
||||
def splitDirectory(path, mode, parentPath):
|
||||
output = []
|
||||
currentSize = 0
|
||||
currentTarget = path
|
||||
if mode == 0:
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in files:
|
||||
size = os.path.getsize(os.path.join(root, name))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
elif mode == 1:
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in dirs:
|
||||
size = getDirectorySize(os.path.join(root, name))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
elif mode == 2:
|
||||
firstTome = True
|
||||
for root, dirs, files in walkLevel(path, 0):
|
||||
for name in dirs:
|
||||
size = getDirectorySize(os.path.join(root, name))
|
||||
currentSize = 0
|
||||
if size > 262144000:
|
||||
if not firstTome:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
else:
|
||||
firstTome = False
|
||||
for rootInside, dirsInside, filesInside in walkLevel(os.path.join(root, name), 0):
|
||||
for nameInside in dirsInside:
|
||||
size = getDirectorySize(os.path.join(rootInside, nameInside))
|
||||
if currentSize + size > 262144000:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
currentSize = size
|
||||
else:
|
||||
currentSize += size
|
||||
if path != currentTarget:
|
||||
move(os.path.join(rootInside, nameInside), os.path.join(currentTarget, nameInside))
|
||||
else:
|
||||
if not firstTome:
|
||||
currentTarget, pathRoot = createNewTome(parentPath)
|
||||
output.append(pathRoot)
|
||||
move(os.path.join(root, name), os.path.join(currentTarget, name))
|
||||
else:
|
||||
firstTome = False
|
||||
return output
|
||||
|
||||
|
||||
# noinspection PyUnboundLocalVariable
|
||||
def preSplitDirectory(path):
|
||||
if getDirectorySize(os.path.join(path, 'OEBPS', 'Images')) > 262144000:
|
||||
# Detect directory stucture
|
||||
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0):
|
||||
subdirectoryNumber = len(dirs)
|
||||
filesNumber = len(files)
|
||||
if subdirectoryNumber == 0:
|
||||
# No subdirectories
|
||||
mode = 0
|
||||
else:
|
||||
if filesNumber > 0:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
detectedSubSubdirectories = False
|
||||
detectedFilesInSubdirectories = False
|
||||
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1):
|
||||
if root != os.path.join(path, 'OEBPS', 'Images'):
|
||||
if len(dirs) != 0:
|
||||
detectedSubSubdirectories = True
|
||||
elif len(dirs) == 0 and detectedSubSubdirectories:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
if len(files) != 0:
|
||||
detectedFilesInSubdirectories = True
|
||||
if detectedSubSubdirectories:
|
||||
# Two levels of subdirectories
|
||||
mode = 2
|
||||
else:
|
||||
# One level of subdirectories
|
||||
mode = 1
|
||||
if detectedFilesInSubdirectories and detectedSubSubdirectories:
|
||||
print '\nWARNING: Automatic output splitting failed.'
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), 'Automatic output splitting failed. <a href='
|
||||
'"https://github.com/ciromattia/kcc/wiki'
|
||||
'/Automatic-output-splitting">'
|
||||
'More details.</a>', 'warning')
|
||||
GUI.emit(QtCore.SIGNAL("addMessage"), '')
|
||||
return [path]
|
||||
# Split directories
|
||||
split = splitDirectory(os.path.join(path, 'OEBPS', 'Images'), mode, os.path.join(path, '..'))
|
||||
path = [path]
|
||||
for tome in split:
|
||||
path.append(tome)
|
||||
return path
|
||||
else:
|
||||
# No splitting is necessary
|
||||
return [path]
|
||||
|
||||
|
||||
def Copyright():
|
||||
print ('comic2ebook v%(__version__)s. '
|
||||
'Written 2013 by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())
|
||||
@@ -686,48 +844,64 @@ def Usage():
|
||||
|
||||
def main(argv=None, qtGUI=None):
|
||||
global parser, options, epub_path, splitCount, GUI
|
||||
usage = "Usage: %prog [options] comic_file|comic_folder"
|
||||
parser = OptionParser(usage=usage, version=__version__)
|
||||
parser.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
|
||||
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD, KFHD8,"
|
||||
" KFA) [Default=KHD]")
|
||||
parser.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename]")
|
||||
parser.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="Manga style (Right-to-left reading and splitting) [Default=False]")
|
||||
parser.add_option("--quality", type="int", dest="quality", default="0",
|
||||
help="Output quality. 0 - Normal 1 - High 2 - Ultra [Default=0]")
|
||||
parser.add_option("-c", "--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
||||
help="Outputs a CBZ archive and does not generate EPUB")
|
||||
parser.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
||||
help="Do not apply image preprocessing (Page splitting and optimizations) [Default=True]")
|
||||
parser.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG (For non-Kindle devices) [Default=False]")
|
||||
parser.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||
parser.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
||||
help="Resize images smaller than device's resolution [Default=False]")
|
||||
parser.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
||||
help="Stretch images to device's resolution [Default=False]")
|
||||
parser.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders instead of white ones when not stretching and ratio "
|
||||
+ "is not like the device's one [Default=False]")
|
||||
parser.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
||||
help="Rotate landscape pages instead of splitting them [Default=False]")
|
||||
parser.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
||||
help="Disable splitting and rotation [Default=False]")
|
||||
parser.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Do not try to cut page numbering on images [Default=True]")
|
||||
parser.add_option("-o", "--output", action="store", dest="output", default=None,
|
||||
help="Output generated file (EPUB or CBZ) to specified directory or file")
|
||||
parser.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||
help="Do not convert images to grayscale [Default=False]")
|
||||
parser.add_option("--customwidth", type="int", dest="customwidth", default=0,
|
||||
help="Replace screen width provided by device profile [Default=0]")
|
||||
parser.add_option("--customheight", type="int", dest="customheight", default=0,
|
||||
help="Replace screen height provided by device profile [Default=0]")
|
||||
parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output [Default=False]")
|
||||
parser = OptionParser(usage="Usage: %prog [options] comic_file|comic_folder", add_help_option=False)
|
||||
mainOptions = OptionGroup(parser, "MAIN")
|
||||
experimentalOptions = OptionGroup(parser, "EXPERIMENTAL")
|
||||
processingOptions = OptionGroup(parser, "PROCESSING")
|
||||
outputOptions = OptionGroup(parser, "OUTPUT SETTINGS")
|
||||
customProfileOptions = OptionGroup(parser, "CUSTOM PROFILE")
|
||||
otherOptions = OptionGroup(parser, "OTHER")
|
||||
mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KHD",
|
||||
help="Device profile (Choose one among K1, K2, K3, K4NT, K4T, KDX, KDXG, KHD, KF, KFHD,"
|
||||
" KFHD8, KFA) [Default=KHD]")
|
||||
mainOptions.add_option("-q", "--quality", type="int", dest="quality", default="0",
|
||||
help="Quality of Panel View. 0 - Normal 1 - High 2 - Ultra [Default=0]")
|
||||
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
|
||||
help="Manga style (Right-to-left reading and splitting)")
|
||||
outputOptions.add_option("-o", "--output", action="store", dest="output", default=None,
|
||||
help="Output generated file to specified directory or file")
|
||||
outputOptions.add_option("-t", "--title", action="store", dest="title", default="defaulttitle",
|
||||
help="Comic title [Default=filename or directory name]")
|
||||
outputOptions.add_option("--cbz-output", action="store_true", dest="cbzoutput", default=False,
|
||||
help="Outputs a CBZ archive and does not generate EPUB")
|
||||
outputOptions.add_option("--batchsplit", action="store_true", dest="batchsplit", default=False,
|
||||
help="Split output into multiple files"),
|
||||
experimentalOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
|
||||
help="Webtoon processing mode"),
|
||||
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
|
||||
help="Use black borders instead of white ones")
|
||||
processingOptions.add_option("--forcecolor", action="store_true", dest="forcecolor", default=False,
|
||||
help="Don't convert images to grayscale")
|
||||
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
|
||||
help="Create PNG files instead JPEG (For non-Kindle devices)")
|
||||
processingOptions.add_option("--gamma", type="float", dest="gamma", default="0.0",
|
||||
help="Apply gamma correction to linearize the image [Default=Auto]")
|
||||
processingOptions.add_option("--nocutpagenumbers", action="store_false", dest="cutpagenumbers", default=True,
|
||||
help="Don't try to cut page numbering on images")
|
||||
processingOptions.add_option("--noprocessing", action="store_false", dest="imgproc", default=True,
|
||||
help="Don't apply image preprocessing")
|
||||
processingOptions.add_option("--nosplitrotate", action="store_true", dest="nosplitrotate", default=False,
|
||||
help="Disable splitting and rotation")
|
||||
processingOptions.add_option("--rotate", action="store_true", dest="rotate", default=False,
|
||||
help="Rotate landscape pages instead of splitting them")
|
||||
processingOptions.add_option("--stretch", action="store_true", dest="stretch", default=False,
|
||||
help="Stretch images to device's resolution")
|
||||
processingOptions.add_option("--upscale", action="store_true", dest="upscale", default=False,
|
||||
help="Resize images smaller than device's resolution")
|
||||
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
|
||||
help="Replace screen width provided by device profile")
|
||||
customProfileOptions.add_option("--customheight", type="int", dest="customheight", default=0,
|
||||
help="Replace screen height provided by device profile")
|
||||
otherOptions.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False,
|
||||
help="Verbose output")
|
||||
otherOptions.add_option("-h", "--help", action="help",
|
||||
help="Show this help message and exit")
|
||||
parser.add_option_group(mainOptions)
|
||||
parser.add_option_group(experimentalOptions)
|
||||
parser.add_option_group(outputOptions)
|
||||
parser.add_option_group(processingOptions)
|
||||
parser.add_option_group(customProfileOptions)
|
||||
parser.add_option_group(otherOptions)
|
||||
options, args = parser.parse_args(argv)
|
||||
checkOptions()
|
||||
if qtGUI:
|
||||
@@ -739,33 +913,57 @@ def main(argv=None, qtGUI=None):
|
||||
parser.print_help()
|
||||
return
|
||||
path = getWorkFolder(args[0])
|
||||
if options.title == 'defaulttitle':
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
if options.webtoon:
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Splitting images')
|
||||
if options.customheight > 0:
|
||||
comic2panel.main(['-y ' + str(options.customheight), '-i', path], qtGUI)
|
||||
else:
|
||||
comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', path], qtGUI)
|
||||
splitCount = 0
|
||||
if options.imgproc:
|
||||
print "Processing images..."
|
||||
print "\nProcessing images..."
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 'status', 'Processing images')
|
||||
dirImgProcess(path + "/OEBPS/Images/")
|
||||
if GUI:
|
||||
GUI.emit(QtCore.SIGNAL("progressBarTick"), 1)
|
||||
if options.cbzoutput:
|
||||
# if CBZ output wanted, compress all images and return filepath
|
||||
print "\nCreating CBZ file..."
|
||||
filepath = getOutputFilename(args[0], options.output, '.cbz')
|
||||
make_archive(path + '_comic', 'zip', path + '/OEBPS/Images')
|
||||
sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
|
||||
if options.batchsplit:
|
||||
tomes = preSplitDirectory(path)
|
||||
else:
|
||||
print "\nCreating EPUB structure..."
|
||||
genEpubStruct(path)
|
||||
# actually zip the ePub
|
||||
filepath = getOutputFilename(args[0], options.output, '.epub')
|
||||
make_archive(path + '_comic', 'zip', path)
|
||||
move(path + '_comic.zip', filepath)
|
||||
rmtree(path)
|
||||
tomes = [path]
|
||||
filepath = []
|
||||
tomeNumber = 0
|
||||
for tome in tomes:
|
||||
if len(tomes) > 1:
|
||||
tomeNumber += 1
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0] + ' ' + str(tomeNumber)
|
||||
elif options.title == 'defaulttitle':
|
||||
options.title = os.path.splitext(os.path.basename(args[0]))[0]
|
||||
if options.cbzoutput:
|
||||
# if CBZ output wanted, compress all images and return filepath
|
||||
print "\nCreating CBZ file..."
|
||||
if len(tomes) > 1:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.cbz', ''))
|
||||
make_archive(tome + '_comic', 'zip', tome + '/OEBPS/Images')
|
||||
else:
|
||||
print "\nCreating EPUB structure..."
|
||||
genEpubStruct(tome)
|
||||
# actually zip the ePub
|
||||
if len(tomes) > 1:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.epub', ' ' + str(tomeNumber)))
|
||||
else:
|
||||
filepath.append(getOutputFilename(args[0], options.output, '.epub', ''))
|
||||
make_archive(tome + '_comic', 'zip', tome)
|
||||
move(tome + '_comic.zip', filepath[-1])
|
||||
rmtree(tome)
|
||||
return filepath
|
||||
|
||||
|
||||
def getOutputFilename(srcpath, wantedname, ext):
|
||||
def getOutputFilename(srcpath, wantedname, ext, tomeNumber):
|
||||
if not ext.startswith('.'):
|
||||
ext = '.' + ext
|
||||
if wantedname is not None:
|
||||
@@ -774,19 +972,23 @@ def getOutputFilename(srcpath, wantedname, ext):
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(srcpath) + ext
|
||||
else:
|
||||
filename = os.path.abspath(options.output) + "/" \
|
||||
+ os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
||||
filename = os.path.abspath(options.output) + "/" + os.path.basename(os.path.splitext(srcpath)[0]) + ext
|
||||
elif os.path.isdir(srcpath):
|
||||
filename = srcpath + ext
|
||||
filename = srcpath + tomeNumber + ext
|
||||
else:
|
||||
filename = os.path.splitext(srcpath)[0] + ext
|
||||
filename = os.path.splitext(srcpath)[0] + tomeNumber + ext
|
||||
if os.path.isfile(filename):
|
||||
filename = os.path.splitext(filename)[0] + '_kcc' + ext
|
||||
filename = os.path.splitext(filename)[0] + '_kcc' + tomeNumber + ext
|
||||
return filename
|
||||
|
||||
|
||||
def checkOptions():
|
||||
global options
|
||||
# Webtoon mode mandatory options
|
||||
if options.webtoon:
|
||||
options.nosplitrotate = True
|
||||
options.black_borders = False
|
||||
options.quality = 0
|
||||
# Landscape mode is only supported by Kindle Touch and Paperwhite.
|
||||
if options.profile == 'K4T' or options.profile == 'KHD':
|
||||
options.landscapemode = True
|
||||
@@ -842,11 +1044,6 @@ def checkOptions():
|
||||
options.profileData = image.ProfileData.Profiles[options.profile]
|
||||
|
||||
|
||||
def getEpubPath():
|
||||
global epub_path
|
||||
return epub_path
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
freeze_support()
|
||||
Copyright()
|
||||
|
||||
315
kcc/comic2panel.py
Normal file
315
kcc/comic2panel.py
Normal file
@@ -0,0 +1,315 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2012 Ciro Mattia Gonano <ciromattia@gmail.com>
|
||||
#
|
||||
# Permission to use, copy, modify, and/or distribute this software for
|
||||
# any purpose with or without fee is hereby granted, provided that the
|
||||
# above copyright notice and this permission notice appear in all
|
||||
# copies.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
|
||||
# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
|
||||
# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
|
||||
# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA
|
||||
# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
|
||||
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
# PERFORMANCE OF THIS SOFTWARE.
|
||||
#
|
||||
__version__ = '3.2'
|
||||
__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
|
||||
try:
|
||||
# noinspection PyUnresolvedReferences
|
||||
# noinspection PyUnresolvedReferences,PyPackageRequirements
|
||||
from PIL import Image, ImageOps, ImageStat, ImageChops
|
||||
except ImportError:
|
||||
print "ERROR: Pillow is not installed!"
|
||||
@@ -115,19 +115,26 @@ class ComicPage:
|
||||
# Detect corrupted files - Phase 2
|
||||
try:
|
||||
self.origFileName = source
|
||||
self.filename = os.path.basename(self.origFileName)
|
||||
self.image = Image.open(source)
|
||||
except IOError:
|
||||
raise RuntimeError('Cannot read image file %s' % source)
|
||||
# Detect corrupted files - Phase 3
|
||||
try:
|
||||
self.image = Image.open(source)
|
||||
self.image.verify()
|
||||
except:
|
||||
raise RuntimeError('Image file %s is corrupted' % source)
|
||||
# Detect corrupted files - Phase 4
|
||||
try:
|
||||
self.image = Image.open(source)
|
||||
self.image.load()
|
||||
except:
|
||||
raise RuntimeError('Image file %s is corrupted' % source)
|
||||
self.image = Image.open(source)
|
||||
self.image = self.image.convert('RGB')
|
||||
|
||||
def saveToDir(self, targetdir, forcepng, color, wipe, suffix=None):
|
||||
filename = os.path.basename(self.origFileName)
|
||||
try:
|
||||
if not color:
|
||||
self.image = self.image.convert('L') # convert to grayscale
|
||||
@@ -136,13 +143,13 @@ class ComicPage:
|
||||
else:
|
||||
suffix = ""
|
||||
if wipe:
|
||||
os.remove(os.path.join(targetdir, filename))
|
||||
os.remove(os.path.join(targetdir, self.filename))
|
||||
else:
|
||||
suffix += "_kcchq"
|
||||
if forcepng:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".png"), "PNG")
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".png"), "PNG")
|
||||
else:
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(filename)[0] + suffix + ".jpg"), "JPEG")
|
||||
self.image.save(os.path.join(targetdir, os.path.splitext(self.filename)[0] + suffix + ".jpg"), "JPEG")
|
||||
except IOError as e:
|
||||
raise RuntimeError('Cannot write image in directory %s: %s' % (targetdir, e))
|
||||
|
||||
@@ -167,10 +174,15 @@ class ComicPage:
|
||||
def resizeImage(self, upscale=False, stretch=False, black_borders=False, isSplit=False, toRight=False,
|
||||
landscapeMode=False, qualityMode=0):
|
||||
method = Image.ANTIALIAS
|
||||
if black_borders:
|
||||
if '-KCCFW' in str(self.filename):
|
||||
fill = 'white'
|
||||
elif '-KCCFB' in str(self.filename):
|
||||
fill = 'black'
|
||||
else:
|
||||
fill = 'white'
|
||||
if black_borders:
|
||||
fill = 'black'
|
||||
else:
|
||||
fill = 'white'
|
||||
if qualityMode == 0:
|
||||
size = (self.size[0], self.size[1])
|
||||
else:
|
||||
@@ -227,7 +239,7 @@ class ComicPage:
|
||||
# source is portrait and target is landscape, so split by the height
|
||||
leftbox = (0, 0, width, height / 2)
|
||||
rightbox = (0, height / 2, width, height)
|
||||
filename = os.path.splitext(os.path.basename(self.origFileName))
|
||||
filename = os.path.splitext(self.filename)
|
||||
fileone = targetdir + '/' + filename[0] + '_kcca' + filename[1]
|
||||
filetwo = targetdir + '/' + filename[0] + '_kccb' + filename[1]
|
||||
try:
|
||||
|
||||
@@ -29,7 +29,7 @@ class PdfJpgExtract:
|
||||
def __init__(self, origFileName):
|
||||
self.origFileName = origFileName
|
||||
self.filename = os.path.splitext(origFileName)
|
||||
self.path = self.filename[0]
|
||||
self.path = self.filename[0] + "-KCC-TMP"
|
||||
|
||||
def getPath(self):
|
||||
return self.path
|
||||
@@ -70,4 +70,4 @@ class PdfJpgExtract:
|
||||
|
||||
njpg += 1
|
||||
i = iend
|
||||
return self.path
|
||||
return self.path, njpg
|
||||
|
||||
Reference in New Issue
Block a user