diff --git a/README.md b/README.md
index f700680b..cfaf2145 100644
--- a/README.md
+++ b/README.md
@@ -75,9 +75,11 @@ Usage: kcc-c2e [options] comic_file|comic_folder
Options:
MAIN:
-p PROFILE, --profile=PROFILE
- Device profile (Available options: K1, K2, K3, K45, KDX,
- KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, KoAO) [Default=KV]
+ Device profile (Available options: K1, K2, K3, K45,
+ KDX, KPW, KV, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O,
+ KoAO) [Default=KV]
-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
OUTPUT SETTINGS:
@@ -86,29 +88,28 @@ Options:
-t TITLE, --title=TITLE
Comic title [Default=filename or directory name]
-f FORMAT, --format=FORMAT
- Output format (Available options: Auto, MOBI, EPUB, CBZ)
- [Default=Auto]
+ Output format (Available options: Auto, MOBI, EPUB,
+ CBZ) [Default=Auto]
-b, --batchsplit Split output into multiple files
PROCESSING:
-u, --upscale Resize images smaller than device's resolution
-s, --stretch Stretch images to device's resolution
-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
- Apply gamma correction to linearize the image [Default=Auto]
- --hq Enable high quality Panel View
+ Apply gamma correction to linearize the image
+ [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
--whiteborders Disable autodetection and force white borders
--forcecolor Don't convert images to grayscale
--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:
--customwidth=CUSTOMWIDTH
@@ -159,6 +160,12 @@ The app relies and includes the following scripts:
* [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub)
## CHANGELOG
+####5.2:
+* Added new Panel View options
+* Implemented new margin detection algorithm
+* Removed HQ Panel View mode
+* Fixed multiple smaller issues
+
####5.1.3:
* Added Kobo Aura ONE profile
* Fixed few small bugs
diff --git a/gui/KCC.ui b/gui/KCC.ui
index eb8b84e4..3bdd9173 100644
--- a/gui/KCC.ui
+++ b/gui/KCC.ui
@@ -174,10 +174,10 @@
-
- <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>
+ <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>
- HQ zoom
+ Panel View 4/2
diff --git a/kcc.iss b/kcc.iss
index d536ab25..28a73176 100644
--- a/kcc.iss
+++ b/kcc.iss
@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter"
-#define MyAppVersion "5.1.3"
+#define MyAppVersion "5.2"
#define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski"
#define MyAppURL "http://kcc.iosphe.re/"
#define MyAppExeName "KCC.exe"
diff --git a/kcc/KCC_gui.py b/kcc/KCC_gui.py
index c7735290..dcc9e1bc 100644
--- a/kcc/KCC_gui.py
+++ b/kcc/KCC_gui.py
@@ -33,7 +33,7 @@
from xml.sax.saxutils import escape
from platform import platform
from raven import Client
-from .shared import md5Checksum, HTMLStripper, sanitizeTrace
+from .shared import md5Checksum, HTMLStripper, sanitizeTrace, saferRemove
from . import __version__
from . import comic2ebook
from . import metadata
@@ -257,7 +257,7 @@ def run(self):
elif GUI.rotateBox.checkState() == 2:
options.splitter = 1
if GUI.qualityBox.isChecked():
- options.hqmode = True
+ options.autoscale = True
if GUI.webtoonBox.isChecked():
options.webtoon = True
if GUI.upscaleBox.checkState() == 1:
@@ -331,7 +331,7 @@ def run(self):
if 'outputPath' in locals():
for item in outputPath:
if os.path.exists(item):
- os.remove(item)
+ saferRemove(item)
self.clean()
return
if not self.errors:
@@ -358,9 +358,9 @@ def run(self):
if not self.conversionAlive:
for item in outputPath:
if os.path.exists(item):
- os.remove(item)
+ saferRemove(item)
if os.path.exists(item.replace('.epub', '.mobi')):
- os.remove(item.replace('.epub', '.mobi'))
+ saferRemove(item.replace('.epub', '.mobi'))
self.clean()
return
if self.kindlegenErrorCode[0] == 0:
@@ -381,7 +381,7 @@ def run(self):
for item in outputPath:
GUI.progress.content = ''
mobiPath = item.replace('.epub', '.mobi')
- os.remove(mobiPath + '_toclean')
+ saferRemove(mobiPath + '_toclean')
if GUI.targetDirectory and GUI.targetDirectory != os.path.dirname(mobiPath):
try:
move(mobiPath, GUI.targetDirectory)
@@ -393,15 +393,15 @@ def run(self):
for item in outputPath:
comic2ebook.options.covers[outputPath.index(item)][0].saveToKindle(
k, comic2ebook.options.covers[outputPath.index(item)][1])
- MW.addMessage.emit('Kindle detected. Uploading covers...', 'info', False)
+ MW.addMessage.emit('Kindle detected. Uploading covers... Done!', 'info', False)
else:
GUI.progress.content = ''
for item in outputPath:
mobiPath = item.replace('.epub', '.mobi')
if os.path.exists(mobiPath):
- os.remove(mobiPath)
+ saferRemove(mobiPath)
if os.path.exists(mobiPath + '_toclean'):
- os.remove(mobiPath + '_toclean')
+ saferRemove(mobiPath + '_toclean')
MW.addMessage.emit('Failed to process MOBI file!', 'error', False)
MW.addTrayMessage.emit('Failed to process MOBI file!', 'Critical')
else:
@@ -409,9 +409,9 @@ def run(self):
epubSize = (os.path.getsize(self.kindlegenErrorCode[2])) // 1024 // 1024
for item in outputPath:
if os.path.exists(item):
- os.remove(item)
+ saferRemove(item)
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.addTrayMessage.emit('KindleGen failed to create MOBI!', 'Critical')
if self.kindlegenErrorCode[0] == 1 and self.kindlegenErrorCode[1] != '':
@@ -603,7 +603,9 @@ def togglewebtoonBox(self, value):
GUI.upscaleBox.setEnabled(False)
GUI.upscaleBox.setChecked(True)
else:
- GUI.qualityBox.setEnabled(True)
+ profile = GUI.profiles[str(GUI.deviceBox.currentText())]
+ if profile['PVOptions']:
+ GUI.qualityBox.setEnabled(True)
GUI.mangaBox.setEnabled(True)
GUI.rotateBox.setEnabled(True)
GUI.upscaleBox.setEnabled(True)
@@ -629,9 +631,9 @@ def changeDevice(self):
self.changeFormat()
GUI.gammaSlider.setValue(0)
self.changeGamma(0)
- GUI.qualityBox.setEnabled(profile['Quality'])
+ GUI.qualityBox.setEnabled(profile['PVOptions'])
GUI.upscaleBox.setChecked(profile['DefaultUpscale'])
- if not profile['Quality']:
+ if not profile['PVOptions']:
GUI.qualityBox.setChecked(False)
if str(GUI.deviceBox.currentText()) == 'Other':
self.addMessage(''
@@ -643,7 +645,7 @@ def changeFormat(self, outputFormat=None):
GUI.formatBox.setCurrentIndex(outputFormat)
else:
GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
- GUI.qualityBox.setEnabled(profile['Quality'])
+ GUI.qualityBox.setEnabled(profile['PVOptions'])
def stripTags(self, html):
s = HTMLStripper()
@@ -894,39 +896,39 @@ def __init__(self, KCCAplication, KCCWindow):
MW.resize(500, 500)
self.profiles = {
- "Kindle Oasis": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle Oasis": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': True, 'Label': 'KV'},
- "Kindle Voyage": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'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'},
- "Kindle PW 1/2": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'KPW'},
- "Kindle": {'Quality': True, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0,
'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'},
- "Kobo Mini/Touch": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
+ "Kobo Mini/Touch": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'KoMT'},
- "Kobo Glo": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
+ "Kobo Glo": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'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'},
- "Kobo Aura": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
+ "Kobo Aura": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'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'},
- "Kobo Aura H2O": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 1,
+ "Kobo Aura H2O": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1,
'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'},
- "Other": {'Quality': False, 'ForceExpert': True, 'DefaultFormat': 1,
+ "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1,
'DefaultUpscale': False, 'Label': 'OTHER'},
- "Kindle 1": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K1'},
- "Kindle 2": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle 2": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K2'},
- "Kindle 3": {'Quality': False, 'ForceExpert': False, 'DefaultFormat': 0,
+ "Kindle 3": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0,
'DefaultUpscale': False, 'Label': 'K3'},
}
profilesGUI = [
diff --git a/kcc/KCC_ui.py b/kcc/KCC_ui.py
index 5418e3ee..5e718ef2 100644
--- a/kcc/KCC_ui.py
+++ b/kcc/KCC_ui.py
@@ -241,8 +241,8 @@ def retranslateUi(self, mainWindow):
self.mangaBox.setText(_translate("mainWindow", "Manga mode"))
self.rotateBox.setToolTip(_translate("mainWindow", "
Unchecked - Split
Double page spreads will be cut into two separate pages.
Indeterminate - Rotate and split
Double page spreads will be displayed twice. First rotated and then split.
Checked - Rotate
Double page spreads will be rotated.
"))
self.rotateBox.setText(_translate("mainWindow", "Spread splitter"))
- self.qualityBox.setToolTip(_translate("mainWindow", "High quality Panel View.
Require source files with bigger resolution than target device.
Highly impact size of output file!
"))
- self.qualityBox.setText(_translate("mainWindow", "HQ zoom"))
+ self.qualityBox.setToolTip(_translate("mainWindow", "Unchecked - 4 panels
Zoom each corner separately.
Checked - 2 panels
Zoom only the top and bottom of the page.
"))
+ self.qualityBox.setText(_translate("mainWindow", "Panel View 4/2"))
self.webtoonBox.setToolTip(_translate("mainWindow", "Enable special parsing mode for Korean Webtoons.
"))
self.webtoonBox.setText(_translate("mainWindow", "Webtoon mode"))
self.upscaleBox.setToolTip(_translate("mainWindow", "Unchecked - Nothing
Images smaller than device resolution will not be resized.
Indeterminate - Stretching
Images smaller than device resolution will be resized. Aspect ratio will be not preserved.
Checked - Upscaling
Images smaller than device resolution will be resized. Aspect ratio will be preserved.
"))
diff --git a/kcc/__init__.py b/kcc/__init__.py
index 5382553c..d4f4bd0a 100644
--- a/kcc/__init__.py
+++ b/kcc/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '5.1.3'
+__version__ = '5.2'
__license__ = 'ISC'
__copyright__ = '2012-2016, Ciro Mattia Gonano , Pawel Jastrzebski '
__docformat__ = 'restructuredtext en'
diff --git a/kcc/cbxarchive.py b/kcc/cbxarchive.py
index 569dad56..3c857e77 100644
--- a/kcc/cbxarchive.py
+++ b/kcc/cbxarchive.py
@@ -27,7 +27,7 @@
except ImportError:
walk = os.walk
from . import rarfile
-from .shared import check7ZFile as is_7zfile, saferReplace
+from .shared import check7ZFile as is_7zfile, saferReplace, saferRemove
class CBxArchive:
@@ -66,7 +66,7 @@ def extractCBR(self, targetdir):
for root, dirnames, filenames in walk(targetdir):
for filename in filenames:
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):
# Workaround for some wide UTF-8 + Popen abnormalities
@@ -80,7 +80,7 @@ def extractCB7(self, targetdir):
if b"Everything is Ok" in line:
extracted = True
if sys.platform.startswith('darwin'):
- os.remove(self.origFileName)
+ saferRemove(self.origFileName)
if not extracted:
raise OSError
diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py
index a3037394..da9c906f 100755
--- a/kcc/comic2ebook.py
+++ b/kcc/comic2ebook.py
@@ -95,8 +95,6 @@ def buildHTML(path, imgfile, imgfilepath):
additionalStyle = 'background-color:#FFFFFF;'
htmlpath = ''
postfix = ''
- size = ''
- imgfilepv = ''
backref = 1
head = path
while True:
@@ -122,21 +120,16 @@ def buildHTML(path, imgfile, imgfilepath):
"\n"])
if options.iskindle and options.panelview:
- if options.hqmode:
- imgfilepv = list(os.path.splitext(imgfile))
- imgfilepv[0] += "-hq"
- imgfilepv = "".join(imgfilepv)
- 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
+ sizeTmp = Image.open(os.path.join(head, "Images", postfix, imgfile)).size
+ if options.autoscale:
+ size = (getPanelViewResolution(sizeTmp, deviceres))
+ else:
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
else:
noHorizontalPV = False
- if size[1] <= deviceres[1]:
+ if size[1] - deviceres[1] < deviceres[1] * 0.01:
noVerticalPV = True
else:
noVerticalPV = False
@@ -193,7 +186,7 @@ def buildHTML(path, imgfile, imgfilepath):
for box in boxes:
f.writelines(["\n",
"
\n",
+ imgfile, "\" width=\"" + str(size[0]) + "\" height=\"" + str(size[1]) + "\"/>\n",
"
\n"])
f.writelines(["\n",
"\n"])
@@ -336,29 +329,6 @@ def buildOPF(dstdir, title, filelist, cover=None):
f.write("\n\n")
else:
f.write("\n\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("\n")
- # else:
- # f.write("\n")
- # elif '-kcc-c' in entry:
- # if options.righttoleft:
- # f.write("\n")
- # else:
- # f.write("\n")
- # else:
- # f.write("\n")
- # if nextflow == 'right':
- # nextflow = 'left'
- # else:
- # nextflow = 'right'
- # else:
for entry in reflist:
f.write("\n")
f.write("\n\n")
@@ -460,17 +430,15 @@ def buildEPUB(path, chapterNames, tomeNumber):
chapter = False
dirnames, filenames = walkSort(dirnames, filenames)
for afile in filenames:
- filename = getImageFileName(afile)
- if not filename[0].endswith('-hq'):
- filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
- if not chapter:
- chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
- chapter = True
- if cover is None:
- cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
- 'cover' + getImageFileName(filelist[-1][1])[1])
- options.covers.append((image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options,
- tomeNumber), options.uuid))
+ filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile)))
+ if not chapter:
+ chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1]))
+ chapter = True
+ if cover is None:
+ cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'),
+ 'cover' + getImageFileName(filelist[-1][1])[1])
+ 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
if not chapterNames and options.chapters:
chapterlist = []
@@ -548,10 +516,10 @@ def imgFileProcessing(work):
workImg = image.ComicPageParser((dirpath, afile), opt)
for i in workImg.payload:
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:
- img.cutPageNumber(opt.croppingpn)
+ img.cropPageNumber(opt.croppingp)
+ if opt.cropping > 0 and not opt.webtoon:
+ img.cropMargin(opt.croppingp)
img.autocontrastImage()
img.resizeImage()
if opt.forcepng and not opt.forcecolor:
@@ -653,7 +621,7 @@ def getComicInfo(path, originalPath):
try:
xml = metadata.MetadataParser(xmlPath)
except Exception:
- os.remove(xmlPath)
+ saferRemove(xmlPath)
return
options.authors = []
if defaultTitle:
@@ -678,7 +646,7 @@ def getComicInfo(path, originalPath):
options.chapters = xml.data['Bookmarks']
if xml.data['Summary']:
options.summary = escape(xml.data['Summary'])
- os.remove(xmlPath)
+ saferRemove(xmlPath)
def getCoversFromMCB(mangaID):
@@ -704,6 +672,11 @@ def getDirectorySize(start_path='.'):
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):
x = int(deviceres[0] / 2 - size[0] / 2) / deviceres[0] * 100
y = int(deviceres[1] / 2 - size[1] / 2) / deviceres[1] * 100
@@ -963,6 +936,8 @@ def makeParser():
" KoA, KoAHD, KoAH2O, KoAO) [Default=KV]")
mainOptions.add_option("-m", "--manga-style", action="store_true", dest="righttoleft", default=False,
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,
help="Webtoon processing mode"),
@@ -983,8 +958,10 @@ def makeParser():
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",
help="Apply gamma correction to linearize the image [Default=Auto]")
- processingOptions.add_option("--hq", action="store_true", dest="hqmode", default=False,
- help="Enable high quality Panel View")
+ processingOptions.add_option("-c", "--cropping", type="int", dest="cropping", default="2",
+ 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,
help="Disable autodetection and force black borders")
processingOptions.add_option("--whiteborders", action="store_true", dest="white_borders", default=False,
@@ -993,12 +970,6 @@ def makeParser():
help="Don't convert images to grayscale")
processingOptions.add_option("--forcepng", action="store_true", dest="forcepng", default=False,
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,
help="Replace screen width provided by device profile")
@@ -1040,20 +1011,16 @@ def checkOptions():
# 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':
options.panelview = False
- options.hqmode = False
# Webtoon mode mandatory options
if options.webtoon:
options.panelview = False
- options.hqmode = False
options.righttoleft = False
options.upscale = True
# Disable all Kindle features for other e-readers
if options.profile == 'OTHER':
options.panelview = False
- options.hqmode = False
if 'Ko' in options.profile:
options.panelview = False
- options.hqmode = False
# CBZ files on Kindle DX/DXG support higher resolution
if options.profile == 'KDX' and options.format == 'CBZ':
options.customheight = 1200
@@ -1066,7 +1033,7 @@ def checkOptions():
if options.customheight != 0:
Y = options.customheight
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
options.profile = "Custom"
options.profileData = image.ProfileData.Profiles[options.profile]
@@ -1184,7 +1151,6 @@ def makeBook(source, qtGUI=None):
if not GUI and options.format == 'MOBI':
print("Creating MOBI files...")
work = []
- k = kindle.Kindle()
for i in filepath:
work.append([i])
output = makeMOBI(work, GUI)
@@ -1193,6 +1159,7 @@ def makeBook(source, qtGUI=None):
print('Error: KindleGen failed to create MOBI!')
print(errors)
return filepath
+ k = kindle.Kindle()
if k.path and k.coverSupport:
print("Kindle detected. Uploading covers...")
for i in filepath:
@@ -1201,14 +1168,14 @@ def makeBook(source, qtGUI=None):
print('Error: Failed to tweak KindleGen output!')
return filepath
else:
- os.remove(i.replace('.epub', '.mobi') + '_toclean')
+ saferRemove(i.replace('.epub', '.mobi') + '_toclean')
if k.path and k.coverSupport:
options.covers[filepath.index(i)][0].saveToKindle(k, options.covers[filepath.index(i)][1])
return filepath
def makeMOBIFix(item, uuid):
- os.remove(item)
+ saferRemove(item)
mobiPath = item.replace('.epub', '.mobi')
move(mobiPath, mobiPath + '_toclean')
try:
diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py
index 6602a91c..def82509 100644
--- a/kcc/comic2panel.py
+++ b/kcc/comic2panel.py
@@ -24,7 +24,7 @@
from optparse import OptionParser, OptionGroup
from multiprocessing import Pool
from PIL import Image, ImageStat, ImageOps
-from .shared import getImageFileName, walkLevel, walkSort
+from .shared import getImageFileName, walkLevel, walkSort, saferRemove
try:
from PyQt5 import QtCore
except ImportError:
@@ -77,7 +77,7 @@ def mergeDirectory(work):
img = ImageOps.fit(img, (targetWidth, img.size[1]), method=Image.BICUBIC, centering=(0.5, 0.5))
result.paste(img, (0, y))
y += img.size[1]
- os.remove(i)
+ saferRemove(i)
savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
except Exception:
@@ -203,7 +203,7 @@ def splitImage(work):
targetHeight += panels[panel][2]
newPage.save(os.path.join(path, fileExpanded[0] + '-' + str(pageNumber) + '.png'), 'PNG')
pageNumber += 1
- os.remove(filePath)
+ saferRemove(filePath)
except Exception:
return str(sys.exc_info()[1])
@@ -275,7 +275,7 @@ def main(argv=None, qtGUI=None):
pagenumber += 1
work.append([root, name, options])
else:
- os.remove(os.path.join(root, name))
+ saferRemove(os.path.join(root, name))
if GUI:
GUI.progressBarTick.emit('Splitting images')
GUI.progressBarTick.emit(str(pagenumber))
diff --git a/kcc/image.py b/kcc/image.py
index aef77e53..009b2bbf 100755
--- a/kcc/image.py
+++ b/kcc/image.py
@@ -1,5 +1,6 @@
# Copyright (C) 2010 Alex Yatskov
# Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov
+# Copyright (c) 2016 Alberto Planas
# Copyright (c) 2012-2014 Ciro Mattia Gonano
# Copyright (c) 2013-2016 Pawel Jastrzebski
#
@@ -20,7 +21,7 @@
from io import BytesIO
from urllib.request import Request, urlopen
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 . import __version__
@@ -77,21 +78,21 @@ def __init__(self):
]
Profiles = {
- 'K1': ("Kindle 1", (600, 670), Palette4, 1.8, (900, 1005)),
- 'K2': ("Kindle 2", (600, 670), Palette15, 1.8, (900, 1005)),
- 'K3': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
- 'K45': ("Kindle", (600, 800), Palette16, 1.8, (900, 1200)),
- 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8, (1236, 1500)),
- 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8, (1137, 1536)),
- 'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8, (1608, 2172)),
- 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8, (900, 1200)),
- 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8, (1152, 1536)),
- 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8, (1608, 2172)),
- 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8, (1137, 1536)),
- 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8, (1620, 2160)),
- 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8, (1620, 2145)),
- 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8, (2106, 2808)),
- 'OTHER': ("Other", (0, 0), Palette16, 1.8, (0, 0)),
+ 'K1': ("Kindle 1", (600, 670), Palette4, 1.8),
+ 'K2': ("Kindle 2", (600, 670), Palette15, 1.8),
+ 'K3': ("Kindle", (600, 800), Palette16, 1.8),
+ 'K45': ("Kindle", (600, 800), Palette16, 1.8),
+ 'KDX': ("Kindle DX/DXG", (824, 1000), Palette16, 1.8),
+ 'KPW': ("Kindle Paperwhite 1/2", (758, 1024), Palette16, 1.8),
+ 'KV': ("Kindle Paperwhite 3/Voyage/Oasis", (1072, 1448), Palette16, 1.8),
+ 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8),
+ 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8),
+ 'KoGHD': ("Kobo Glo HD", (1072, 1448), Palette16, 1.8),
+ 'KoA': ("Kobo Aura", (758, 1024), Palette16, 1.8),
+ 'KoAHD': ("Kobo Aura HD", (1080, 1440), Palette16, 1.8),
+ 'KoAH2O': ("Kobo Aura H2O", (1080, 1430), Palette16, 1.8),
+ 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8),
+ 'OTHER': ("Other", (0, 0), Palette16, 1.8),
}
@@ -105,8 +106,6 @@ def __init__(self, source, options):
self.color = self.colorCheck()
self.fill = self.fillCheck()
self.splitCheck()
- if self.opt.hqmode:
- self.sizeCheck()
def getImageHistogram(self, image):
histogram = image.histogram()
@@ -205,29 +204,16 @@ def fillCheck(self):
else:
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:
def __init__(self, mode, path, image, color, fill, 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.color = color
self.fill = fill
self.rotated = False
self.orgPath = os.path.join(path[0], path[1])
- if '+' in mode:
- self.hqMode = True
- else:
- self.hqMode = False
if 'N' in mode:
self.targetPath = os.path.join(path[0], os.path.splitext(path[1])[0]) + '-KCC'
elif 'R' in mode:
@@ -247,8 +233,6 @@ def saveToDir(self):
flags.append('Rotated')
if self.fill != 'white':
flags.append('BlackFill')
- if self.hqMode:
- self.targetPath += '-HQ'
if self.opt.forcepng:
self.targetPath += '.png'
self.image.save(self.targetPath, 'PNG', optimize=1)
@@ -282,128 +266,69 @@ def quantizeImage(self):
self.image = self.image.quantize(palette=palImg)
def resizeImage(self):
- if self.hqMode:
- size = (self.panelviewsize[0], self.panelviewsize[1])
- if self.image.size[0] > size[0] or self.image.size[1] > size[1]:
- self.image.thumbnail(size, Image.LANCZOS)
+ if self.image.size[0] <= self.size[0] and self.image.size[1] <= self.size[1]:
+ method = Image.BICUBIC
else:
- size = (self.size[0], self.size[1])
- if self.image.size[0] <= size[0] and self.image.size[1] <= size[1]:
- method = Image.BICUBIC
- else:
- method = Image.LANCZOS
- if self.opt.stretch:
- self.image = self.image.resize(size, method)
- elif self.image.size[0] <= size[0] and self.image.size[1] <= size[1] and not self.opt.upscale:
- if self.opt.format == 'CBZ':
- 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))
+ method = Image.LANCZOS
+ if self.opt.stretch:
+ 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:
- 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)
+ hpercent = self.size[1] / float(self.image.size[1])
+ wsize = int((float(self.image.size[0]) * float(hpercent)))
+ self.image = self.image.resize((wsize, self.size[1]), method)
+ if self.image.size[0] > self.size[0] or self.image.size[1] > self.size[1]:
+ self.image.thumbnail(self.size, Image.LANCZOS)
- def cutPageNumber(self, fixedThreshold):
- if ImageChops.invert(self.image).getbbox() is not None:
- widthImg, heightImg = self.image.size
- delta = 2
- diff = delta
- if ImageStat.Stat(self.image).var[0] < 2 * fixedThreshold:
- return self.image
- while ImageStat.Stat(self.image.crop((0, heightImg - diff, widthImg, heightImg))).var[0] < fixedThreshold\
- and diff < heightImg:
- diff += delta
- diff -= delta
- pageNumberCut1 = diff
- if diff < delta:
- 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 getBoundingBox(self, tmpImg):
+ min_margin = [int(0.005 * i + 0.5) for i in tmpImg.size]
+ max_margin = [int(0.1 * i + 0.5) for i in tmpImg.size]
+ bbox = tmpImg.getbbox()
+ bbox = (
+ max(0, min(max_margin[0], bbox[0] - min_margin[0])),
+ max(0, min(max_margin[1], bbox[1] - min_margin[1])),
+ min(tmpImg.size[0],
+ max(tmpImg.size[0] - max_margin[0], bbox[2] + min_margin[0])),
+ min(tmpImg.size[1],
+ max(tmpImg.size[1] - max_margin[1], bbox[3] + min_margin[1])),
+ )
+ return bbox
- def cropWhiteSpace(self, fixedThreshold):
- if ImageChops.invert(self.image).getbbox() is not None:
- widthImg, heightImg = self.image.size
- delta = 10
- diff = delta
- # top
- while ImageStat.Stat(self.image.crop((0, 0, widthImg, diff))).var[0] < fixedThreshold and diff < heightImg:
- diff += delta
- diff -= delta
- self.image = self.image.crop((0, diff, widthImg, heightImg))
- widthImg, heightImg = self.image.size
- diff = delta
- # left
- while ImageStat.Stat(self.image.crop((0, 0, diff, heightImg))).var[0] < fixedThreshold and diff < widthImg:
- diff += delta
- diff -= delta
- self.image = self.image.crop((diff, 0, widthImg, heightImg))
- widthImg, heightImg = self.image.size
- diff = delta
- # 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))
+ def cropPageNumber(self, power):
+ if self.fill != 'white':
+ tmpImg = self.image.convert(mode='L')
+ else:
+ tmpImg = ImageOps.invert(self.image.convert(mode='L'))
+ tmpImg = tmpImg.point(lambda x: x and 255)
+ tmpImg = tmpImg.filter(ImageFilter.MinFilter(size=3))
+ tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=5))
+ tmpImg = tmpImg.point(lambda x: (x >= 48 * power) and x)
+ self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
+
+ def cropMargin(self, power):
+ if self.fill != 'white':
+ tmpImg = self.image.convert(mode='L')
+ else:
+ tmpImg = ImageOps.invert(self.image.convert(mode='L'))
+ tmpImg = tmpImg.filter(ImageFilter.GaussianBlur(radius=3))
+ tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
+ self.image = self.image.crop(self.getBoundingBox(tmpImg)) if tmpImg.getbbox() else self.image
class Cover:
diff --git a/kcc/shared.py b/kcc/shared.py
index c1495024..88d1782d 100644
--- a/kcc/shared.py
+++ b/kcc/shared.py
@@ -144,8 +144,7 @@ def removeFromZIP(zipfname, *filenames):
def sanitizeTrace(traceback):
return ''.join(format_tb(traceback))\
- .replace('C:\\Users\\pawel\\Documents\\Projekty\\KCC\\', '') \
- .replace('C:\\Users\\Paweł\\Documents\\Projekty\\KCC\\', '') \
+ .replace('C:\\Users\\Pawel\\Documents\\Projekty\\KCC\\', '') \
.replace('C:\\Python35\\', '')\
.replace('c:\\python35\\', '')
diff --git a/other/osx/Info.plist b/other/osx/Info.plist
index 0194378c..d883b4d2 100644
--- a/other/osx/Info.plist
+++ b/other/osx/Info.plist
@@ -9,7 +9,7 @@
CFBundleExecutable
MacOS/Kindle Comic Converter
CFBundleGetInfoString
- KindleComicConverter 5.1.3, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski
+ KindleComicConverter 5.2, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski
CFBundleIconFile
comic2ebook.icns
CFBundleIdentifier
@@ -21,11 +21,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 5.1.3
+ 5.2.0
CFBundleSignature
????
CFBundleVersion
- 5.1.3
+ 5.2.0
LSEnvironment
PATH