diff --git a/README.md b/README.md
index cfaf2145..3f8b6767 100644
--- a/README.md
+++ b/README.md
@@ -90,7 +90,10 @@ Options:
-f FORMAT, --format=FORMAT
Output format (Available options: Auto, MOBI, EPUB,
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:
-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)
## CHANGELOG
+####5.2.1:
+* Improved directory parsing
+* Tweaked margin detection algorithm
+* Improved error reporting
+
####5.2:
* Added new Panel View options
* Implemented new margin detection algorithm
diff --git a/gui/KCC.ui b/gui/KCC.ui
index 3bdd9173..0b94d429 100644
--- a/gui/KCC.ui
+++ b/gui/KCC.ui
@@ -228,12 +228,12 @@
-
-
+
- <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>
+ <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>
- PNG output
+ Output split
@@ -510,7 +510,7 @@
upscaleBox
gammaBox
borderBox
- noDitheringBox
+ outputSplit
colorBox
editorButton
wikiButton
diff --git a/kcc.iss b/kcc.iss
index 28a73176..9f0e8e63 100644
--- a/kcc.iss
+++ b/kcc.iss
@@ -1,5 +1,5 @@
#define MyAppName "Kindle Comic Converter"
-#define MyAppVersion "5.2"
+#define MyAppVersion "5.2.1"
#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 dcc9e1bc..b60c2be3 100644
--- a/kcc/KCC_gui.py
+++ b/kcc/KCC_gui.py
@@ -21,17 +21,15 @@
import sys
from urllib.parse import unquote
from urllib.request import urlopen, urlretrieve, Request
-from time import sleep, time
-from datetime import datetime
+from time import sleep
from shutil import move
from subprocess import STDOUT, PIPE
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 copy import copy
from distutils.version import StrictVersion
from xml.sax.saxutils import escape
-from platform import platform
from raven import Client
from .shared import md5Checksum, HTMLStripper, sanitizeTrace, saferRemove
from . import __version__
@@ -270,8 +268,8 @@ def run(self):
options.white_borders = True
elif GUI.borderBox.checkState() == 2:
options.black_borders = True
- if GUI.noDitheringBox.isChecked():
- options.forcepng = True
+ if GUI.outputSplit.isChecked():
+ options.batchsplit = 2
if GUI.colorBox.isChecked():
options.forcecolor = True
if GUI.currentMode > 2:
@@ -319,10 +317,15 @@ def run(self):
GUI.progress.content = ''
self.errors = True
_, _, traceback = sys.exc_info()
+ if len(err.args) == 1:
+ MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
+ % (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
+ else:
+ MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
+ % (jobargv[-1], str(err.args[0]), err.args[1]), 'error')
+ GUI.sentry.extra_context({'realTraceback': err.args[1]})
if ' is corrupted.' not in str(err):
GUI.sentry.captureException()
- MW.showDialog.emit("Error during conversion %s:\n\n%s\n\nTraceback:\n%s"
- % (jobargv[-1], str(err), sanitizeTrace(traceback)), 'error')
MW.addMessage.emit('Error during conversion! Please consult '
'wiki '
'for more details.', 'error', False)
@@ -528,7 +531,7 @@ def selectFileMetaEditor(self):
def clearJobs(self):
GUI.jobList.clear()
- # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
+ # noinspection PyCallByClass,PyTypeChecker
def openWiki(self):
QtGui.QDesktopServices.openUrl(QtCore.QUrl('https://github.com/ciromattia/kcc/wiki'))
@@ -646,6 +649,11 @@ def changeFormat(self, outputFormat=None):
else:
GUI.formatBox.setCurrentIndex(profile['DefaultFormat'])
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):
s = HTMLStripper()
@@ -700,7 +708,6 @@ def convertStart(self):
self.conversionAlive = False
self.worker.sync()
else:
- # noinspection PyArgumentList
if QtWidgets.QApplication.keyboardModifiers() == QtCore.Qt.ShiftModifier:
dname = QtWidgets.QFileDialog.getExistingDirectory(MW, 'Select output directory', self.lastPath)
if dname != '':
@@ -762,7 +769,7 @@ def saveSettings(self, event):
'upscaleBox': GUI.upscaleBox.checkState(),
'borderBox': GUI.borderBox.checkState(),
'webtoonBox': GUI.webtoonBox.checkState(),
- 'noDitheringBox': GUI.noDitheringBox.checkState(),
+ 'outputSplit': GUI.outputSplit.checkState(),
'colorBox': GUI.colorBox.checkState(),
'widthBox': GUI.widthBox.value(),
'heightBox': GUI.heightBox.value(),
@@ -848,7 +855,6 @@ def detectKindleGen(self, startup=False):
else:
self.addMessage('Download it and place executable in /usr/local/bin directory.', 'error')
- # noinspection PyArgumentList
def __init__(self, KCCAplication, KCCWindow):
global APP, MW, GUI
APP = KCCAplication
diff --git a/kcc/KCC_ui.py b/kcc/KCC_ui.py
index 5e718ef2..6e2ed50a 100644
--- a/kcc/KCC_ui.py
+++ b/kcc/KCC_ui.py
@@ -97,9 +97,9 @@ def setupUi(self, mainWindow):
self.borderBox.setTristate(True)
self.borderBox.setObjectName("borderBox")
self.gridLayout_2.addWidget(self.borderBox, 2, 0, 1, 1)
- self.noDitheringBox = QtWidgets.QCheckBox(self.optionWidget)
- self.noDitheringBox.setObjectName("noDitheringBox")
- self.gridLayout_2.addWidget(self.noDitheringBox, 2, 1, 1, 1)
+ self.outputSplit = QtWidgets.QCheckBox(self.optionWidget)
+ self.outputSplit.setObjectName("outputSplit")
+ self.gridLayout_2.addWidget(self.outputSplit, 2, 1, 1, 1)
self.colorBox = QtWidgets.QCheckBox(self.optionWidget)
self.colorBox.setObjectName("colorBox")
self.gridLayout_2.addWidget(self.colorBox, 2, 2, 1, 1)
@@ -219,8 +219,8 @@ def setupUi(self, mainWindow):
mainWindow.setTabOrder(self.webtoonBox, self.upscaleBox)
mainWindow.setTabOrder(self.upscaleBox, self.gammaBox)
mainWindow.setTabOrder(self.gammaBox, self.borderBox)
- mainWindow.setTabOrder(self.borderBox, self.noDitheringBox)
- mainWindow.setTabOrder(self.noDitheringBox, self.colorBox)
+ mainWindow.setTabOrder(self.borderBox, self.outputSplit)
+ mainWindow.setTabOrder(self.outputSplit, self.colorBox)
mainWindow.setTabOrder(self.colorBox, self.editorButton)
mainWindow.setTabOrder(self.editorButton, self.wikiButton)
mainWindow.setTabOrder(self.wikiButton, self.jobList)
@@ -251,8 +251,8 @@ def retranslateUi(self, mainWindow):
self.gammaBox.setText(_translate("mainWindow", "Custom gamma"))
self.borderBox.setToolTip(_translate("mainWindow", "
Unchecked - Autodetection
Color of margins fill will be detected automatically.
Indeterminate - White
Margins will be filled with white color.
Checked - Black
Margins will be filled with black color.
"))
self.borderBox.setText(_translate("mainWindow", "W/B margins"))
- self.noDitheringBox.setToolTip(_translate("mainWindow", "Create PNG files instead JPEG.
Quality increase is not noticeable on most of devices.
Output files might be smaller.
MOBI conversion will be much slower.
"))
- self.noDitheringBox.setText(_translate("mainWindow", "PNG output"))
+ self.outputSplit.setToolTip(_translate("mainWindow", "Unchecked - Automatic mode
Output will be splitted automatically.
Checked - Volume mode
Every subdirectory will be considered as separate volume.
"))
+ self.outputSplit.setText(_translate("mainWindow", "Output split"))
self.colorBox.setToolTip(_translate("mainWindow", "Disable conversion to grayscale.
"))
self.colorBox.setText(_translate("mainWindow", "Color mode"))
self.gammaLabel.setText(_translate("mainWindow", "Gamma: Auto"))
diff --git a/kcc/__init__.py b/kcc/__init__.py
index d4f4bd0a..8fd5a243 100644
--- a/kcc/__init__.py
+++ b/kcc/__init__.py
@@ -1,4 +1,4 @@
-__version__ = '5.2'
+__version__ = '5.2.1'
__license__ = 'ISC'
__copyright__ = '2012-2016, Ciro Mattia Gonano , Pawel Jastrzebski '
__docformat__ = 'restructuredtext en'
diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py
index da9c906f..e16ea4f2 100755
--- a/kcc/comic2ebook.py
+++ b/kcc/comic2ebook.py
@@ -46,7 +46,7 @@
from scandir import walk
except ImportError:
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 image
from . import cbxarchive
@@ -93,7 +93,6 @@ def buildHTML(path, imgfile, imgfilepath):
additionalStyle = 'background-color:#000000;'
else:
additionalStyle = 'background-color:#FFFFFF;'
- htmlpath = ''
postfix = ''
backref = 1
head = path
@@ -483,7 +482,7 @@ def imgDirectoryProcessing(path):
raise UserWarning("Conversion interrupted.")
if len(workerOutput) > 0:
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:
if os.path.isfile(file):
saferRemove(file)
@@ -493,7 +492,7 @@ def imgDirectoryProcessing(path):
def imgFileProcessingTick(output):
- if isinstance(output, str):
+ if isinstance(output, tuple):
workerOutput.append(output)
workerPool.terminate()
else:
@@ -527,7 +526,7 @@ def imgFileProcessing(work):
output.append(img.saveToDir())
return output
except Exception:
- return str(sys.exc_info()[:2])
+ return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def getWorkFolder(afile):
@@ -734,63 +733,25 @@ def sanitizePermissions(filetree):
os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC)
-# noinspection PyUnboundLocalVariable
def splitDirectory(path):
- # Detect directory stucture
- for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 0):
- subdirectoryNumber = len(dirs)
- filesNumber = len(files)
- if subdirectoryNumber == 0:
- # No subdirectories
- mode = 0
+ level = -1
+ for root, _, files in os.walk(os.path.join(path, 'OEBPS', 'Images')):
+ for f in files:
+ if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif'):
+ newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep)
+ if level != -1 and level != newLevel:
+ level = 0
+ break
+ else:
+ level = newLevel
+ if level > 0:
+ splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), level)
+ path = [path]
+ for tome in splitter:
+ path.append(tome)
+ return path
else:
- if filesNumber > 0:
- print('WARNING: Automatic output splitting failed.')
- if GUI:
- GUI.addMessage.emit('Automatic output splitting failed. '
- 'More details.', '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. '
- 'More details.', '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. '
- 'More details.', 'warning', False)
- GUI.addMessage.emit('', '', False)
- return [path]
- # Split directories
- splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), mode)
- path = [path]
- for tome in splitter:
- path.append(tome)
- return path
+ raise UserWarning('Unsupported directory structure.')
def splitProcess(path, mode):
@@ -801,10 +762,15 @@ def splitProcess(path, mode):
targetSize = 104857600
else:
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 name in files:
- size = os.path.getsize(os.path.join(root, name))
+ for name in files if mode == 1 else dirs:
+ if mode == 1:
+ size = os.path.getsize(os.path.join(root, name))
+ else:
+ size = getDirectorySize(os.path.join(root, name))
if currentSize + size > targetSize:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
@@ -813,48 +779,16 @@ def splitProcess(path, mode):
currentSize += size
if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name))
- elif mode == 1:
+ else:
+ firstTome = True
for root, dirs, files in walkLevel(path, 0):
for name in dirs:
- size = getDirectorySize(os.path.join(root, name))
- if currentSize + size > targetSize:
+ if not firstTome:
currentTarget, pathRoot = createNewTome()
output.append(pathRoot)
- currentSize = size
- else:
- currentSize += size
- if path != currentTarget:
move(os.path.join(root, name), os.path.join(currentTarget, name))
- elif mode == 2:
- firstTome = True
- for root, dirs, files in walkLevel(path, 0):
- 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:
- currentTarget, pathRoot = createNewTome()
- output.append(pathRoot)
- move(os.path.join(root, name), os.path.join(currentTarget, name))
- else:
- firstTome = False
+ firstTome = False
return output
@@ -947,8 +881,9 @@ def makeParser():
help="Comic title [Default=filename or directory name]")
outputOptions.add_option("-f", "--format", action="store", dest="format", 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,
- help="Split output into multiple files"),
+ outputOptions.add_option("-b", "--batchsplit", type="int", dest="batchsplit", default="0",
+ 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,
help="Resize images smaller than device's resolution")
@@ -1006,8 +941,8 @@ def checkOptions():
if options.black_borders:
options.bordersColor = 'black'
# Splitting MOBI is not optional
- if options.format == 'MOBI':
- options.batchsplit = True
+ if options.format == 'MOBI' and options.batchsplit != 2:
+ options.batchsplit = 1
# 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
@@ -1092,8 +1027,8 @@ def makeBook(source, qtGUI=None):
getComicInfo(os.path.join(path, "OEBPS", "Images"), source)
detectCorruption(os.path.join(path, "OEBPS", "Images"), source)
if options.webtoon:
- if image.ProfileData.Profiles[options.profile][1][1] > 1000:
- y = 1000
+ if image.ProfileData.Profiles[options.profile][1][1] > 1024:
+ y = 1024
else:
y = image.ProfileData.Profiles[options.profile][1][1]
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'))
if 'Ko' in options.profile and options.format == 'CBZ':
sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images'))
- if options.batchsplit:
+ if options.batchsplit > 0:
tomes = splitDirectory(path)
else:
tomes = [path]
diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py
index def82509..5e2b4f98 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, saferRemove
+from .shared import getImageFileName, walkLevel, walkSort, saferRemove, sanitizeTrace
try:
from PyQt5 import QtCore
except ImportError:
@@ -81,7 +81,7 @@ def mergeDirectory(work):
savePath = os.path.split(imagesValid[0])
result.save(os.path.join(savePath[0], os.path.splitext(savePath[1])[0] + '.png'), 'PNG')
except Exception:
- return str(sys.exc_info()[1])
+ return str(sys.exc_info()[1]), sanitizeTrace(sys.exc_info()[2])
def sanitizePanelSize(panel, opt):
@@ -205,7 +205,7 @@ def splitImage(work):
pageNumber += 1
saferRemove(filePath)
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):
@@ -267,7 +267,7 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.")
if len(mergeWorkerOutput) > 0:
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...")
for root, dirs, files in walk(options.targetDir, False):
for name in files:
@@ -290,7 +290,7 @@ def main(argv=None, qtGUI=None):
raise UserWarning("Conversion interrupted.")
if len(splitWorkerOutput) > 0:
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:
rmtree(options.sourceDir)
move(options.targetDir, options.sourceDir)
diff --git a/kcc/image.py b/kcc/image.py
index 009b2bbf..682ae454 100755
--- a/kcc/image.py
+++ b/kcc/image.py
@@ -318,7 +318,7 @@ def cropPageNumber(self, power):
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)
+ tmpImg = tmpImg.point(lambda x: (x >= 16 * power) and x)
self.image = self.image.crop(tmpImg.getbbox()) if tmpImg.getbbox() else self.image
def cropMargin(self, power):
diff --git a/kcc/pdfjpgextract.py b/kcc/pdfjpgextract.py
index f4d5813c..06403adb 100644
--- a/kcc/pdfjpgextract.py
+++ b/kcc/pdfjpgextract.py
@@ -61,7 +61,6 @@ def extract(self):
iend += endfix
jpg = pdf[istart:iend]
jpgfile = open(self.path + "/jpg%d.jpg" % njpg, "wb")
- # noinspection PyTypeChecker
jpgfile.write(jpg)
jpgfile.close()
njpg += 1
diff --git a/kcc/shared.py b/kcc/shared.py
index 88d1782d..c228d30f 100644
--- a/kcc/shared.py
+++ b/kcc/shared.py
@@ -144,7 +144,10 @@ def removeFromZIP(zipfname, *filenames):
def sanitizeTrace(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\\', '')
diff --git a/other/osx/Info.plist b/other/osx/Info.plist
index d883b4d2..0eb9c942 100644
--- a/other/osx/Info.plist
+++ b/other/osx/Info.plist
@@ -9,7 +9,7 @@
CFBundleExecutable
MacOS/Kindle Comic Converter
CFBundleGetInfoString
- KindleComicConverter 5.2, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski
+ KindleComicConverter 5.2.1, written 2012-2016 by Ciro Mattia Gonano and Pawel Jastrzebski
CFBundleIconFile
comic2ebook.icns
CFBundleIdentifier
@@ -21,11 +21,11 @@
CFBundlePackageType
APPL
CFBundleShortVersionString
- 5.2.0
+ 5.2.1
CFBundleSignature
????
CFBundleVersion
- 5.2.0
+ 5.2.1
LSEnvironment
PATH