1
0
mirror of https://github.com/ciromattia/kcc synced 2026-04-17 22:48:53 +00:00

Compare commits

...

18 Commits
5.1.3 ... 5.2.1

Author SHA1 Message Date
Paweł Jastrzębski
d76eea9f43 Merge pull request #216 from ciromattia/dev
5.2.1
2016-11-26 18:13:08 +01:00
Paweł Jastrzębski
2e55f22355 Updated README + version bump 2016-11-26 18:12:29 +01:00
Paweł Jastrzębski
30b8770e34 Improved error reporting 2016-11-26 17:59:40 +01:00
Paweł Jastrzębski
9ad161489f Decreased ferocity of margin cropping 2016-11-26 14:56:51 +01:00
Paweł Jastrzębski
bdb459cfab Code cleanup 2016-11-26 09:23:39 +01:00
Paweł Jastrzębski
2e85556543 GUI update 2016-11-25 18:57:42 +01:00
Paweł Jastrzębski
93ebbbd0af Refactored and improved output splitting 2016-11-25 18:05:05 +01:00
Paweł Jastrzębski
dd5c907bad Merge pull request #215 from ciromattia/dev
5.2
2016-11-22 08:53:56 +01:00
Paweł Jastrzębski
64fb4a9eca Updated README + version bump 2016-11-22 08:33:02 +01:00
Paweł Jastrzębski
284c577894 Fixed some file lock anomalies 2016-11-21 17:24:58 +01:00
Paweł Jastrzębski
c68c9892e4 GUI update 2016-11-21 16:36:46 +01:00
Paweł Jastrzębski
aa00ea3aa2 Expanded autoscale option 2016-11-21 15:59:14 +01:00
Paweł Jastrzębski
88f005824c Merge branch 'dev' of https://github.com/ciromattia/kcc into dev
# Conflicts:
#	kcc/comic2ebook.py
2016-11-21 14:06:04 +01:00
Paweł Jastrzębski
2a2bfae112 Dropped HQ PV option 2016-11-21 13:55:12 +01:00
Paweł Jastrzębski
583eec787f Merge pull request #214 from houcheng/autoscale
Add autoscale option
2016-11-21 13:51:51 +01:00
Houcheng Lin
9ce691aecb add autoscale option
Instead of fixed 1.5 scale ratio, the autoscale feature uses current page's
image width, and dynamically determine the needed scale ratio. The rendering
effects looks okay and speed is fine in my KPW1.

The generated panel view will have two view ports: (top and bottom).
2016-11-20 17:38:06 -05:00
Paweł Jastrzębski
d1a07d7ffa Improved cropping mechanism 2016-11-19 17:57:16 +01:00
Paweł Jastrzębski
b545f7ad48 Small tweaks 2016-11-18 08:39:16 +01:00
13 changed files with 273 additions and 422 deletions

View File

@@ -75,9 +75,11 @@ Usage: kcc-c2e [options] comic_file|comic_folder
Options: Options:
MAIN: MAIN:
-p PROFILE, --profile=PROFILE -p PROFILE, --profile=PROFILE
Device profile (Available options: K1, K2, K3, K45, KDX, Device profile (Available options: K1, K2, K3, K45,
KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO) [Default=KV] KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
KoAO) [Default=KV]
-m, --manga-style Manga style (right-to-left reading and splitting) -m, --manga-style Manga style (right-to-left reading and splitting)
-2, --two-panel Display two not four panels in Panel View mode
-w, --webtoon Webtoon processing mode -w, --webtoon Webtoon processing mode
OUTPUT SETTINGS: OUTPUT SETTINGS:
@@ -86,29 +88,31 @@ Options:
-t TITLE, --title=TITLE -t TITLE, --title=TITLE
Comic title [Default=filename or directory name] Comic title [Default=filename or directory name]
-f FORMAT, --format=FORMAT -f FORMAT, --format=FORMAT
Output format (Available options: Auto, MOBI, EPUB, CBZ) Output format (Available options: Auto, MOBI, EPUB,
[Default=Auto] CBZ) [Default=Auto]
-b, --batchsplit Split output into multiple files -b BATCHSPLIT, --batchsplit=BATCHSPLIT
Split output into multiple files. 0: Don't split 1:
Automatic mode 2: Consider every subdirectory as
separate volume [Default=0]
PROCESSING: PROCESSING:
-u, --upscale Resize images smaller than device's resolution -u, --upscale Resize images smaller than device's resolution
-s, --stretch Stretch images to device's resolution -s, --stretch Stretch images to device's resolution
-r SPLITTER, --splitter=SPLITTER -r SPLITTER, --splitter=SPLITTER
Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0] Double page parsing mode. 0: Split 1: Rotate 2: Both
[Default=0]
-g GAMMA, --gamma=GAMMA -g GAMMA, --gamma=GAMMA
Apply gamma correction to linearize the image [Default=Auto] Apply gamma correction to linearize the image
--hq Enable high quality Panel View [Default=Auto]
-c CROPPING, --cropping=CROPPING
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
page numbers [Default=2]
--cp=CROPPINGP, --croppingpower=CROPPINGP
Set cropping power [Default=1.0]
--blackborders Disable autodetection and force black borders --blackborders Disable autodetection and force black borders
--whiteborders Disable autodetection and force white borders --whiteborders Disable autodetection and force white borders
--forcecolor Don't convert images to grayscale --forcecolor Don't convert images to grayscale
--forcepng Create PNG files instead JPEG --forcepng Create PNG files instead JPEG
--cropping=CROPPING
Set cropping mode. 0: Disabled 1: Margins 2: Margins +
page numbers [Default=2]
--croppingpower=CROPPINGP
Set margin cropping threshold [Default=0.1]
--croppingpowerpage=CROPPINGPN
Set page number cropping threshold [Default=5.0]
CUSTOM PROFILE: CUSTOM PROFILE:
--customwidth=CUSTOMWIDTH --customwidth=CUSTOMWIDTH
@@ -159,6 +163,17 @@ The app relies and includes the following scripts:
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub) * [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
## CHANGELOG ## CHANGELOG
####5.2.1:
* Improved directory parsing
* Tweaked margin detection algorithm
* Improved error reporting
####5.2:
* Added new Panel View options
* Implemented new margin detection algorithm
* Removed HQ Panel View mode
* Fixed multiple smaller issues
####5.1.3: ####5.1.3:
* Added Kobo Aura ONE profile * Added Kobo Aura ONE profile
* Fixed few small bugs * Fixed few small bugs

View File

@@ -174,10 +174,10 @@
<item row="0" column="2"> <item row="0" column="2">
<widget class="QCheckBox" name="qualityBox"> <widget class="QCheckBox" name="qualityBox">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;High quality Panel View.&lt;br/&gt;Require source files with bigger resolution than target device.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Highly impact size of output file!&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 style=\'white-space:pre\'&gt;&lt;span style=\&quot; font-weight:600; text-decoration: underline;\&quot;&gt;Unchecked - 4 panels&lt;br/&gt;&lt;/span&gt;Zoom each corner separately.&lt;/p&gt;&lt;p style=\'white-space:pre\'&gt;&lt;span style=\&quot; font-weight:600; text-decoration: underline;\&quot;&gt;Checked - 2 panels&lt;br/&gt;&lt;/span&gt;Zoom only the top and bottom of the page.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>HQ zoom</string> <string>Panel View 4/2</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -228,12 +228,12 @@
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="2" column="1">
<widget class="QCheckBox" name="noDitheringBox"> <widget class="QCheckBox" name="outputSplit">
<property name="toolTip"> <property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p style='white-space:pre'&gt;Create PNG files instead JPEG.&lt;br/&gt;Quality increase is not noticeable on most of devices.&lt;br/&gt;Output files &lt;span style=&quot; font-weight:600;&quot;&gt;might&lt;/span&gt; be smaller.&lt;br/&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;MOBI conversion will be much slower.&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 style='white-space:pre'&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Unchecked - Automatic mode&lt;br/&gt;&lt;/span&gt;Output will be splitted automatically.&lt;/p&gt;&lt;p style='white-space:pre'&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - Volume mode&lt;br/&gt;&lt;/span&gt;Every subdirectory will be considered as separate volume.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>PNG output</string> <string>Output split</string>
</property> </property>
</widget> </widget>
</item> </item>
@@ -510,7 +510,7 @@
<tabstop>upscaleBox</tabstop> <tabstop>upscaleBox</tabstop>
<tabstop>gammaBox</tabstop> <tabstop>gammaBox</tabstop>
<tabstop>borderBox</tabstop> <tabstop>borderBox</tabstop>
<tabstop>noDitheringBox</tabstop> <tabstop>outputSplit</tabstop>
<tabstop>colorBox</tabstop> <tabstop>colorBox</tabstop>
<tabstop>editorButton</tabstop> <tabstop>editorButton</tabstop>
<tabstop>wikiButton</tabstop> <tabstop>wikiButton</tabstop>

View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "5.1.3" #define MyAppVersion "5.2.1"
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski" #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
#define MyAppURL "http://kcc.iosphe.re/" #define MyAppURL "http://kcc.iosphe.re/"
#define MyAppExeName "KCC.exe" #define MyAppExeName "KCC.exe"

View File

@@ -21,19 +21,17 @@ import os
import sys import sys
from urllib.parse import unquote from urllib.parse import unquote
from urllib.request import urlopen, urlretrieve, Request from urllib.request import urlopen, urlretrieve, Request
from time import sleep, time from time import sleep
from datetime import datetime
from shutil import move from shutil import move
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork
from xml.dom.minidom import parse, Document from xml.dom.minidom import parse
from psutil import Popen, Process from psutil import Popen, Process
from copy import copy from copy import copy
from distutils.version import StrictVersion from distutils.version import StrictVersion
from xml.sax.saxutils import escape from xml.sax.saxutils import escape
from platform import platform
from raven import Client from raven import Client
from .shared import md5Checksum, HTMLStripper, sanitizeTrace from .shared import md5Checksum, HTMLStripper, sanitizeTrace, saferRemove
from . import __version__ from . import __version__
from . import comic2ebook from . import comic2ebook
from . import metadata from . import metadata
@@ -257,7 +255,7 @@ class WorkerThread(QtCore.QThread):
elif GUI.rotateBox.checkState() == 2: elif GUI.rotateBox.checkState() == 2:
options.splitter = 1 options.splitter = 1
if GUI.qualityBox.isChecked(): if GUI.qualityBox.isChecked():
options.hqmode = True options.autoscale = True
if GUI.webtoonBox.isChecked(): if GUI.webtoonBox.isChecked():
options.webtoon = True options.webtoon = True
if GUI.upscaleBox.checkState() == 1: if GUI.upscaleBox.checkState() == 1:
@@ -270,8 +268,8 @@ class WorkerThread(QtCore.QThread):
options.white_borders = True options.white_borders = True
elif GUI.borderBox.checkState() == 2: elif GUI.borderBox.checkState() == 2:
options.black_borders = True options.black_borders = True
if GUI.noDitheringBox.isChecked(): if GUI.outputSplit.isChecked():
options.forcepng = True options.batchsplit = 2
if GUI.colorBox.isChecked(): if GUI.colorBox.isChecked():
options.forcecolor = True options.forcecolor = True
if GUI.currentMode > 2: if GUI.currentMode > 2:
@@ -319,10 +317,15 @@ class WorkerThread(QtCore.QThread):
GUI.progress.content = '' GUI.progress.content = ''
self.errors = True self.errors = True
_, _, traceback = sys.exc_info() _, _, traceback = sys.exc_info()
if len(err.args) == 1:
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
else:
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
GUI.sentry.extra_context({'realTraceback': err.args[1]})
if ' is corrupted.' not in str(err): if ' is corrupted.' not in str(err):
GUI.sentry.captureException() GUI.sentry.captureException()
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
MW.addMessage.emit('Error during conversion! Please consult ' MW.addMessage.emit('Error during conversion! Please consult '
'<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> ' '<a href="https://github.com/ciromattia/kcc/wiki/Error-messages">wiki</a> '
'for more details.', 'error', False) 'for more details.', 'error', False)
@@ -331,7 +334,7 @@ class WorkerThread(QtCore.QThread):
if 'outputPath' in locals(): if 'outputPath' in locals():
for item in outputPath: for item in outputPath:
if os.path.exists(item): if os.path.exists(item):
os.remove(item) saferRemove(item)
self.clean() self.clean()
return return
if not self.errors: if not self.errors:
@@ -358,9 +361,9 @@ class WorkerThread(QtCore.QThread):
if not self.conversionAlive: if not self.conversionAlive:
for item in outputPath: for item in outputPath:
if os.path.exists(item): if os.path.exists(item):
os.remove(item) saferRemove(item)
if os.path.exists(item.replace('.epub', '.mobi')): if os.path.exists(item.replace('.epub', '.mobi')):
os.remove(item.replace('.epub', '.mobi')) saferRemove(item.replace('.epub', '.mobi'))
self.clean() self.clean()
return return
if self.kindlegenErrorCode[0] == 0: if self.kindlegenErrorCode[0] == 0:
@@ -381,7 +384,7 @@ class WorkerThread(QtCore.QThread):
for item in outputPath: for item in outputPath:
GUI.progress.content = '' GUI.progress.content = ''
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
os.remove(mobiPath + '_toclean') saferRemove(mobiPath + '_toclean')
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath): if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath):
try: try:
move(mobiPath, GUI.targetDirectory) move(mobiPath, GUI.targetDirectory)
@@ -393,15 +396,15 @@ class WorkerThread(QtCore.QThread):
for item in outputPath: for item in outputPath:
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle( comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
k, comic2ebook.options.covers[outputPath.index(item)][1]) k, comic2ebook.options.covers[outputPath.index(item)][1])
MW.addMessage.emit('Kindle detected. Uploading covers...', 'info', False) MW.addMessage.emit('Kindle detected. Uploading covers... <b>Done!</b>', 'info', False)
else: else:
GUI.progress.content = '' GUI.progress.content = ''
for item in outputPath: for item in outputPath:
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
if os.path.exists(mobiPath): if os.path.exists(mobiPath):
os.remove(mobiPath) saferRemove(mobiPath)
if os.path.exists(mobiPath + '_toclean'): if os.path.exists(mobiPath + '_toclean'):
os.remove(mobiPath + '_toclean') saferRemove(mobiPath + '_toclean')
MW.addMessage.emit('Failed to process MOBI file!', 'error', False) MW.addMessage.emit('Failed to process MOBI file!', 'error', False)
MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical') MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
else: else:
@@ -409,9 +412,9 @@ class WorkerThread(QtCore.QThread):
epubSize = (os.path.getsize(self.kindlegenErrorCode[2])) // 1024 // 1024 epubSize = (os.path.getsize(self.kindlegenErrorCode[2])) // 1024 // 1024
for item in outputPath: for item in outputPath:
if os.path.exists(item): if os.path.exists(item):
os.remove(item) saferRemove(item)
if os.path.exists(item.replace('.epub', '.mobi')): if os.path.exists(item.replace('.epub', '.mobi')):
os.remove(item.replace('.epub', '.mobi')) saferRemove(item.replace('.epub', '.mobi'))
MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False) MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False)
MW.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical') MW.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical')
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '': if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
@@ -528,7 +531,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def clearJobs(self): def clearJobs(self):
GUI.jobList.clear() GUI.jobList.clear()
# noinspection PyCallByClass,PyTypeChecker,PyArgumentList # noinspection PyCallByClass,PyTypeChecker
def openWiki(self): def openWiki(self):
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki')) QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
@@ -603,7 +606,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.upscaleBox.setEnabled(False) GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True) GUI.upscaleBox.setChecked(True)
else: else:
GUI.qualityBox.setEnabled(True) profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if profile['PVOptions']:
GUI.qualityBox.setEnabled(True)
GUI.mangaBox.setEnabled(True) GUI.mangaBox.setEnabled(True)
GUI.rotateBox.setEnabled(True) GUI.rotateBox.setEnabled(True)
GUI.upscaleBox.setEnabled(True) GUI.upscaleBox.setEnabled(True)
@@ -629,9 +634,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.changeFormat() self.changeFormat()
GUI.gammaSlider.setValue(0) GUI.gammaSlider.setValue(0)
self.changeGamma(0) self.changeGamma(0)
GUI.qualityBox.setEnabled(profile['Quality']) GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale']) GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
if not profile['Quality']: if not profile['PVOptions']:
GUI.qualityBox.setChecked(False) GUI.qualityBox.setChecked(False)
if str(GUI.deviceBox.currentText()) == 'Other': if str(GUI.deviceBox.currentText()) == 'Other':
self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">' self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">'
@@ -643,7 +648,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.formatBox.setCurrentIndex(outputFormat) GUI.formatBox.setCurrentIndex(outputFormat)
else: else:
GUI.formatBox.setCurrentIndex(profile['DefaultFormat']) GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
GUI.qualityBox.setEnabled(profile['Quality']) GUI.qualityBox.setEnabled(profile['PVOptions'])
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
GUI.outputSplit.setEnabled(True)
else:
GUI.outputSplit.setEnabled(False)
GUI.outputSplit.setChecked(False)
def stripTags(self, html): def stripTags(self, html):
s = HTMLStripper() s = HTMLStripper()
@@ -698,7 +708,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.conversionAlive = False self.conversionAlive = False
self.worker.sync() self.worker.sync()
else: else:
# noinspection PyArgumentList
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier: if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath) dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
if dname != '': if dname != '':
@@ -760,7 +769,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
'upscaleBox': GUI.upscaleBox.checkState(), 'upscaleBox': GUI.upscaleBox.checkState(),
'borderBox': GUI.borderBox.checkState(), 'borderBox': GUI.borderBox.checkState(),
'webtoonBox': GUI.webtoonBox.checkState(), 'webtoonBox': GUI.webtoonBox.checkState(),
'noDitheringBox': GUI.noDitheringBox.checkState(), 'outputSplit': GUI.outputSplit.checkState(),
'colorBox': GUI.colorBox.checkState(), 'colorBox': GUI.colorBox.checkState(),
'widthBox': GUI.widthBox.value(), 'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(), 'heightBox': GUI.heightBox.value(),
@@ -846,7 +855,6 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
else: else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error') self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
# noinspection PyArgumentList
def __init__(self, KCCAplication, KCCWindow): def __init__(self, KCCAplication, KCCWindow):
global APP, MW, GUI global APP, MW, GUI
APP = KCCAplication APP = KCCAplication
@@ -894,39 +902,39 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
MW.resize(500, 500) MW.resize(500, 500)
self.profiles = { self.profiles = {
"Kindle Oasis": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 3": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'}, 'DefaultUpscale': True, 'Label': 'KV'},
"Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'}, 'DefaultUpscale': False, 'Label': 'KPW'},
"Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K45'}, 'DefaultUpscale': False, 'Label': 'K45'},
"Kindle DX/DXG": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 2, "Kindle DX/DXG": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 2,
'DefaultUpscale': False, 'Label': 'KDX'}, 'DefaultUpscale': False, 'Label': 'KDX'},
"Kobo Mini/Touch": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoMT'}, 'DefaultUpscale': False, 'Label': 'KoMT'},
"Kobo Glo": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Glo": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoG'}, 'DefaultUpscale': False, 'Label': 'KoG'},
"Kobo Glo HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Glo HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoGHD'}, 'DefaultUpscale': False, 'Label': 'KoGHD'},
"Kobo Aura": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoA'}, 'DefaultUpscale': False, 'Label': 'KoA'},
"Kobo Aura HD": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura HD": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAHD'}, 'DefaultUpscale': True, 'Label': 'KoAHD'},
"Kobo Aura H2O": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura H2O": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAH2O'}, 'DefaultUpscale': True, 'Label': 'KoAH2O'},
"Kobo Aura ONE": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1, "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': True, 'Label': 'KoAO'}, 'DefaultUpscale': True, 'Label': 'KoAO'},
"Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1, "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'OTHER'}, 'DefaultUpscale': False, 'Label': 'OTHER'},
"Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K1'}, 'DefaultUpscale': False, 'Label': 'K1'},
"Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K2'}, 'DefaultUpscale': False, 'Label': 'K2'},
"Kindle 3": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0, "Kindle 3": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K3'}, 'DefaultUpscale': False, 'Label': 'K3'},
} }
profilesGUI = [ profilesGUI = [

View File

@@ -97,9 +97,9 @@ class Ui_mainWindow(object):
self.borderBox.setTristate(True) self.borderBox.setTristate(True)
self.borderBox.setObjectName("borderBox") self.borderBox.setObjectName("borderBox")
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1) self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
self.noDitheringBox = QtWidgets.QCheckBox(self.optionWidget) self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
self.noDitheringBox.setObjectName("noDitheringBox") self.outputSplit.setObjectName("outputSplit")
self.gridLayout_2.addWidget(self.noDitheringBox, 2, 1, 1, 1) self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
self.colorBox = QtWidgets.QCheckBox(self.optionWidget) self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
self.colorBox.setObjectName("colorBox") self.colorBox.setObjectName("colorBox")
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1) self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
@@ -219,8 +219,8 @@ class Ui_mainWindow(object):
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox) mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox) mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
mainWindow.setTabOrder(self.gammaBox, self.borderBox) mainWindow.setTabOrder(self.gammaBox, self.borderBox)
mainWindow.setTabOrder(self.borderBox, self.noDitheringBox) mainWindow.setTabOrder(self.borderBox, self.outputSplit)
mainWindow.setTabOrder(self.noDitheringBox, self.colorBox) mainWindow.setTabOrder(self.outputSplit, self.colorBox)
mainWindow.setTabOrder(self.colorBox, self.editorButton) mainWindow.setTabOrder(self.colorBox, self.editorButton)
mainWindow.setTabOrder(self.editorButton, self.wikiButton) mainWindow.setTabOrder(self.editorButton, self.wikiButton)
mainWindow.setTabOrder(self.wikiButton, self.jobList) mainWindow.setTabOrder(self.wikiButton, self.jobList)
@@ -241,8 +241,8 @@ class Ui_mainWindow(object):
self.mangaBox.setText(_translate("mainWindow", "Manga mode")) self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>")) self.rotateBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Split<br/></span>Double page spreads will be cut into two separate pages.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - Rotate and split<br/></span>Double page spreads will be displayed twice. First rotated and then split. </p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Rotate<br/></span>Double page spreads will be rotated.</p></body></html>"))
self.rotateBox.setText(_translate("mainWindow", "Spread splitter")) self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>High quality Panel View.<br/>Require source files with bigger resolution than target device.<br/><span style=\" font-weight:600;\">Highly impact size of output file!</span></p></body></html>")) self.qualityBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - 4 panels<br/></span>Zoom each corner separately.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Checked - 2 panels<br/></span>Zoom only the top and bottom of the page.</p></body></html>"))
self.qualityBox.setText(_translate("mainWindow", "HQ zoom")) self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2"))
self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>")) self.webtoonBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Enable special parsing mode for Korean Webtoons.</p></body></html>"))
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode")) self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
self.upscaleBox.setToolTip(_translate("mainWindow", "<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>")) self.upscaleBox.setToolTip(_translate("mainWindow", "<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>"))
@@ -251,8 +251,8 @@ class Ui_mainWindow(object):
self.gammaBox.setText(_translate("mainWindow", "Custom gamma")) self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>")) self.borderBox.setToolTip(_translate("mainWindow", "<html><head/><body><p><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Autodetection<br/></span>Color of margins fill will be detected automatically.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Indeterminate - White<br/></span>Margins will be filled with white color.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - Black<br/></span>Margins will be filled with black color.</p></body></html>"))
self.borderBox.setText(_translate("mainWindow", "W/B margins")) self.borderBox.setText(_translate("mainWindow", "W/B margins"))
self.noDitheringBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Create PNG files instead JPEG.<br/>Quality increase is not noticeable on most of devices.<br/>Output files <span style=\" font-weight:600;\">might</span> be smaller.<br/><span style=\" font-weight:600;\">MOBI conversion will be much slower.</span></p></body></html>")) self.outputSplit.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Unchecked - Automatic mode<br/></span>Output will be splitted automatically.</p><p style=\'white-space:pre\'><span style=\" font-weight:600; text-decoration: underline;\">Checked - Volume mode<br/></span>Every subdirectory will be considered as separate volume.</p></body></html>"))
self.noDitheringBox.setText(_translate("mainWindow", "PNG output")) self.outputSplit.setText(_translate("mainWindow", "Output split"))
self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>")) self.colorBox.setToolTip(_translate("mainWindow", "<html><head/><body><p style=\'white-space:pre\'>Disable conversion to grayscale.</p></body></html>"))
self.colorBox.setText(_translate("mainWindow", "Color mode")) self.colorBox.setText(_translate("mainWindow", "Color mode"))
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto")) self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))

View File

@@ -1,4 +1,4 @@
__version__ = '5.1.3' __version__ = '5.2.1'
__license__ = 'ISC' __license__ = 'ISC'
__copyright__ = '2012-2016, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __copyright__ = '2012-2016, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>'
__docformat__ = 'restructuredtext en' __docformat__ = 'restructuredtext en'

View File

@@ -27,7 +27,7 @@ try:
except ImportError: except ImportError:
walk = os.walk walk = os.walk
from . import rarfile from . import rarfile
from .shared import check7ZFile as is_7zfile, saferReplace from .shared import check7ZFile as is_7zfile, saferReplace, saferRemove
class CBxArchive: class CBxArchive:
@@ -66,7 +66,7 @@ class CBxArchive:
for root, dirnames, filenames in walk(targetdir): for root, dirnames, filenames in walk(targetdir):
for filename in filenames: for filename in filenames:
if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'): if filename.startswith('__MACOSX') or filename.endswith('.DS_Store') or filename.endswith('humbs.db'):
os.remove(os.path.join(root, filename)) saferRemove(os.path.join(root, filename))
def extractCB7(self, targetdir): def extractCB7(self, targetdir):
# Workaround for some wide UTF-8 + Popen abnormalities # Workaround for some wide UTF-8 + Popen abnormalities
@@ -80,7 +80,7 @@ class CBxArchive:
if b"Everything is Ok" in line: if b"Everything is Ok" in line:
extracted = True extracted = True
if sys.platform.startswith('darwin'): if sys.platform.startswith('darwin'):
os.remove(self.origFileName) saferRemove(self.origFileName)
if not extracted: if not extracted:
raise OSError raise OSError

View File

@@ -46,7 +46,7 @@ try:
from scandir import walk from scandir import walk
except ImportError: except ImportError:
walk = os.walk walk = os.walk
from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace, saferRemove from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, saferReplace, saferRemove, sanitizeTrace
from . import comic2panel from . import comic2panel
from . import image from . import image
from . import cbxarchive from . import cbxarchive
@@ -93,10 +93,7 @@ def buildHTML(path, imgfile, imgfilepath):
additionalStyle = 'background-color:#000000;' additionalStyle = 'background-color:#000000;'
else: else:
additionalStyle = 'background-color:#FFFFFF;' additionalStyle = 'background-color:#FFFFFF;'
htmlpath = ''
postfix = '' postfix = ''
size = ''
imgfilepv = ''
backref = 1 backref = 1
head = path head = path
while True: while True:
@@ -122,21 +119,16 @@ def buildHTML(path, imgfile, imgfilepath):
"<body style=\"background-image: ", "<body style=\"background-image: ",
"url('", "../" * backref, "Images/", postfix, imgfile, "'); " + additionalStyle + "\">\n"]) "url('", "../" * backref, "Images/", postfix, imgfile, "'); " + additionalStyle + "\">\n"])
if options.iskindle and options.panelview: if options.iskindle and options.panelview:
if options.hqmode: sizeTmp = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
imgfilepv = list(os.path.splitext(imgfile)) if options.autoscale:
imgfilepv[0] += "-hq" size = (getPanelViewResolution(sizeTmp, deviceres))
imgfilepv = "".join(imgfilepv) else:
if os.path.isfile(os.path.join(head, "Images", postfix, imgfilepv)):
size = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size
if not options.hqmode or not size:
imgfilepv = imgfile
sizeTmp = Image.open(os.path.join(head, "Images", postfix, imgfilepv)).size
size = (int(sizeTmp[0] * 1.5), int(sizeTmp[1] * 1.5)) size = (int(sizeTmp[0] * 1.5), int(sizeTmp[1] * 1.5))
if size[0] <= deviceres[0]: if size[0] - deviceres[0] < deviceres[0] * 0.01:
noHorizontalPV = True noHorizontalPV = True
else: else:
noHorizontalPV = False noHorizontalPV = False
if size[1] <= deviceres[1]: if size[1] - deviceres[1] < deviceres[1] * 0.01:
noVerticalPV = True noVerticalPV = True
else: else:
noVerticalPV = False noVerticalPV = False
@@ -193,7 +185,7 @@ def buildHTML(path, imgfile, imgfilepath):
for box in boxes: for box in boxes:
f.writelines(["<div class=\"PV-P\" id=\"" + box + "-P\" style=\"" + additionalStyle + "\">\n", f.writelines(["<div class=\"PV-P\" id=\"" + box + "-P\" style=\"" + additionalStyle + "\">\n",
"<img style=\"" + boxStyles[box] + "\" src=\"", "../" * backref, "Images/", postfix, "<img style=\"" + boxStyles[box] + "\" src=\"", "../" * backref, "Images/", postfix,
imgfilepv, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n", imgfile, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n",
"</div>\n"]) "</div>\n"])
f.writelines(["</body>\n", f.writelines(["</body>\n",
"</html>\n"]) "</html>\n"])
@@ -336,29 +328,6 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"rtl\" toc=\"ncx\">\n")
else: else:
f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n") f.write("</manifest>\n<spine page-progression-direction=\"ltr\" toc=\"ncx\">\n")
# if options.iskindle and options.profile != 'Custom':
# if options.righttoleft:
# nextflow = 'right'
# else:
# nextflow = 'left'
# for entry in reflist:
# if '-kcc-b' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# elif '-kcc-c' in entry:
# if options.righttoleft:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-left\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"page-spread-right\"/>\n")
# else:
# f.write("<itemref idref=\"page_" + entry + "\" properties=\"facing-page-" + nextflow + "\"/>\n")
# if nextflow == 'right':
# nextflow = 'left'
# else:
# nextflow = 'right'
# else:
for entry in reflist: for entry in reflist:
f.write("<itemref idref=\"page_" + entry + "\"/>\n") f.write("<itemref idref=\"page_" + entry + "\"/>\n")
f.write("</spine>\n</package>\n") f.write("</spine>\n</package>\n")
@@ -460,17 +429,15 @@ def buildEPUB(path, chapterNames, tomeNumber):
chapter = False chapter = False
dirnames, filenames = walkSort(dirnames, filenames) dirnames, filenames = walkSort(dirnames, filenames)
for afile in filenames: for afile in filenames:
filename = getImageFileName(afile) filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
if not filename[0].endswith('-hq'): if not chapter:
filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile))) chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
if not chapter: chapter = True
chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1])) if cover is None:
chapter = True cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
if cover is None: 'cover' + getImageFileName(filelist[-1][1])[1])
cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'), options.covers.append((image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options,
'cover' + getImageFileName(filelist[-1][1])[1]) tomeNumber), options.uuid))
options.covers.append((image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options,
tomeNumber), options.uuid))
# Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks # Overwrite chapternames if tree is flat and ComicInfo.xml has bookmarks
if not chapterNames and options.chapters: if not chapterNames and options.chapters:
chapterlist = [] chapterlist = []
@@ -515,7 +482,7 @@ def imgDirectoryProcessing(path):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(workerOutput) > 0: if len(workerOutput) > 0:
rmtree(os.path.join(path, '..', '..'), True) rmtree(os.path.join(path, '..', '..'), True)
raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0][0], workerOutput[0][1])
for file in options.imgPurgeIndex: for file in options.imgPurgeIndex:
if os.path.isfile(file): if os.path.isfile(file):
saferRemove(file) saferRemove(file)
@@ -525,7 +492,7 @@ def imgDirectoryProcessing(path):
def imgFileProcessingTick(output): def imgFileProcessingTick(output):
if isinstance(output, str): if isinstance(output, tuple):
workerOutput.append(output) workerOutput.append(output)
workerPool.terminate() workerPool.terminate()
else: else:
@@ -548,10 +515,10 @@ def imgFileProcessing(work):
workImg = image.ComicPageParser((dirpath, afile), opt) workImg = image.ComicPageParser((dirpath, afile), opt)
for i in workImg.payload: for i in workImg.payload:
img = image.ComicPage(i[0], i[1], i[2], i[3], i[4], opt) img = image.ComicPage(i[0], i[1], i[2], i[3], i[4], opt)
if opt.cropping > 0 and not opt.webtoon:
img.cropWhiteSpace(opt.croppingp)
if opt.cropping == 2 and not opt.webtoon: if opt.cropping == 2 and not opt.webtoon:
img.cutPageNumber(opt.croppingpn) img.cropPageNumber(opt.croppingp)
if opt.cropping > 0 and not opt.webtoon:
img.cropMargin(opt.croppingp)
img.autocontrastImage() img.autocontrastImage()
img.resizeImage() img.resizeImage()
if opt.forcepng and not opt.forcecolor: if opt.forcepng and not opt.forcecolor:
@@ -559,7 +526,7 @@ def imgFileProcessing(work):
output.append(img.saveToDir()) output.append(img.saveToDir())
return output return output
except Exception: except Exception:
return str(sys.exc_info()[:2]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def getWorkFolder(afile): def getWorkFolder(afile):
@@ -653,7 +620,7 @@ def getComicInfo(path, originalPath):
try: try:
xml = metadata.MetadataParser(xmlPath) xml = metadata.MetadataParser(xmlPath)
except Exception: except Exception:
os.remove(xmlPath) saferRemove(xmlPath)
return return
options.authors = [] options.authors = []
if defaultTitle: if defaultTitle:
@@ -678,7 +645,7 @@ def getComicInfo(path, originalPath):
options.chapters = xml.data['Bookmarks'] options.chapters = xml.data['Bookmarks']
if xml.data['Summary']: if xml.data['Summary']:
options.summary = escape(xml.data['Summary']) options.summary = escape(xml.data['Summary'])
os.remove(xmlPath) saferRemove(xmlPath)
def getCoversFromMCB(mangaID): def getCoversFromMCB(mangaID):
@@ -704,6 +671,11 @@ def getDirectorySize(start_path='.'):
return total_size return total_size
def getPanelViewResolution(imageSize, deviceRes):
scale = float(deviceRes[0]) / float(imageSize[0])
return int(deviceRes[0]), int(scale * imageSize[1])
def getPanelViewSize(deviceres, size): def getPanelViewSize(deviceres, size):
x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100 x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100
y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100 y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100
@@ -761,63 +733,25 @@ def sanitizePermissions(filetree):
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC) os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
# noinspection PyUnboundLocalVariable
def splitDirectory(path): def splitDirectory(path):
# Detect directory stucture level = -1
for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0): for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
subdirectoryNumber = len(dirs) for f in files:
filesNumber = len(files) if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif'):
if subdirectoryNumber == 0: newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
# No subdirectories if level != -1 and level != newLevel:
mode = 0 level = 0
break
else:
level = newLevel
if level > 0:
splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), level)
path = [path]
for tome in splitter:
path.append(tome)
return path
else: else:
if filesNumber > 0: raise UserWarning('Unsupported directory structure.')
print('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
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('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
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('WARNING: Automatic output splitting failed.')
if GUI:
GUI.addMessage.emit('Automatic output splitting failed. <a href='
'"https://github.com/ciromattia/kcc/wiki'
'/Automatic-output-splitting">'
'More details.</a>', 'warning', False)
GUI.addMessage.emit('', '', False)
return [path]
# Split directories
splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), mode)
path = [path]
for tome in splitter:
path.append(tome)
return path
def splitProcess(path, mode): def splitProcess(path, mode):
@@ -828,10 +762,15 @@ def splitProcess(path, mode):
targetSize = 104857600 targetSize = 104857600
else: else:
targetSize = 419430400 targetSize = 419430400
if mode == 0: if options.batchsplit == 2 and mode == 2:
mode = 3
if mode < 3:
for root, dirs, files in walkLevel(path, 0): for root, dirs, files in walkLevel(path, 0):
for name in files: for name in files if mode == 1 else dirs:
size = os.path.getsize(os.path.join(root, name)) if mode == 1:
size = os.path.getsize(os.path.join(root, name))
else:
size = getDirectorySize(os.path.join(root, name))
if currentSize + size > targetSize: if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome() currentTarget, pathRoot = createNewTome()
output.append(pathRoot) output.append(pathRoot)
@@ -840,48 +779,16 @@ def splitProcess(path, mode):
currentSize += size currentSize += size
if path != currentTarget: if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name)) move(os.path.join(root, name), os.path.join(currentTarget, name))
elif mode == 1: else:
for root, dirs, files in walkLevel(path, 0):
for name in dirs:
size = getDirectorySize(os.path.join(root, name))
if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome()
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 firstTome = True
for root, dirs, files in walkLevel(path, 0): for root, dirs, files in walkLevel(path, 0):
for name in dirs: for name in dirs:
size = getDirectorySize(os.path.join(root, name)) if not firstTome:
currentSize = 0 currentTarget, pathRoot = createNewTome()
if size > targetSize: output.append(pathRoot)
if not firstTome: move(os.path.join(root, name), os.path.join(currentTarget, name))
currentTarget, pathRoot = createNewTome()
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 > targetSize:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
currentSize = size
else:
currentSize += size
if path != currentTarget:
move(os.path.join(rootInside, nameInside), os.path.join(currentTarget, nameInside))
else: else:
if not firstTome: firstTome = False
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
move(os.path.join(root, name), os.path.join(currentTarget, name))
else:
firstTome = False
return output return output
@@ -963,6 +870,8 @@ def makeParser():
" KoA, KoAHD, KoAH2O, KoAO) [Default=KV]") " KoA, KoAHD, KoAH2O, KoAO) [Default=KV]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False, mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
help="Manga style (right-to-left reading and splitting)") help="Manga style (right-to-left reading and splitting)")
mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
help="Display two not four panels in Panel View mode")
mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False, mainOptions.add_option("-w", "--webtoon", action="store_true", dest="webtoon", default=False,
help="Webtoon processing mode"), help="Webtoon processing mode"),
@@ -972,8 +881,9 @@ def makeParser():
help="Comic title [Default=filename or directory name]") help="Comic title [Default=filename or directory name]")
outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto", outputOptions.add_option("-f", "--format", action="store", dest="format", default="Auto",
help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]") help="Output format (Available options: Auto, MOBI, EPUB, CBZ) [Default=Auto]")
outputOptions.add_option("-b", "--batchsplit", action="store_true", dest="batchsplit", default=False, outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0",
help="Split output into multiple files"), help="Split output into multiple files. 0: Don't split 1: Automatic mode "
"2: Consider every subdirectory as separate volume [Default=0]")
processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False, processingOptions.add_option("-u", "--upscale", action="store_true", dest="upscale", default=False,
help="Resize images smaller than device's resolution") help="Resize images smaller than device's resolution")
@@ -983,8 +893,10 @@ def makeParser():
help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]") help="Double page parsing mode. 0: Split 1: Rotate 2: Both [Default=0]")
processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0", processingOptions.add_option("-g", "--gamma", type="float", dest="gamma", default="0.0",
help="Apply gamma correction to linearize the image [Default=Auto]") help="Apply gamma correction to linearize the image [Default=Auto]")
processingOptions.add_option("--hq", action="store_true", dest="hqmode", default=False, processingOptions.add_option("-c", "--cropping", type="int", dest="cropping", default="2",
help="Enable high quality Panel View") help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--cp", "--croppingpower", type="float", dest="croppingp", default="1.0",
help="Set cropping power [Default=1.0]")
processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False, processingOptions.add_option("--blackborders", action="store_true", dest="black_borders", default=False,
help="Disable autodetection and force black borders") help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False, processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
@@ -993,12 +905,6 @@ def makeParser():
help="Don't convert images to grayscale") help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False, processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
help="Create PNG files instead JPEG") help="Create PNG files instead JPEG")
processingOptions.add_option("--cropping", type="int", dest="cropping", default="2",
help="Set cropping mode. 0: Disabled 1: Margins 2: Margins + page numbers [Default=2]")
processingOptions.add_option("--croppingpower", type="float", dest="croppingp", default="0.1",
help="Set margin cropping threshold [Default=0.1]")
processingOptions.add_option("--croppingpowerpage", type="float", dest="croppingpn", default="5.0",
help="Set page number cropping threshold [Default=5.0]")
customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0, customProfileOptions.add_option("--customwidth", type="int", dest="customwidth", default=0,
help="Replace screen width provided by device profile") help="Replace screen width provided by device profile")
@@ -1035,25 +941,21 @@ def checkOptions():
if options.black_borders: if options.black_borders:
options.bordersColor = 'black' options.bordersColor = 'black'
# Splitting MOBI is not optional # Splitting MOBI is not optional
if options.format == 'MOBI': if options.format == 'MOBI' and options.batchsplit != 2:
options.batchsplit = True options.batchsplit = 1
# Older Kindle don't need higher resolution files due lack of Panel View. # Older Kindle don't need higher resolution files due lack of Panel View.
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K3' or options.profile == 'KDX': if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'K3' or options.profile == 'KDX':
options.panelview = False options.panelview = False
options.hqmode = False
# Webtoon mode mandatory options # Webtoon mode mandatory options
if options.webtoon: if options.webtoon:
options.panelview = False options.panelview = False
options.hqmode = False
options.righttoleft = False options.righttoleft = False
options.upscale = True options.upscale = True
# Disable all Kindle features for other e-readers # Disable all Kindle features for other e-readers
if options.profile == 'OTHER': if options.profile == 'OTHER':
options.panelview = False options.panelview = False
options.hqmode = False
if 'Ko' in options.profile: if 'Ko' in options.profile:
options.panelview = False options.panelview = False
options.hqmode = False
# CBZ files on Kindle DX/DXG support higher resolution # CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ': if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200 options.customheight = 1200
@@ -1066,7 +968,7 @@ def checkOptions():
if options.customheight != 0: if options.customheight != 0:
Y = options.customheight Y = options.customheight
newProfile = ("Custom", (int(X), int(Y)), image.ProfileData.Palette16, newProfile = ("Custom", (int(X), int(Y)), image.ProfileData.Palette16,
image.ProfileData.Profiles[options.profile][3], (int(int(X) * 1.5), int(int(Y) * 1.5))) image.ProfileData.Profiles[options.profile][3])
image.ProfileData.Profiles["Custom"] = newProfile image.ProfileData.Profiles["Custom"] = newProfile
options.profile = "Custom" options.profile = "Custom"
options.profileData = image.ProfileData.Profiles[options.profile] options.profileData = image.ProfileData.Profiles[options.profile]
@@ -1125,8 +1027,8 @@ def makeBook(source, qtGUI=None):
getComicInfo(os.path.join(path, "OEBPS", "Images"), source) getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
detectCorruption(os.path.join(path, "OEBPS", "Images"), source) detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
if options.webtoon: if options.webtoon:
if image.ProfileData.Profiles[options.profile][1][1] > 1000: if image.ProfileData.Profiles[options.profile][1][1] > 1024:
y = 1000 y = 1024
else: else:
y = image.ProfileData.Profiles[options.profile][1][1] y = image.ProfileData.Profiles[options.profile][1][1]
comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI) comic2panel.main(['-y ' + str(y), '-i', '-m', path], qtGUI)
@@ -1139,7 +1041,7 @@ def makeBook(source, qtGUI=None):
chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images'))
if 'Ko' in options.profile and options.format == 'CBZ': if 'Ko' in options.profile and options.format == 'CBZ':
sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images')) sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images'))
if options.batchsplit: if options.batchsplit > 0:
tomes = splitDirectory(path) tomes = splitDirectory(path)
else: else:
tomes = [path] tomes = [path]
@@ -1184,7 +1086,6 @@ def makeBook(source, qtGUI=None):
if not GUI and options.format == 'MOBI': if not GUI and options.format == 'MOBI':
print("Creating MOBI files...") print("Creating MOBI files...")
work = [] work = []
k = kindle.Kindle()
for i in filepath: for i in filepath:
work.append([i]) work.append([i])
output = makeMOBI(work, GUI) output = makeMOBI(work, GUI)
@@ -1193,6 +1094,7 @@ def makeBook(source, qtGUI=None):
print('Error: KindleGen failed to create MOBI!') print('Error: KindleGen failed to create MOBI!')
print(errors) print(errors)
return filepath return filepath
k = kindle.Kindle()
if k.path and k.coverSupport: if k.path and k.coverSupport:
print("Kindle detected. Uploading covers...") print("Kindle detected. Uploading covers...")
for i in filepath: for i in filepath:
@@ -1201,14 +1103,14 @@ def makeBook(source, qtGUI=None):
print('Error: Failed to tweak KindleGen output!') print('Error: Failed to tweak KindleGen output!')
return filepath return filepath
else: else:
os.remove(i.replace('.epub', '.mobi') + '_toclean') saferRemove(i.replace('.epub', '.mobi') + '_toclean')
if k.path and k.coverSupport: if k.path and k.coverSupport:
options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1]) options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1])
return filepath return filepath
def makeMOBIFix(item, uuid): def makeMOBIFix(item, uuid):
os.remove(item) saferRemove(item)
mobiPath = item.replace('.epub', '.mobi') mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean') move(mobiPath, mobiPath + '_toclean')
try: try:

View File

@@ -24,7 +24,7 @@ from shutil import rmtree, copytree, move
from optparse import OptionParser, OptionGroup from optparse import OptionParser, OptionGroup
from multiprocessing import Pool from multiprocessing import Pool
from PIL import Image, ImageStat, ImageOps from PIL import Image, ImageStat, ImageOps
from .shared import getImageFileName, walkLevel, walkSort from .shared import getImageFileName, walkLevel, walkSort, saferRemove, sanitizeTrace
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
except ImportError: except ImportError:
@@ -77,11 +77,11 @@ def mergeDirectory(work):
img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5)) img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5))
result.paste(img, (0, y)) result.paste(img, (0, y))
y += img.size[1] y += img.size[1]
os.remove(i) saferRemove(i)
savePath = os.path.split(imagesValid[0]) savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG') result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
except Exception: except Exception:
return str(sys.exc_info()[1]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def sanitizePanelSize(panel, opt): def sanitizePanelSize(panel, opt):
@@ -203,9 +203,9 @@ def splitImage(work):
targetHeight += panels[panel][2] targetHeight += panels[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG') newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG')
pageNumber += 1 pageNumber += 1
os.remove(filePath) saferRemove(filePath)
except Exception: except Exception:
return str(sys.exc_info()[1]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def main(argv=None, qtGUI=None): def main(argv=None, qtGUI=None):
@@ -267,7 +267,7 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(mergeWorkerOutput) > 0: if len(mergeWorkerOutput) > 0:
rmtree(options.targetDir, True) rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0], mergeWorkerOutput[0][1])
print("Splitting images...") print("Splitting images...")
for root, dirs, files in walk(options.targetDir, False): for root, dirs, files in walk(options.targetDir, False):
for name in files: for name in files:
@@ -275,7 +275,7 @@ def main(argv=None, qtGUI=None):
pagenumber += 1 pagenumber += 1
work.append([root, name, options]) work.append([root, name, options])
else: else:
os.remove(os.path.join(root, name)) saferRemove(os.path.join(root, name))
if GUI: if GUI:
GUI.progressBarTick.emit('Splitting images') GUI.progressBarTick.emit('Splitting images')
GUI.progressBarTick.emit(str(pagenumber)) GUI.progressBarTick.emit(str(pagenumber))
@@ -290,7 +290,7 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.") raise UserWarning("Conversion interrupted.")
if len(splitWorkerOutput) > 0: if len(splitWorkerOutput) > 0:
rmtree(options.targetDir, True) rmtree(options.targetDir, True)
raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0]) raise RuntimeError("One of workers crashed. Cause: " + splitWorkerOutput[0][0], splitWorkerOutput[0][1])
if options.inPlace: if options.inPlace:
rmtree(options.sourceDir) rmtree(options.sourceDir)
move(options.targetDir, options.sourceDir) move(options.targetDir, options.sourceDir)

View File

@@ -1,5 +1,6 @@
# Copyright (C) 2010 Alex Yatskov # Copyright (C) 2010 Alex Yatskov
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com> # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov <prodoomman@gmail.com>
# Copyright (c) 2016 Alberto Planas <aplanas@gmail.com>
# Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com> # Copyright (c) 2012-2014 Ciro Mattia Gonano <ciromattia@gmail.com>
# Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re> # Copyright (c) 2013-2016 Pawel Jastrzebski <pawelj@iosphe.re>
# #
@@ -20,7 +21,7 @@ import os
from io import BytesIO from io import BytesIO
from urllib.request import Request, urlopen from urllib.request import Request, urlopen
from urllib.parse import quote from urllib.parse import quote
from PIL import Image, ImageOps, ImageStat, ImageChops from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter
from .shared import md5Checksum from .shared import md5Checksum
from . import __version__ from . import __version__
@@ -77,21 +78,21 @@ class ProfileData:
] ]
Profiles = { Profiles = {
'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)), 'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)), 'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
'K3': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K3': ("Kindle", (600, 800), Palette16, 1.8),
'K45': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)), 'K45': ("Kindle", (600, 800), Palette16, 1.8),
'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)), 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8, (1608, 2172)), 'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)), 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)), 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)), 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)), 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)), 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)), 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8, (2106, 2808)), 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)), 'OTHER': ("Other", (0, 0), Palette16, 1.8),
} }
@@ -105,8 +106,6 @@ class ComicPageParser:
self.color = self.colorCheck() self.color = self.colorCheck()
self.fill = self.fillCheck() self.fill = self.fillCheck()
self.splitCheck() self.splitCheck()
if self.opt.hqmode:
self.sizeCheck()
def getImageHistogram(self, image): def getImageHistogram(self, image):
histogram = image.histogram() histogram = image.histogram()
@@ -205,29 +204,16 @@ class ComicPageParser:
else: else:
return 'white' return 'white'
def sizeCheck(self):
additionalPayload = []
width, height = self.image.size
dstwidth, dstheight = self.size
for work in self.payload:
if width > dstwidth and height > dstheight:
additionalPayload.append([work[0] + '+', work[1], work[2].copy(), work[3], work[4]])
self.payload = self.payload + additionalPayload
class ComicPage: class ComicPage:
def __init__(self, mode, path, image, color, fill, options): def __init__(self, mode, path, image, color, fill, options):
self.opt = options self.opt = options
_, self.size, self.palette, self.gamma, self.panelviewsize = self.opt.profileData _, self.size, self.palette, self.gamma = self.opt.profileData
self.image = image self.image = image
self.color = color self.color = color
self.fill = fill self.fill = fill
self.rotated = False self.rotated = False
self.orgPath = os.path.join(path[0], path[1]) self.orgPath = os.path.join(path[0], path[1])
if '+' in mode:
self.hqMode = True
else:
self.hqMode = False
if 'N' in mode: if 'N' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC' self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
elif 'R' in mode: elif 'R' in mode:
@@ -247,8 +233,6 @@ class ComicPage:
flags.append('Rotated') flags.append('Rotated')
if self.fill != 'white': if self.fill != 'white':
flags.append('BlackFill') flags.append('BlackFill')
if self.hqMode:
self.targetPath += '-HQ'
if self.opt.forcepng: if self.opt.forcepng:
self.targetPath += '.png' self.targetPath += '.png'
self.image.save(self.targetPath, 'PNG', optimize=1) self.image.save(self.targetPath, 'PNG', optimize=1)
@@ -282,128 +266,69 @@ class ComicPage:
self.image = self.image.quantize(palette=palImg) self.image = self.image.quantize(palette=palImg)
def resizeImage(self): def resizeImage(self):
if self.hqMode: if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
size = (self.panelviewsize[0], self.panelviewsize[1]) method = Image.BICUBIC
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.image.thumbnail(size, Image.LANCZOS)
else: else:
size = (self.size[0], self.size[1]) method = Image.LANCZOS
if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]: if self.opt.stretch:
method = Image.BICUBIC self.image = self.image.resize(self.size, method)
elif self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1] and not self.opt.upscale:
if self.opt.format == 'CBZ':
borderw = int((self.size[0] - self.image.size[0]) / 2)
borderh = int((self.size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
if self.image.size[0] != self.size[0] or self.image.size[1] != self.size[1]:
self.image = ImageOps.fit(self.image, self.size, method=Image.BICUBIC, centering=(0.5, 0.5))
else:
if self.opt.format == 'CBZ':
ratioDev = float(self.size[0]) / float(self.size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
self.image = ImageOps.fit(self.image, self.size, method=method, centering=(0.5, 0.5))
else: else:
method = Image.LANCZOS hpercent = self.size[1] / float(self.image.size[1])
if self.opt.stretch: wsize = int((float(self.image.size[0]) * float(hpercent)))
self.image = self.image.resize(size, method) self.image = self.image.resize((wsize, self.size[1]), method)
elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale: if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
if self.opt.format == 'CBZ': self.image.thumbnail(self.size, Image.LANCZOS)
borderw = int((size[0] - self.image.size[0]) / 2)
borderh = int((size[1] - self.image.size[1]) / 2)
self.image = ImageOps.expand(self.image, border=(borderw, borderh), fill=self.fill)
if self.image.size[0] != size[0] or self.image.size[1] != size[1]:
self.image = ImageOps.fit(self.image, size, method=Image.BICUBIC, centering=(0.5, 0.5))
else:
if self.opt.format == 'CBZ':
ratioDev = float(size[0]) / float(size[1])
if (float(self.image.size[0]) / float(self.image.size[1])) < ratioDev:
diff = int(self.image.size[1] * ratioDev) - self.image.size[0]
self.image = ImageOps.expand(self.image, border=(int(diff / 2), 0), fill=self.fill)
elif (float(self.image.size[0]) / float(self.image.size[1])) > ratioDev:
diff = int(self.image.size[0] / ratioDev) - self.image.size[1]
self.image = ImageOps.expand(self.image, border=(0, int(diff / 2)), fill=self.fill)
self.image = ImageOps.fit(self.image, size, method=method, centering=(0.5, 0.5))
else:
hpercent = size[1] / float(self.image.size[1])
wsize = int((float(self.image.size[0]) * float(hpercent)))
self.image = self.image.resize((wsize, size[1]), method)
if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
self.image.thumbnail(size, Image.LANCZOS)
def cutPageNumber(self, fixedThreshold): def getBoundingBox(self, tmpImg):
if ImageChops.invert(self.image).getbbox() is not None: min_margin = [int(0.005 * i + 0.5) for i in tmpImg.size]
widthImg, heightImg = self.image.size max_margin = [int(0.1 * i + 0.5) for i in tmpImg.size]
delta = 2 bbox = tmpImg.getbbox()
diff = delta bbox = (
if ImageStat.Stat(self.image).var[0] < 2 * fixedThreshold: max(0, min(max_margin[0], bbox[0] - min_margin[0])),
return self.image max(0, min(max_margin[1], bbox[1] - min_margin[1])),
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\ min(tmpImg.size[0],
and diff < heightImg: max(tmpImg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
diff += delta min(tmpImg.size[1],
diff -= delta max(tmpImg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
pageNumberCut1 = diff )
if diff < delta: return bbox
diff = delta
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
diff += delta
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] - oldStat > 0\
and diff < heightImg // 4:
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0]
diff += delta
diff -= delta
pageNumberCut2 = diff
diff += delta
oldStat = ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg,
heightImg - pageNumberCut2))).var[0]
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg - pageNumberCut2))).var[0]\
< fixedThreshold + oldStat and diff < heightImg // 4:
diff += delta
diff -= delta
pageNumberCut3 = diff
delta = 5
diff = delta
while ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut2, diff, heightImg))).var[0]\
< fixedThreshold and diff < widthImg:
diff += delta
diff -= delta
pageNumberX1 = diff
diff = delta
while ImageStat.Stat(self.image.crop((widthImg - diff, heightImg - pageNumberCut2,
widthImg, heightImg))).var[0] < fixedThreshold and diff < widthImg:
diff += delta
diff -= delta
pageNumberX2 = widthImg - diff
if pageNumberCut3 - pageNumberCut1 > 2 * delta\
and float(pageNumberX2 - pageNumberX1) / float(pageNumberCut2 - pageNumberCut1) <= 9.0\
and ImageStat.Stat(self.image.crop((0, heightImg - pageNumberCut3, widthImg, heightImg))).var[0]\
/ ImageStat.Stat(self.image).var[0] < 0.1\
and pageNumberCut3 < heightImg / 4 - delta:
diff = pageNumberCut3
else:
diff = pageNumberCut1
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
def cropWhiteSpace(self, fixedThreshold): def cropPageNumber(self, power):
if ImageChops.invert(self.image).getbbox() is not None: if self.fill != 'white':
widthImg, heightImg = self.image.size tmpImg = self.image.convert(mode='L')
delta = 10 else:
diff = delta tmpImg = ImageOps.invert(self.image.convert(mode='L'))
# top tmpImg = tmpImg.point(lambda x: x and 255)
while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < fixedThreshold and diff < heightImg: tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
diff += delta tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
diff -= delta tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop((0, diff, widthImg, heightImg)) self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
widthImg, heightImg = self.image.size
diff = delta def cropMargin(self, power):
# left if self.fill != 'white':
while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < fixedThreshold and diff < widthImg: tmpImg = self.image.convert(mode='L')
diff += delta else:
diff -= delta tmpImg = ImageOps.invert(self.image.convert(mode='L'))
self.image = self.image.crop((diff, 0, widthImg, heightImg)) tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=3))
widthImg, heightImg = self.image.size tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
diff = delta self.image = self.image.crop(self.getBoundingBox(tmpImg)) if tmpImg.getbbox() else self.image
# down
while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
and diff < heightImg:
diff += delta
diff -= delta
self.image = self.image.crop((0, 0, widthImg, heightImg - diff))
widthImg, heightImg = self.image.size
diff = delta
# right
while ImageStat.Stat(self.image.crop((widthImg - diff, 0, widthImg, heightImg))).var[0] < fixedThreshold\
and diff < widthImg:
diff += delta
diff -= delta
self.image = self.image.crop((0, 0, widthImg - diff, heightImg))
class Cover: class Cover:

View File

@@ -61,7 +61,6 @@ class PdfJpgExtract:
iend += endfix iend += endfix
jpg = pdf[istart:iend] jpg = pdf[istart:iend]
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb") jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
# noinspection PyTypeChecker
jpgfile.write(jpg) jpgfile.write(jpg)
jpgfile.close() jpgfile.close()
njpg += 1 njpg += 1

View File

@@ -144,8 +144,10 @@ def removeFromZIP(zipfname, *filenames):
def sanitizeTrace(traceback): def sanitizeTrace(traceback):
return ''.join(format_tb(traceback))\ return ''.join(format_tb(traceback))\
.replace('C:\\Users\\pawel\\Documents\\Projekty\\KCC\\', '') \ .replace('C:/Users/Pawel/Documents/Projekty/KCC/', '')\
.replace('C:\\Users\\Paweł\\Documents\\Projekty\\KCC\\', '') \ .replace('C:/Python35/', '')\
.replace('c:/python35/', '')\
.replace('C:\\Users\\Pawel\\Documents\\Projekty\\KCC\\', '')\
.replace('C:\\Python35\\', '')\ .replace('C:\\Python35\\', '')\
.replace('c:\\python35\\', '') .replace('c:\\python35\\', '')

View File

@@ -9,7 +9,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>MacOS/Kindle Comic Converter</string> <string>MacOS/Kindle Comic Converter</string>
<key>CFBundleGetInfoString</key> <key>CFBundleGetInfoString</key>
<string>KindleComicConverter 5.1.3, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski</string> <string>KindleComicConverter 5.2.1, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski</string>
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>comic2ebook.icns</string> <string>comic2ebook.icns</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
@@ -21,11 +21,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.1.3</string> <string>5.2.1</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5.1.3</string> <string>5.2.1</string>
<key>LSEnvironment</key> <key>LSEnvironment</key>
<dict> <dict>
<key>PATH</key> <key>PATH</key>