1
0
mirror of https://github.com/ciromattia/kcc synced 2025-12-19 20:51:45 +00:00

Merge pull request #233 from ciromattia/dev

5.4
This commit is contained in:
Paweł Jastrzębski
2017-04-09 15:35:41 +02:00
committed by GitHub
16 changed files with 185 additions and 123 deletions

View File

@@ -1,6 +1,6 @@
# KCC # KCC
[![GitHub release](https://img.shields.io/github/release/ciromattia/kcc.svg)]() [![PyPI](https://img.shields.io/pypi/v/KindleComicConverter.svg)]() [![AUR](https://img.shields.io/aur/version/kcc.svg)]() [![GitHub release](https://img.shields.io/github/release/ciromattia/kcc.svg)](https://github.com/ciromattia/kcc/releases) [![PyPI](https://img.shields.io/pypi/v/KindleComicConverter.svg)](https://pypi.python.org/pypi/KindleComicConverter) [![AUR](https://img.shields.io/aur/version/kcc.svg)](https://aur.archlinux.org/packages/kcc/)
**Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ. **Kindle Comic Converter** is a Python app to convert comic/manga files or folders to EPUB, Panel View MOBI or E-Ink optimized CBZ.
It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is It was initially developed for Kindle but since version 4.6 it outputs valid EPUB 3.0 so _**despite its name, KCC is
@@ -72,7 +72,7 @@ After completed conversion you should find ready file alongside the original inp
Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details. Please check [our wiki](https://github.com/ciromattia/kcc/wiki/) for more details.
CLI version of **KCC** is intended for power users. It is not idiot-proof like GUI :-) CLI version of **KCC** is intended for power users. It allow to use options that might not be compatible and decrease quality of output.
### Standalone `kcc-c2e.py` usage: ### Standalone `kcc-c2e.py` usage:
@@ -86,6 +86,7 @@ Options:
KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
KoAO) [Default=KV] 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)
-q, --hq Try to increase the quality of magnification
-2, --two-panel Display two not four panels in Panel View mode -2, --two-panel Display two not four panels in Panel View mode
-w, --webtoon Webtoon processing mode -w, --webtoon Webtoon processing mode
@@ -170,6 +171,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.4:
* Reimplemented high quality Panel View option
* Improved webtoon splitter
* Fixed page splitter
#### 5.3.1: #### 5.3.1:
* Small increase of output quality * Small increase of output quality
* Improved error reporting * Improved error reporting

View File

@@ -66,7 +66,16 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="2"> <item row="0" column="2">
@@ -127,7 +136,16 @@
<item row="4" column="0" colspan="2"> <item row="4" column="0" colspan="2">
<widget class="QWidget" name="optionWidget" native="true"> <widget class="QWidget" name="optionWidget" native="true">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="0" column="0">
@@ -156,10 +174,13 @@
<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;&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> <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;Indeterminate - 2 panels&lt;br/&gt;&lt;/span&gt;Zoom only the top and bottom of the page.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600; text-decoration: underline;&quot;&gt;Checked - 4 high quality panels&lt;br/&gt;&lt;/span&gt;Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
<property name="text"> <property name="text">
<string>Panel View 4/2</string> <string>Panel View 4/2/HQ</string>
</property>
<property name="tristate">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
@@ -238,7 +259,16 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_2"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
@@ -267,7 +297,16 @@
<item row="0" column="0" colspan="2"> <item row="0" column="0" colspan="2">
<widget class="QWidget" name="toolWidget" native="true"> <widget class="QWidget" name="toolWidget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item>
@@ -316,7 +355,16 @@
</sizepolicy> </sizepolicy>
</property> </property>
<layout class="QGridLayout" name="gridLayout_4"> <layout class="QGridLayout" name="gridLayout_4">
<property name="margin"> <property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item row="0" column="0"> <item row="0" column="0">

View File

@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter" #define MyAppName "Kindle Comic Converter"
#define MyAppVersion "5.3.1" #define MyAppVersion "5.4"
#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"

2
kcc.py
View File

@@ -39,7 +39,7 @@ elif sys.platform.startswith('win'):
class _Popen(forking.Popen): class _Popen(forking.Popen):
def __init__(self, *args, **kw): def __init__(self, *args, **kw):
if hasattr(sys, 'frozen'): if hasattr(sys, 'frozen'):
# noinspection PyProtectedMember # noinspection PyUnresolvedReferences,PyProtectedMember
os.putenv('_MEIPASS2', sys._MEIPASS) os.putenv('_MEIPASS2', sys._MEIPASS)
try: try:
super(_Popen, self).__init__(*args, **kw) super(_Popen, self).__init__(*args, **kw)

View File

@@ -254,8 +254,10 @@ class WorkerThread(QtCore.QThread):
options.splitter = 2 options.splitter = 2
elif GUI.rotateBox.checkState() == 2: elif GUI.rotateBox.checkState() == 2:
options.splitter = 1 options.splitter = 1
if GUI.qualityBox.isChecked(): if GUI.qualityBox.checkState() == 1:
options.autoscale = True options.autoscale = True
elif GUI.qualityBox.checkState() == 2:
options.hq = 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:
@@ -531,8 +533,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
def clearJobs(self): def clearJobs(self):
GUI.jobList.clear() GUI.jobList.clear()
# noinspection PyCallByClass,PyTypeChecker
def openWiki(self): def openWiki(self):
# noinspection PyCallByClass
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki')) QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
def modeChange(self, mode): def modeChange(self, mode):
@@ -613,6 +615,18 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.rotateBox.setEnabled(True) GUI.rotateBox.setEnabled(True)
GUI.upscaleBox.setEnabled(True) GUI.upscaleBox.setEnabled(True)
def togglequalityBox(self, value):
profile = GUI.profiles[str(GUI.deviceBox.currentText())]
if value == 2:
if profile['Label'] in ['KV']:
self.addMessage('This option is intended for older Kindle models.', 'warning')
self.addMessage('It might not provide any quality increase in this case.', 'warning')
GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True)
else:
GUI.upscaleBox.setEnabled(True)
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
def changeGamma(self, value): def changeGamma(self, value):
valueRaw = int(5 * round(float(value) / 5)) valueRaw = int(5 * round(float(value) / 5))
value = '%.2f' % (float(valueRaw) / 100) value = '%.2f' % (float(valueRaw) / 100)
@@ -634,6 +648,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
self.changeFormat() self.changeFormat()
GUI.gammaSlider.setValue(0) GUI.gammaSlider.setValue(0)
self.changeGamma(0) self.changeGamma(0)
if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale']) GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
if not profile['PVOptions']: if not profile['PVOptions']:
@@ -648,6 +663,7 @@ 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'])
if not GUI.webtoonBox.isChecked():
GUI.qualityBox.setEnabled(profile['PVOptions']) GUI.qualityBox.setEnabled(profile['PVOptions'])
if str(GUI.formatBox.currentText()) == 'MOBI/AZW3': if str(GUI.formatBox.currentText()) == 'MOBI/AZW3':
GUI.outputSplit.setEnabled(True) GUI.outputSplit.setEnabled(True)
@@ -1002,6 +1018,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow):
GUI.gammaSlider.valueChanged.connect(self.changeGamma) GUI.gammaSlider.valueChanged.connect(self.changeGamma)
GUI.gammaBox.stateChanged.connect(self.togglegammaBox) GUI.gammaBox.stateChanged.connect(self.togglegammaBox)
GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox) GUI.webtoonBox.stateChanged.connect(self.togglewebtoonBox)
GUI.qualityBox.stateChanged.connect(self.togglequalityBox)
GUI.deviceBox.activated.connect(self.changeDevice) GUI.deviceBox.activated.connect(self.changeDevice)
GUI.formatBox.activated.connect(self.changeFormat) GUI.formatBox.activated.connect(self.changeFormat)
MW.progressBarTick.connect(self.updateProgressbar) MW.progressBarTick.connect(self.updateProgressbar)

View File

@@ -2,7 +2,7 @@
# Form implementation generated from reading ui file 'gui\KCC.ui' # Form implementation generated from reading ui file 'gui\KCC.ui'
# #
# Created by: PyQt5 UI code generator 5.6 # Created by: PyQt5 UI code generator 5.8.1
# #
# WARNING! All changes made in this file will be lost! # WARNING! All changes made in this file will be lost!
@@ -81,6 +81,7 @@ class Ui_mainWindow(object):
self.rotateBox.setObjectName("rotateBox") self.rotateBox.setObjectName("rotateBox")
self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1) self.gridLayout_2.addWidget(self.rotateBox, 0, 1, 1, 1)
self.qualityBox = QtWidgets.QCheckBox(self.optionWidget) self.qualityBox = QtWidgets.QCheckBox(self.optionWidget)
self.qualityBox.setTristate(True)
self.qualityBox.setObjectName("qualityBox") self.qualityBox.setObjectName("qualityBox")
self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1) self.gridLayout_2.addWidget(self.qualityBox, 0, 2, 1, 1)
self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget) self.webtoonBox = QtWidgets.QCheckBox(self.optionWidget)
@@ -241,8 +242,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\'><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.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;\">Indeterminate - 2 panels<br/></span>Zoom only the top and bottom of the page.</p><p><span style=\" font-weight:600; text-decoration: underline;\">Checked - 4 high quality panels<br/></span>Zoom each corner separately. Try to increase the quality of magnification. Check wiki for more details.</p></body></html>"))
self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2")) self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2/HQ"))
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>"))

View File

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

View File

@@ -16,12 +16,11 @@
# PERFORMANCE OF THIS SOFTWARE. # PERFORMANCE OF THIS SOFTWARE.
# #
import sys
import os import os
from zipfile import is_zipfile, ZipFile from zipfile import is_zipfile, ZipFile
from subprocess import STDOUT, PIPE from subprocess import STDOUT, PIPE
from psutil import Popen from psutil import Popen
from shutil import move, copy from shutil import move
from . import rarfile from . import rarfile
from .shared import check7ZFile as is_7zfile from .shared import check7ZFile as is_7zfile

View File

@@ -103,6 +103,10 @@ def buildHTML(path, imgfile, imgfilepath):
os.makedirs(htmlpath) os.makedirs(htmlpath)
htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml') htmlfile = os.path.join(htmlpath, filename[0] + '.xhtml')
imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size imgsize = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
if options.hq:
imgsizeframe = deviceres
else:
imgsizeframe = imgsize
f = open(htmlfile, "w", encoding='UTF-8') f = open(htmlfile, "w", encoding='UTF-8')
f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", f.writelines(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n",
"<!DOCTYPE html>\n", "<!DOCTYPE html>\n",
@@ -111,15 +115,18 @@ def buildHTML(path, imgfile, imgfilepath):
"<title>", escape(filename[0]), "</title>\n", "<title>", escape(filename[0]), "</title>\n",
"<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", "<link href=\"", "../" * (backref - 1), "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n",
"<meta name=\"viewport\" " "<meta name=\"viewport\" "
"content=\"width=" + str(deviceres[0]) + ", height=" + str(deviceres[1]) + "\"/>\n" "content=\"width=" + str(imgsize[0]) + ", height=" + str(imgsize[1]) + "\"/>\n"
"</head>\n", "</head>\n",
"<body style=\"" + additionalStyle + "\">\n", "<body style=\"" + additionalStyle + "\">\n",
"<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsize) + "%;\">\n", "<div style=\"text-align:center;top:" + getTopMargin(deviceres, imgsize) + "%;\">\n",
"<img width=\"" + str(imgsize[0]) + "\" height=\"" + str(imgsize[1]) + "\" ", "<img width=\"" + str(imgsizeframe[0]) + "\" height=\"" + str(imgsizeframe[1]) + "\" ",
"src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"]) "src=\"", "../" * backref, "Images/", postfix, imgfile, "\"/>\n</div>\n"])
if options.iskindle and options.panelview: if options.iskindle and options.panelview:
if options.autoscale: if options.autoscale:
size = (getPanelViewResolution(imgsize, deviceres)) size = (getPanelViewResolution(imgsize, deviceres))
else:
if options.hq:
size = imgsize
else: else:
size = (int(imgsize[0] * 1.5), int(imgsize[1] * 1.5)) size = (int(imgsize[0] * 1.5), int(imgsize[1] * 1.5))
if size[0] - deviceres[0] < deviceres[0] * 0.01: if size[0] - deviceres[0] < deviceres[0] * 0.01:
@@ -455,7 +462,7 @@ def buildEPUB(path, chapterNames, tomeNumber):
def imgDirectoryProcessing(path): def imgDirectoryProcessing(path):
global workerPool, workerOutput global workerPool, workerOutput
workerPool = Pool() workerPool = Pool(maxtasksperchild=100)
workerOutput = [] workerOutput = []
options.imgMetadata = {} options.imgMetadata = {}
options.imgOld = [] options.imgOld = []
@@ -884,6 +891,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("-q", "--hq", action="store_true", dest="hq", default=False,
help="Try to increase the quality of magnification")
mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False, mainOptions.add_option("-2", "--two-panel", action="store_true", dest="autoscale", default=False,
help="Display two not four panels in Panel View mode") 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,
@@ -960,16 +969,20 @@ def checkOptions():
# Older Kindle models don't support Panel View. # Older Kindle models don't support Panel View.
if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX': if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX':
options.panelview = False options.panelview = False
options.hq = False
# Webtoon mode mandatory options # Webtoon mode mandatory options
if options.webtoon: if options.webtoon:
options.panelview = False options.panelview = False
options.righttoleft = False options.righttoleft = False
options.upscale = True options.upscale = True
options.hq = False
# 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.hq = False
if 'Ko' in options.profile: if 'Ko' in options.profile:
options.panelview = False options.panelview = False
options.hq = 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
@@ -1185,9 +1198,11 @@ def makeMOBI(work, qtGUI=None):
threadNumber = 1 threadNumber = 1
elif 2 < availableMemory <= 4: elif 2 < availableMemory <= 4:
threadNumber = 2 threadNumber = 2
else: elif 4 < availableMemory <= 8:
threadNumber = 4 threadNumber = 4
makeMOBIWorkerPool = Pool(threadNumber) else:
threadNumber = None
makeMOBIWorkerPool = Pool(threadNumber, maxtasksperchild=10)
for i in work: for i in work:
makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick) makeMOBIWorkerPool.apply_async(func=makeMOBIWorker, args=(i, ), callback=makeMOBIWorkerTick)
makeMOBIWorkerPool.close() makeMOBIWorkerPool.close()

View File

@@ -23,7 +23,7 @@ import sys
from shutil import rmtree, copytree, move 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, ImageChops, ImageOps, ImageDraw
from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace from .shared import getImageFileName, walkLevel, walkSort, sanitizeTrace
try: try:
from PyQt5 import QtCore from PyQt5 import QtCore
@@ -67,8 +67,7 @@ def mergeDirectory(work):
result = Image.new('RGB', (targetWidth, targetHeight)) result = Image.new('RGB', (targetWidth, targetHeight))
y = 0 y = 0
for i in imagesValid: for i in imagesValid:
img = Image.open(i) img = Image.open(i).convert('RGB')
img = img.convert('RGB')
if img.size[0] < targetWidth: if img.size[0] < targetWidth:
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))
@@ -80,30 +79,8 @@ def mergeDirectory(work):
return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2]) return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def sanitizePanelSize(panel, opt): def detectSolid(img):
newPanels = [] return not ImageChops.invert(img).getbbox() or not img.getbbox()
if panel[2] > 6 * opt.height:
diff = int(panel[2] / 8)
newPanels.append([panel[0], panel[1] - diff * 7, diff])
newPanels.append([panel[1] - diff * 7, panel[1] - diff * 6, diff])
newPanels.append([panel[1] - diff * 6, panel[1] - diff * 5, diff])
newPanels.append([panel[1] - diff * 5, panel[1] - diff * 4, diff])
newPanels.append([panel[1] - diff * 4, panel[1] - diff * 3, diff])
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 3 * opt.height:
diff = int(panel[2] / 4)
newPanels.append([panel[0], panel[1] - diff * 3, diff])
newPanels.append([panel[1] - diff * 3, panel[1] - diff * 2, diff])
newPanels.append([panel[1] - diff * 2, panel[1] - diff, diff])
newPanels.append([panel[1] - diff, panel[1], diff])
elif panel[2] > 1.5 * opt.height:
newPanels.append([panel[0], panel[1] - int(panel[2] / 2), int(panel[2] / 2)])
newPanels.append([panel[1] - int(panel[2] / 2), panel[1], int(panel[2] / 2)])
else:
newPanels = [panel]
return newPanels
def splitImageTick(output): def splitImageTick(output):
@@ -121,56 +98,60 @@ def splitImage(work):
path = work[0] path = work[0]
name = work[1] name = work[1]
opt = work[2] opt = work[2]
# Hardcoded options
threshold = 1.0
delta = 15
fileExpanded = os.path.splitext(name)
filePath = os.path.join(path, name) filePath = os.path.join(path, name)
image = Image.open(filePath) imgOrg = Image.open(filePath).convert('RGB')
image = image.convert('RGB') imgProcess = Image.open(filePath).convert('1')
widthImg, heightImg = image.size widthImg, heightImg = imgOrg.size
if heightImg > opt.height: if heightImg > opt.height:
if opt.debug: if opt.debug:
from PIL import ImageDraw drawImg = Image.open(filePath).convert(mode='RGBA')
debugImage = Image.open(filePath) draw = ImageDraw.Draw(drawImg)
draw = ImageDraw.Draw(debugImage)
# Find panels # Find panels
y1 = 0 yWork = 0
y2 = 15 panelDetected = False
panels = [] panels = []
while y2 < heightImg: while yWork < heightImg:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] < threshold and y2 < heightImg: tmpImg = imgProcess.crop([0, yWork, widthImg, yWork + 4])
y2 += delta solid = detectSolid(tmpImg)
y2 -= delta if not solid and not panelDetected:
y1Temp = y2 panelDetected = True
y1 = y2 + delta panelY1 = yWork - 2
y2 = y1 + delta if solid and panelDetected:
while ImageStat.Stat(image.crop([0, y1, widthImg, y2])).var[0] >= threshold and y2 < heightImg: panelDetected = False
y1 += delta panelY2 = yWork + 6
y2 += delta panels.append((panelY1, panelY2, panelY2 - panelY1))
if y1 + delta >= heightImg: yWork += 5
y1 = heightImg - 1
y2Temp = y1 # Split too big panels
if opt.debug: panelsProcessed = []
draw.line([(0, y1Temp), (widthImg, y1Temp)], fill=(0, 255, 0)) for panel in panels:
draw.line([(0, y2Temp), (widthImg, y2Temp)], fill=(255, 0, 0)) if panel[2] <= opt.height * 1.5:
panelHeight = y2Temp - y1Temp panelsProcessed.append(panel)
if panelHeight > delta: elif panel[2] < opt.height * 2:
# Panels that can't be cut nicely will be forcefully splitted diff = panel[2] - opt.height
panelsCleaned = sanitizePanelSize([y1Temp, y2Temp, panelHeight], opt) panelsProcessed.append((panel[0], panel[1] - diff, opt.height))
for panel in panelsCleaned: panelsProcessed.append((panel[1] - opt.height, panel[1], opt.height))
panels.append(panel) else:
parts = round(panel[2] / opt.height)
diff = panel[2] // parts
for x in range(0, parts):
panelsProcessed.append((panel[0] + (x * diff), panel[1] - ((parts - x - 1) * diff), diff))
if opt.debug: if opt.debug:
for panel in panelsProcessed:
# noinspection PyUnboundLocalVariable # noinspection PyUnboundLocalVariable
debugImage.save(os.path.join(path, fileExpanded[0] + '-debug.png'), 'PNG') draw.rectangle([(0, panel[0]), (widthImg, panel[1])], (0, 255, 0, 128), (0, 0, 255, 255))
# noinspection PyUnboundLocalVariable
debugImage = Image.alpha_composite(imgOrg.convert(mode='RGBA'), drawImg)
debugImage.save(os.path.join(path, os.path.splitext(name)[0] + '-debug.png'), 'PNG')
# Create virtual pages # Create virtual pages
pages = [] pages = []
currentPage = [] currentPage = []
pageLeft = opt.height pageLeft = opt.height
panelNumber = 0 panelNumber = 0
for panel in panels: for panel in panelsProcessed:
if pageLeft - panel[2] > 0: if pageLeft - panel[2] > 0:
pageLeft -= panel[2] pageLeft -= panel[2]
currentPage.append(panelNumber) currentPage.append(panelNumber)
@@ -190,14 +171,14 @@ def splitImage(work):
pageHeight = 0 pageHeight = 0
targetHeight = 0 targetHeight = 0
for panel in page: for panel in page:
pageHeight += panels[panel][2] pageHeight += panelsProcessed[panel][2]
if pageHeight > delta: if pageHeight > 15:
newPage = Image.new('RGB', (widthImg, pageHeight)) newPage = Image.new('RGB', (widthImg, pageHeight))
for panel in page: for panel in page:
panelImg = image.crop([0, panels[panel][0], widthImg, panels[panel][1]]) panelImg = imgOrg.crop([0, panelsProcessed[panel][0], widthImg, panelsProcessed[panel][1]])
newPage.paste(panelImg, (0, targetHeight)) newPage.paste(panelImg, (0, targetHeight))
targetHeight += panels[panel][2] targetHeight += panelsProcessed[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG') newPage.save(os.path.join(path, os.path.splitext(name)[0] + '-' + str(pageNumber) + '.png'), 'PNG')
pageNumber += 1 pageNumber += 1
os.remove(filePath) os.remove(filePath)
except Exception: except Exception:
@@ -238,13 +219,13 @@ def main(argv=None, qtGUI=None):
work = [] work = []
pagenumber = 1 pagenumber = 1
splitWorkerOutput = [] splitWorkerOutput = []
splitWorkerPool = Pool() splitWorkerPool = Pool(maxtasksperchild=10)
if options.merge: if options.merge:
print("Merging images...") print("Merging images...")
directoryNumer = 1 directoryNumer = 1
mergeWork = [] mergeWork = []
mergeWorkerOutput = [] mergeWorkerOutput = []
mergeWorkerPool = Pool() mergeWorkerPool = Pool(maxtasksperchild=10)
mergeWork.append([options.targetDir]) mergeWork.append([options.targetDir])
for root, dirs, files in os.walk(options.targetDir, False): for root, dirs, files in os.walk(options.targetDir, False):
dirs, files = walkSort(dirs, files) dirs, files = walkSort(dirs, files)
@@ -263,7 +244,8 @@ 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][0], mergeWorkerOutput[0][1]) raise RuntimeError("One of workers crashed. Cause: " + mergeWorkerOutput[0][0],
mergeWorkerOutput[0][1])
print("Splitting images...") print("Splitting images...")
for root, _, files in os.walk(options.targetDir, False): for root, _, files in os.walk(options.targetDir, False):
for name in files: for name in files:
@@ -286,7 +268,8 @@ 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][0], splitWorkerOutput[0][1]) 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

@@ -120,7 +120,7 @@ class ComicPageParser:
width, height = self.image.size width, height = self.image.size
dstwidth, dstheight = self.size dstwidth, dstheight = self.size
if (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \ if (width > height) != (dstwidth > dstheight) and width <= dstheight and height <= dstwidth \
and not self.opt.webtoon: and not self.opt.webtoon and self.opt.splitter == 1:
self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True), self.color, self.fill]) self.payload.append(['R', self.source, self.image.rotate(90, Image.BICUBIC, True), self.color, self.fill])
elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon: elif (width > height) != (dstwidth > dstheight) and not self.opt.webtoon:
if self.opt.splitter != 1: if self.opt.splitter != 1:
@@ -209,6 +209,8 @@ class ComicPage:
def __init__(self, options, mode, path, image, color, fill): def __init__(self, options, mode, path, image, color, fill):
self.opt = options self.opt = options
_, self.size, self.palette, self.gamma = self.opt.profileData _, self.size, self.palette, self.gamma = self.opt.profileData
if self.opt.hq:
self.size = (int(self.size[0] * 1.5), int(self.size[1] * 1.5))
self.image = image self.image = image
self.color = color self.color = color
self.fill = fill self.fill = fill

View File

@@ -17,11 +17,9 @@
# #
import os import os
from sys import version_info
from hashlib import md5 from hashlib import md5
from html.parser import HTMLParser from html.parser import HTMLParser
from distutils.version import StrictVersion from distutils.version import StrictVersion
from time import sleep
from shutil import rmtree, copy from shutil import rmtree, copy
from tempfile import mkdtemp from tempfile import mkdtemp
from zipfile import ZipFile, ZIP_DEFLATED from zipfile import ZipFile, ZIP_DEFLATED

View File

@@ -23,6 +23,7 @@ import sys
from . import __version__ from . import __version__
from .shared import dependencyCheck from .shared import dependencyCheck
def start(): def start():
dependencyCheck(3) dependencyCheck(3)
from . import KCC_gui from . import KCC_gui
@@ -40,12 +41,14 @@ def start():
KCCUI.handleMessage(sys.argv[1]) KCCUI.handleMessage(sys.argv[1])
sys.exit(KCCAplication.exec_()) sys.exit(KCCAplication.exec_())
def startC2E(): def startC2E():
dependencyCheck(2) dependencyCheck(2)
from .comic2ebook import main from .comic2ebook import main
print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.') print('comic2ebook v' + __version__ + ' - Written by Ciro Mattia Gonano and Pawel Jastrzebski.')
sys.exit(main(sys.argv[1:])) sys.exit(main(sys.argv[1:]))
def startC2P(): def startC2P():
dependencyCheck(1) dependencyCheck(1)
from .comic2panel import main from .comic2panel import main

View File

@@ -30,7 +30,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.3.1, written 2012-2017 by Ciro Mattia Gonano and Pawel Jastrzebski</string> <string>KindleComicConverter 5.4.0, written 2012-2017 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>
@@ -42,11 +42,11 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>5.3.1</string> <string>5.4.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>5.3.1</string> <string>5.4.0</string>
<key>LSEnvironment</key> <key>LSEnvironment</key>
<dict> <dict>
<key>PATH</key> <key>PATH</key>

View File

@@ -2,10 +2,10 @@
""" """
pip/pyinstaller build script for KCC. pip/pyinstaller build script for KCC.
Usage (Windows): Install as Python package:
py -3 setup.py build_binary python3 setup.py install
Usage (Linux/OS X): Create EXE/APP/DEB:
python3 setup.py build_binary python3 setup.py build_binary
""" """
@@ -14,32 +14,22 @@ import sys
import shutil import shutil
import setuptools import setuptools
import distutils.cmd import distutils.cmd
from distutils.command.build import build
from kindlecomicconverter import __version__ from kindlecomicconverter import __version__
NAME = 'KindleComicConverter' NAME = 'KindleComicConverter'
MAIN = 'kcc.py' MAIN = 'kcc.py'
VERSION = __version__ VERSION = __version__
class BuildBinaryCommand(distutils.cmd.Command): class BuildBinaryCommand(distutils.cmd.Command):
description = 'build binary release' description = 'build binary release'
user_options = [
('pyz', None, 'build PYZ package'),
]
def initialize_options(self):
# noinspection PyAttributeOutsideInit
self.pyz = False
def finalize_options(self):
pass
def run(self): def run(self):
if sys.platform == 'darwin': if sys.platform == 'darwin':
if os.path.isfile('Kindle Comic Converter.spec'): if os.path.isfile('Kindle Comic Converter.spec'):
os.system('pyinstaller "Kindle Comic Converter.spec"') os.system('pyinstaller "Kindle Comic Converter.spec"')
else: else:
os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s --noupx kcc.py') os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py')
shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources')
shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents') shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents')