mirror of
https://github.com/ciromattia/kcc
synced 2025-12-21 05:31:49 +00:00
10
README.md
10
README.md
@@ -90,7 +90,10 @@ Options:
|
|||||||
-f FORMAT, --format=FORMAT
|
-f FORMAT, --format=FORMAT
|
||||||
Output format (Available options: Auto, MOBI, EPUB,
|
Output format (Available options: Auto, MOBI, EPUB,
|
||||||
CBZ) [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
|
||||||
@@ -160,6 +163,11 @@ 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:
|
####5.2:
|
||||||
* Added new Panel View options
|
* Added new Panel View options
|
||||||
* Implemented new margin detection algorithm
|
* Implemented new margin detection algorithm
|
||||||
|
|||||||
@@ -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><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></string>
|
<string><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></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>
|
||||||
|
|||||||
2
kcc.iss
2
kcc.iss
@@ -1,5 +1,5 @@
|
|||||||
#define MyAppName "Kindle Comic Converter"
|
#define MyAppName "Kindle Comic Converter"
|
||||||
#define MyAppVersion "5.2"
|
#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"
|
||||||
|
|||||||
@@ -21,17 +21,15 @@ 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, saferRemove
|
from .shared import md5Checksum, HTMLStripper, sanitizeTrace, saferRemove
|
||||||
from . import __version__
|
from . import __version__
|
||||||
@@ -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 ' is corrupted.' not in str(err):
|
if len(err.args) == 1:
|
||||||
GUI.sentry.captureException()
|
|
||||||
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
|
||||||
% (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
|
% (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):
|
||||||
|
GUI.sentry.captureException()
|
||||||
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)
|
||||||
@@ -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'))
|
||||||
|
|
||||||
@@ -646,6 +649,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
|
|||||||
else:
|
else:
|
||||||
GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
|
GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
|
||||||
GUI.qualityBox.setEnabled(profile['PVOptions'])
|
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()
|
||||||
@@ -700,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 != '':
|
||||||
@@ -762,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(),
|
||||||
@@ -848,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
|
||||||
|
|||||||
@@ -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)
|
||||||
@@ -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"))
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
__version__ = '5.2'
|
__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'
|
||||||
|
|||||||
@@ -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,7 +93,6 @@ 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 = ''
|
||||||
backref = 1
|
backref = 1
|
||||||
head = path
|
head = path
|
||||||
@@ -483,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)
|
||||||
@@ -493,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:
|
||||||
@@ -527,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):
|
||||||
@@ -734,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:
|
else:
|
||||||
if filesNumber > 0:
|
level = newLevel
|
||||||
print('WARNING: Automatic output splitting failed.')
|
if level > 0:
|
||||||
if GUI:
|
splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), level)
|
||||||
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]
|
path = [path]
|
||||||
for tome in splitter:
|
for tome in splitter:
|
||||||
path.append(tome)
|
path.append(tome)
|
||||||
return path
|
return path
|
||||||
|
else:
|
||||||
|
raise UserWarning('Unsupported directory structure.')
|
||||||
|
|
||||||
|
|
||||||
def splitProcess(path, mode):
|
def splitProcess(path, mode):
|
||||||
@@ -801,21 +762,14 @@ 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:
|
||||||
|
if mode == 1:
|
||||||
size = os.path.getsize(os.path.join(root, name))
|
size = os.path.getsize(os.path.join(root, name))
|
||||||
if currentSize + size > targetSize:
|
|
||||||
currentTarget, pathRoot = createNewTome()
|
|
||||||
output.append(pathRoot)
|
|
||||||
currentSize = size
|
|
||||||
else:
|
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))
|
size = getDirectorySize(os.path.join(root, name))
|
||||||
if currentSize + size > targetSize:
|
if currentSize + size > targetSize:
|
||||||
currentTarget, pathRoot = createNewTome()
|
currentTarget, pathRoot = createNewTome()
|
||||||
@@ -825,30 +779,10 @@ 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 == 2:
|
else:
|
||||||
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))
|
|
||||||
currentSize = 0
|
|
||||||
if size > targetSize:
|
|
||||||
if not firstTome:
|
|
||||||
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:
|
|
||||||
if not firstTome:
|
if not firstTome:
|
||||||
currentTarget, pathRoot = createNewTome()
|
currentTarget, pathRoot = createNewTome()
|
||||||
output.append(pathRoot)
|
output.append(pathRoot)
|
||||||
@@ -947,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")
|
||||||
@@ -1006,8 +941,8 @@ 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
|
||||||
@@ -1092,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)
|
||||||
@@ -1106,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]
|
||||||
|
|||||||
@@ -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, saferRemove
|
from .shared import getImageFileName, walkLevel, walkSort, saferRemove, sanitizeTrace
|
||||||
try:
|
try:
|
||||||
from PyQt5 import QtCore
|
from PyQt5 import QtCore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@@ -81,7 +81,7 @@ def mergeDirectory(work):
|
|||||||
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):
|
||||||
@@ -205,7 +205,7 @@ def splitImage(work):
|
|||||||
pageNumber += 1
|
pageNumber += 1
|
||||||
saferRemove(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:
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -318,7 +318,7 @@ class ComicPage:
|
|||||||
tmpImg = tmpImg.point(lambda x: x and 255)
|
tmpImg = tmpImg.point(lambda x: x and 255)
|
||||||
tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
|
tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
|
||||||
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
|
tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
|
||||||
tmpImg = tmpImg.point(lambda x: (x >= 48 * power) and x)
|
tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
|
||||||
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
|
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
|
||||||
|
|
||||||
def cropMargin(self, power):
|
def cropMargin(self, power):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -144,7 +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:/Python35/', '')\
|
||||||
|
.replace('c:/python35/', '')\
|
||||||
|
.replace('C:\\Users\\Pawel\\Documents\\Projekty\\KCC\\', '')\
|
||||||
.replace('C:\\Python35\\', '')\
|
.replace('C:\\Python35\\', '')\
|
||||||
.replace('c:\\python35\\', '')
|
.replace('c:\\python35\\', '')
|
||||||
|
|
||||||
|
|||||||
@@ -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.2, 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.2.0</string>
|
<string>5.2.1</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>5.2.0</string>
|
<string>5.2.1</string>
|
||||||
<key>LSEnvironment</key>
|
<key>LSEnvironment</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>PATH</key>
|
<key>PATH</key>
|
||||||
|
|||||||
Reference in New Issue
Block a user