diff options
author | Paweł Jastrzębski <pawelj@iosphe.re> | 2014-07-18 10:29:14 +0200 |
---|---|---|
committer | Paweł Jastrzębski <pawelj@iosphe.re> | 2014-07-18 10:29:14 +0200 |
commit | 15a240cceae92552e7d0a6e5d3529fd24dbd9434 (patch) | |
tree | f176a3435f33c16d48a6c7eb9685569ca29c2616 | |
parent | Updated README (diff) | |
parent | Updated README + version bump (diff) | |
download | kcc-15a240cceae92552e7d0a6e5d3529fd24dbd9434.tar.gz kcc-15a240cceae92552e7d0a6e5d3529fd24dbd9434.tar.bz2 kcc-15a240cceae92552e7d0a6e5d3529fd24dbd9434.zip |
Merge pull request #103 from ciromattia/4.x
Version 4.2
-rw-r--r-- | README.md | 11 | ||||
-rwxr-xr-x | kcc-c2e.py | 12 | ||||
-rwxr-xr-x | kcc-c2p.py | 12 | ||||
-rw-r--r-- | kcc.iss | 3 | ||||
-rwxr-xr-x | kcc.py | 8 | ||||
-rw-r--r-- | kcc/KCC_gui.py | 55 | ||||
-rw-r--r-- | kcc/__init__.py | 2 | ||||
-rwxr-xr-x | kcc/comic2ebook.py | 992 | ||||
-rw-r--r-- | kcc/comic2panel.py | 14 | ||||
-rwxr-xr-x | kcc/image.py | 43 | ||||
-rwxr-xr-x | setup.py | 2 | ||||
-rwxr-xr-x | setup.sh | 2 |
12 files changed, 598 insertions, 558 deletions
diff --git a/README.md b/README.md index 814ba77..eef9924 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ If you find **KCC** valuable you can consider donating to the authors: ## BINARY RELEASES You can find the latest released binary at the following links: -- **Windows:** [http://kcc.iosphe.re/Windows/](http://kcc.iosphe.re/Windows/) +- **Windows (Vista or newer):** [http://kcc.iosphe.re/Windows/](http://kcc.iosphe.re/Windows/) - **Linux:** [http://kcc.iosphe.re/Linux/](http://kcc.iosphe.re/Linux/) - **OS X (10.8+):** [http://kcc.iosphe.re/OSX/](http://kcc.iosphe.re/OSX/) @@ -46,7 +46,7 @@ You can find the latest released binary at the following links: ### For running from source: - Python 3.3+ - [PyQt5](http://www.riverbankcomputing.co.uk/software/pyqt/download5) 5.2.0+ -- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.3.0+ +- [Pillow](http://pypi.python.org/pypi/Pillow/) 2.5.0+ - [psutil](https://pypi.python.org/pypi/psutil) 2.0+ - [python-slugify](http://pypi.python.org/pypi/python-slugify) @@ -354,6 +354,13 @@ The app relies and includes the following scripts/binaries: * Fixed _No optimization_ mode * Multiple small tweaks nad minor bug fixes +####4.2: +* Added [Manga Cover Database](http://manga.joentjuh.nl/) support +* Officially dropped Windows XP support +* Fixed _Other_ profile +* Fixed problems with page order on stock KOBO CBZ reader +* Many other small bug fixes and tweaks + ## KNOWN ISSUES Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues). diff --git a/kcc-c2e.py b/kcc-c2e.py index 4a6442f..a89fe1a 100755 --- a/kcc-c2e.py +++ b/kcc-c2e.py @@ -18,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' @@ -40,10 +40,10 @@ except ImportError: try: # noinspection PyUnresolvedReferences import PIL - if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): - missing.append('Pillow 2.3.0+') + if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): + missing.append('Pillow 2.5.0+') except ImportError: - missing.append('Pillow 2.3.0+') + missing.append('Pillow 2.5.0+') try: # noinspection PyUnresolvedReferences import slugify @@ -63,10 +63,10 @@ if len(missing) > 0: exit(1) from multiprocessing import freeze_support -from kcc.comic2ebook import main, Copyright +from kcc.comic2ebook import main if __name__ == "__main__": freeze_support() - Copyright() + print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) main(sys.argv[1:]) sys.exit(0) \ No newline at end of file diff --git a/kcc-c2p.py b/kcc-c2p.py index 3976e2e..a50500c 100755 --- a/kcc-c2p.py +++ b/kcc-c2p.py @@ -18,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' @@ -33,10 +33,10 @@ missing = [] try: # noinspection PyUnresolvedReferences import PIL - if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): - missing.append('Pillow 2.3.0+') + if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): + missing.append('Pillow 2.5.0+') except ImportError: - missing.append('Pillow 2.3.0+') + missing.append('Pillow 2.5.0+') if len(missing) > 0: try: # noinspection PyUnresolvedReferences @@ -51,10 +51,10 @@ if len(missing) > 0: exit(1) from multiprocessing import freeze_support -from kcc.comic2panel import main, Copyright +from kcc.comic2panel import main if __name__ == "__main__": freeze_support() - Copyright() + print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) main(sys.argv[1:]) sys.exit(0) \ No newline at end of file diff --git a/kcc.iss b/kcc.iss index bd78427..5b7b067 100644 --- a/kcc.iss +++ b/kcc.iss @@ -1,5 +1,5 @@ #define MyAppName "Kindle Comic Converter" -#define MyAppVersion "4.1" +#define MyAppVersion "4.2" #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski" #define MyAppURL "http://kcc.iosphe.re/" #define MyAppExeName "KCC.exe" @@ -30,6 +30,7 @@ UninstallDisplayIcon={app}\{#MyAppExeName} ChangesAssociations=True InfoAfterFile=other\InstallWarning.rtf SignTool=SignTool /d $q{#MyAppName}$q /du $q{#MyAppURL}$q $f +MinVersion=0,6.0 [Languages] Name: "english"; MessagesFile: "compiler:Default.isl" diff --git a/kcc.py b/kcc.py index 1264841..9e546b9 100755 --- a/kcc.py +++ b/kcc.py @@ -18,7 +18,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' @@ -47,10 +47,10 @@ except ImportError: try: # noinspection PyUnresolvedReferences import PIL - if tuple(map(int, ('2.3.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): - missing.append('Pillow 2.3.0+') + if tuple(map(int, ('2.5.0'.split(".")))) > tuple(map(int, (PIL.PILLOW_VERSION.split(".")))): + missing.append('Pillow 2.5.0+') except ImportError: - missing.append('Pillow 2.3.0+') + missing.append('Pillow 2.5.0+') try: # noinspection PyUnresolvedReferences import slugify diff --git a/kcc/KCC_gui.py b/kcc/KCC_gui.py index 1102067..30a4e91 100644 --- a/kcc/KCC_gui.py +++ b/kcc/KCC_gui.py @@ -17,7 +17,7 @@ # TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' @@ -38,6 +38,7 @@ from xml.dom.minidom import parse from html.parser import HTMLParser from psutil import virtual_memory, Popen, Process from uuid import uuid4 +from copy import copy from .shared import md5Checksum from . import comic2ebook from . import dualmetafix @@ -196,7 +197,6 @@ class VersionThread(QtCore.QThread): def run(self): try: - sleep(1) XML = urlopen('http://kcc.iosphe.re/Version.php') XML = parse(XML) except Exception: @@ -422,9 +422,6 @@ class WorkerThread(QtCore.QThread): if GUI.ColorBox.isChecked(): options.forcecolor = True - comic2ebook.options = options - comic2ebook.checkOptions() - for i in range(GUI.JobList.count()): # Make sure that we don't consider any system message as job to do if GUI.JobList.item(i).icon().isNull(): @@ -446,7 +443,8 @@ class WorkerThread(QtCore.QThread): jobargv = list(argv) jobargv.append(job) try: - comic2ebook.options.title = 'defaulttitle' + comic2ebook.options = copy(options) + comic2ebook.checkOptions() outputPath = comic2ebook.makeBook(job, self) MW.hideProgressBar.emit() except UserWarning as warn: @@ -493,7 +491,6 @@ class WorkerThread(QtCore.QThread): worker.signals.result.connect(self.addResult) self.pool.start(worker) self.pool.waitForDone() - sleep(0.5) self.kindlegenErrorCode = [0] for errors in self.workerOutput: if errors[0] != 0: @@ -503,7 +500,6 @@ class WorkerThread(QtCore.QThread): for item in outputPath: if os.path.exists(item): os.remove(item) - sleep(1) if os.path.exists(item.replace('.epub', '.mobi')): os.remove(item.replace('.epub', '.mobi')) self.clean() @@ -521,7 +517,6 @@ class WorkerThread(QtCore.QThread): worker.signals.result.connect(self.addResult) self.pool.start(worker) self.pool.waitForDone() - sleep(0.5) for success in self.workerOutput: if not success[0]: self.errors = True @@ -555,7 +550,6 @@ class WorkerThread(QtCore.QThread): for item in outputPath: if os.path.exists(item): os.remove(item) - sleep(1) if os.path.exists(item.replace('.epub', '.mobi')): os.remove(item.replace('.epub', '.mobi')) MW.addMessage.emit('KindleGen failed to create MOBI!', 'error', False) @@ -834,6 +828,9 @@ class KCCGUI(KCC_ui.Ui_KCC): if value == 2 and 'Kobo' in str(GUI.DeviceBox.currentText()): self.addMessage('Kobo devices can\'t use ultra quality mode!', 'warning') GUI.QualityBox.setCheckState(0) + elif value == 2 and 'CBZ' in str(GUI.FormatBox.currentText()): + self.addMessage('CBZ format don\'t support ultra quality mode!', 'warning') + GUI.QualityBox.setCheckState(0) def changeGamma(self, value): value = float(value) @@ -861,7 +858,7 @@ class KCCGUI(KCC_ui.Ui_KCC): GUI.AdvModeButton.setEnabled(True) if self.currentMode == 3: self.modeBasic() - self.changeFormat() + self.changeFormat(event=False) GUI.GammaSlider.setValue(0) self.changeGamma(0) if profile['DefaultUpscale']: @@ -870,19 +867,12 @@ class KCCGUI(KCC_ui.Ui_KCC): self.addMessage('<a href="https://github.com/ciromattia/kcc/wiki/NonKindle-devices">' 'List of supported Non-Kindle devices.</a>', 'info') - def changeFormat(self, outputFormat=None): + def changeFormat(self, outputFormat=None, event=True): profile = GUI.profiles[str(GUI.DeviceBox.currentText())] if outputFormat is not None: GUI.FormatBox.setCurrentIndex(outputFormat) else: - if GUI.FormatBox.count() == 3: - GUI.FormatBox.setCurrentIndex(profile['DefaultFormat']) - else: - if profile['DefaultFormat'] != 0: - tmpFormat = profile['DefaultFormat'] - 1 - else: - tmpFormat = 0 - GUI.FormatBox.setCurrentIndex(tmpFormat) + GUI.FormatBox.setCurrentIndex(profile['DefaultFormat']) if GUI.WebtoonBox.isChecked(): GUI.MangaBox.setEnabled(False) GUI.QualityBox.setEnabled(False) @@ -895,6 +885,10 @@ class KCCGUI(KCC_ui.Ui_KCC): if GUI.ProcessingBox.isChecked(): GUI.QualityBox.setEnabled(False) GUI.QualityBox.setChecked(False) + if event and GUI.QualityBox.isEnabled() and 'CBZ' in str(GUI.FormatBox.currentText()) and\ + GUI.QualityBox.checkState() == 2: + self.addMessage('CBZ format don\'t support ultra quality mode!', 'warning') + GUI.QualityBox.setCheckState(0) def stripTags(self, html): s = HTMLStripper() @@ -973,6 +967,15 @@ class KCCGUI(KCC_ui.Ui_KCC): self.addMessage('Target resolution is not set!', 'error') self.needClean = True return + if str(GUI.FormatBox.currentText()) == 'MOBI' and not GUI.KindleGen: + self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">' + '<b>KindleGen</b></a>! MOBI conversion is not possible!', 'error') + if sys.platform.startswith('win'): + self.addMessage('Download it and place EXE in KCC directory.', 'error') + else: + self.addMessage('Download it, and place executable in /usr/local/bin directory.', 'error') + self.needClean = True + return self.worker.start() def hideProgressBar(self): @@ -1196,7 +1199,6 @@ class KCCGUI(KCC_ui.Ui_KCC): kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) if kindleGenExitCode.wait() == 0: self.KindleGen = True - formats = ['MOBI', 'EPUB', 'CBZ'] versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) for line in versionCheck.stdout: line = line.decode("utf-8") @@ -1210,13 +1212,6 @@ class KCCGUI(KCC_ui.Ui_KCC): break else: self.KindleGen = False - formats = ['EPUB', 'CBZ'] - if sys.platform.startswith('win'): - self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">' - 'kindlegen</a> in KCC directory! MOBI creation will be disabled.', 'warning') - else: - self.addMessage('Cannot find <a href="http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211">' - 'kindlegen</a> in PATH! MOBI creation will be disabled.', 'warning') rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, shell=True) rarExitCode = rarExitCode.wait() if rarExitCode == 0 or rarExitCode == 7: @@ -1271,7 +1266,7 @@ class KCCGUI(KCC_ui.Ui_KCC): GUI.DeviceBox.addItem(self.icons.deviceKobo, profile) else: GUI.DeviceBox.addItem(self.icons.deviceKindle, profile) - for f in formats: + for f in ['MOBI', 'EPUB', 'CBZ']: GUI.FormatBox.addItem(eval('self.icons.' + f + 'Format'), f) if self.lastDevice > GUI.DeviceBox.count(): self.lastDevice = 0 @@ -1282,7 +1277,7 @@ class KCCGUI(KCC_ui.Ui_KCC): GUI.DeviceBox.setCurrentIndex(self.lastDevice) self.changeDevice() if self.currentFormat != self.profiles[str(GUI.DeviceBox.currentText())]['DefaultFormat']: - self.changeFormat(self.currentFormat) + self.changeFormat(self.currentFormat, False) for option in self.options: if str(option) == "customWidth": GUI.customWidth.setText(str(self.options[option])) diff --git a/kcc/__init__.py b/kcc/__init__.py index 90c588a..34c10c7 100644 --- a/kcc/__init__.py +++ b/kcc/__init__.py @@ -1,4 +1,4 @@ -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' \ No newline at end of file diff --git a/kcc/comic2ebook.py b/kcc/comic2ebook.py index c654071..73b798b 100755 --- a/kcc/comic2ebook.py +++ b/kcc/comic2ebook.py @@ -18,14 +18,16 @@ # PERFORMANCE OF THIS SOFTWARE. # -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' import os import sys -from re import split, sub +from json import loads +from urllib.request import Request, urlopen +from re import split, sub, compile from stat import S_IWRITE, S_IREAD, S_IEXEC from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED from tempfile import mkdtemp @@ -47,154 +49,136 @@ from . import cbxarchive from . import pdfjpgextract +def main(argv=None): + global options + parser = makeParser() + options, args = parser.parse_args(argv) + checkOptions() + if len(args) != 1: + parser.print_help() + return + outputPath = makeBook(args[0]) + return outputPath + + def buildHTML(path, imgfile, imgfilepath): imgfilepath = md5Checksum(imgfilepath) filename = getImageFileName(imgfile) - if filename is not None: - if options.imgproc: - if "Rotated" in theGreatIndex[imgfilepath]: - rotatedPage = True - else: - rotatedPage = False - if "NoPanelView" in theGreatIndex[imgfilepath]: - noPV = True - else: - noPV = False - if "NoHorizontalPanelView" in theGreatIndex[imgfilepath]: - noHorizontalPV = True - else: - noHorizontalPV = False - if "NoVerticalPanelView" in theGreatIndex[imgfilepath]: - noVerticalPV = True - else: - noVerticalPV = False + if options.imgproc: + if "Rotated" in options.imgIndex[imgfilepath]: + rotatedPage = True else: rotatedPage = False + if "NoPanelView" in options.imgIndex[imgfilepath]: + noPV = True + else: noPV = False + if "NoHorizontalPanelView" in options.imgIndex[imgfilepath]: + noHorizontalPV = True + else: noHorizontalPV = False + if "NoVerticalPanelView" in options.imgIndex[imgfilepath]: + noVerticalPV = True + else: noVerticalPV = False - htmlpath = '' - postfix = '' - backref = 1 - head = path - while True: - head, tail = os.path.split(head) - if tail == 'Images': - htmlpath = os.path.join(head, 'Text', postfix) - break - postfix = tail + "/" + postfix - backref += 1 - if not os.path.exists(htmlpath): - os.makedirs(htmlpath) - htmlfile = os.path.join(htmlpath, filename[0] + '.html') - f = open(htmlfile, "w", encoding='UTF-8') - f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ", - "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n", - "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", - "<head>\n", - "<title>", filename[0], "</title>\n", - "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n", - "<link href=\"", "../" * (backref - 1), - "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", - "</head>\n", - "<body>\n", - "<div class=\"fs\">\n", - "<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"", - imgfile, "\" class=\"singlePage\"/></div>\n" - ]) - if options.panelview and not noPV: - if not noHorizontalPV and not noVerticalPV: - if rotatedPage: - if options.righttoleft: - order = [1, 3, 2, 4] - else: - order = [2, 4, 1, 3] + else: + rotatedPage = False + noPV = False + noHorizontalPV = False + noVerticalPV = False + htmlpath = '' + postfix = '' + backref = 1 + head = path + while True: + head, tail = os.path.split(head) + if tail == 'Images': + htmlpath = os.path.join(head, 'Text', postfix) + break + postfix = tail + "/" + postfix + backref += 1 + if not os.path.exists(htmlpath): + os.makedirs(htmlpath) + htmlfile = os.path.join(htmlpath, filename[0] + '.html') + f = open(htmlfile, "w", encoding='UTF-8') + f.writelines(["<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" ", + "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">\n", + "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n", + "<head>\n", + "<title>", filename[0], "</title>\n", + "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n", + "<link href=\"", "../" * (backref - 1), + "style.css\" type=\"text/css\" rel=\"stylesheet\"/>\n", + "</head>\n", + "<body>\n", + "<div class=\"fs\">\n", + "<div><img src=\"", "../" * backref, "Images/", postfix, imgfile, "\" alt=\"", + imgfile, "\" class=\"singlePage\"/></div>\n" + ]) + if options.panelview and not noPV: + if not noHorizontalPV and not noVerticalPV: + if rotatedPage: + if options.righttoleft: + order = [1, 3, 2, 4] else: - if options.righttoleft: - order = [2, 1, 4, 3] - else: - order = [1, 2, 3, 4] - boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"] - elif noHorizontalPV and not noVerticalPV: - if rotatedPage: - if options.righttoleft: - order = [1, 2] - else: - order = [2, 1] + order = [2, 4, 1, 3] + else: + if options.righttoleft: + order = [2, 1, 4, 3] else: - order = [1, 2] - boxes = ["BoxT", "BoxB"] - elif not noHorizontalPV and noVerticalPV: - if rotatedPage: + order = [1, 2, 3, 4] + boxes = ["BoxTL", "BoxTR", "BoxBL", "BoxBR"] + elif noHorizontalPV and not noVerticalPV: + if rotatedPage: + if options.righttoleft: order = [1, 2] else: - if options.righttoleft: - order = [2, 1] - else: - order = [1, 2] - boxes = ["BoxL", "BoxR"] + order = [2, 1] else: - order = [1] - boxes = ["BoxC"] - for i in range(0, len(boxes)): - f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=", - "'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]), - "}'></a></div>\n"]) - if options.quality == 2: - imgfilepv = str.split(imgfile, ".") - imgfilepv[0] += "-hq" - imgfilepv = ".".join(imgfilepv) + order = [1, 2] + boxes = ["BoxT", "BoxB"] + elif not noHorizontalPV and noVerticalPV: + if rotatedPage: + order = [1, 2] else: - imgfilepv = imgfile - xl, yu, xr, yd = checkMargins(imgfilepath) - boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";", - "BoxTR": "right:" + xr + ";top:" + yu + ";", - "BoxBL": "left:" + xl + ";bottom:" + yd + ";", - "BoxBR": "right:" + xr + ";bottom:" + yd + ";", - "BoxT": "left:-25%;top:" + yu + ";", - "BoxB": "left:-25%;bottom:" + yd + ";", - "BoxL": "left:" + xl + ";top:-25%;", - "BoxR": "right:" + xr + ";top:-25%;", - "BoxC": "left:-25%;top:-25%;" - } - for box in boxes: - f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"", - "Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"", - "../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv, - "\"/></div></div>\n", - ]) - f.writelines(["</div>\n</body>\n</html>"]) - f.close() - return path, imgfile - - -def checkMargins(path): - if options.imgproc: - for flag in theGreatIndex[path]: - if "Margins-" in flag: - flag = flag.split('-') - xl = flag[1] - yu = flag[2] - xr = flag[3] - yd = flag[4] - if xl != "0": - xl = "-" + str(float(xl)/100) + "%" - else: - xl = "0%" - if xr != "0": - xr = "-" + str(float(xr)/100) + "%" - else: - xr = "0%" - if yu != "0": - yu = "-" + str(float(yu)/100) + "%" - else: - yu = "0%" - if yd != "0": - yd = "-" + str(float(yd)/100) + "%" + if options.righttoleft: + order = [2, 1] else: - yd = "0%" - return xl, yu, xr, yd - return '0%', '0%', '0%', '0%' + order = [1, 2] + boxes = ["BoxL", "BoxR"] + else: + order = [1] + boxes = ["BoxC"] + for i in range(0, len(boxes)): + f.writelines(["<div id=\"" + boxes[i] + "\"><a class=\"app-amzn-magnify\" data-app-amzn-magnify=", + "'{\"targetId\":\"" + boxes[i] + "-Panel-Parent\", \"ordinal\":" + str(order[i]), + "}'></a></div>\n"]) + if options.quality == 2: + imgfilepv = str.split(imgfile, ".") + imgfilepv[0] += "-hq" + imgfilepv = ".".join(imgfilepv) + else: + imgfilepv = imgfile + xl, yu, xr, yd = detectMargins(imgfilepath) + boxStyles = {"BoxTL": "left:" + xl + ";top:" + yu + ";", + "BoxTR": "right:" + xr + ";top:" + yu + ";", + "BoxBL": "left:" + xl + ";bottom:" + yd + ";", + "BoxBR": "right:" + xr + ";bottom:" + yd + ";", + "BoxT": "left:-25%;top:" + yu + ";", + "BoxB": "left:-25%;bottom:" + yd + ";", + "BoxL": "left:" + xl + ";top:-25%;", + "BoxR": "right:" + xr + ";top:-25%;", + "BoxC": "left:-25%;top:-25%;" + } + for box in boxes: + f.writelines(["<div id=\"" + box + "-Panel-Parent\" class=\"target-mag-parent\"><div id=\"", + "Generic-Panel\" class=\"target-mag\"><img style=\"" + boxStyles[box] + "\" src=\"", + "../" * backref, "Images/", postfix, imgfilepv, "\" alt=\"" + imgfilepv, + "\"/></div></div>\n", + ]) + f.writelines(["</div>\n</body>\n</html>"]) + f.close() + return path, imgfile def buildNCX(dstdir, title, chapters, chapterNames): @@ -305,133 +289,7 @@ def buildOPF(dstdir, title, filelist, cover=None): f.close() -def applyImgOptimization(img, opt, hqImage=None): - if not img.fill: - img.getImageFill(opt.webtoon) - if not opt.webtoon: - img.cropWhiteSpace() - if opt.cutpagenumbers and not opt.webtoon: - img.cutPageNumber() - img.optimizeImage(opt.gamma) - if hqImage: - img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, 0) - img.calculateBorder(hqImage, True) - else: - img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, opt.quality) - if opt.panelview: - if opt.quality == 0: - img.calculateBorder(img) - elif opt.quality == 1: - img.calculateBorder(img, True) - if opt.forcepng and not opt.forcecolor: - img.quantizeImage() - - -def dirImgProcess(path): - global workerPool, workerOutput, theGreatIndex, theGreatWipe - workerPool = Pool() - workerOutput = [] - work = [] - theGreatIndex = {} - theGreatWipe = [] - pagenumber = 0 - for (dirpath, dirnames, filenames) in os.walk(path): - for afile in filenames: - if getImageFileName(afile) is not None: - pagenumber += 1 - work.append([afile, dirpath, options]) - if GUI: - GUI.progressBarTick.emit(str(pagenumber)) - if len(work) > 0: - for i in work: - workerPool.apply_async(func=fileImgProcess, args=(i, ), callback=fileImgProcess_tick) - workerPool.close() - workerPool.join() - if GUI and not GUI.conversionAlive: - rmtree(os.path.join(path, '..', '..'), True) - raise UserWarning("Conversion interrupted.") - if len(workerOutput) > 0: - rmtree(os.path.join(path, '..', '..'), True) - raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0]) - for file in theGreatWipe: - if os.path.isfile(file): - os.remove(file) - else: - rmtree(os.path.join(path, '..', '..'), True) - raise UserWarning("Source directory is empty.") - - -def fileImgProcess_tick(output): - if isinstance(output, str): - workerOutput.append(output) - workerPool.terminate() - else: - for page in output: - if page is not None: - if isinstance(page, str): - theGreatWipe.append(page) - else: - theGreatIndex[page[0]] = page[1] - if GUI: - GUI.progressBarTick.emit('tick') - if not GUI.conversionAlive: - workerPool.terminate() - - -def fileImgProcess(work): - try: - afile = work[0] - dirpath = work[1] - opt = work[2] - output = [] - img = image.ComicPage(os.path.join(dirpath, afile), opt.profileData) - if opt.quality == 2: - wipe = False - else: - wipe = True - if opt.nosplitrotate: - splitter = None - else: - splitter = img.splitPage(dirpath, opt.righttoleft, opt.rotate) - if splitter is not None: - img0 = image.ComicPage(splitter[0], opt.profileData) - applyImgOptimization(img0, opt) - output.append(img0.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - img1 = image.ComicPage(splitter[1], opt.profileData) - applyImgOptimization(img1, opt) - output.append(img1.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - if wipe: - output.append(img0.origFileName) - output.append(img1.origFileName) - if opt.quality == 2: - img0b = image.ComicPage(splitter[0], opt.profileData, img0.fill) - applyImgOptimization(img0b, opt, img0) - output.append(img0b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - img1b = image.ComicPage(splitter[1], opt.profileData, img1.fill) - applyImgOptimization(img1b, opt, img1) - output.append(img1b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - output.append(img0.origFileName) - output.append(img1.origFileName) - output.append(img.origFileName) - else: - applyImgOptimization(img, opt) - output.append(img.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - if wipe: - output.append(img.origFileName) - if opt.quality == 2: - img2 = image.ComicPage(os.path.join(dirpath, afile), opt.profileData, img.fill) - if img.rotated: - img2.image = img2.image.rotate(90) - img2.rotated = True - applyImgOptimization(img2, opt, img) - output.append(img2.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) - output.append(img.origFileName) - return output - except Exception: - return str(sys.exc_info()[1]) - - -def genEpubStruct(path, chapterNames): +def buildEPUB(path, chapterNames, tomeNumber): filelist = [] chapterlist = [] cover = None @@ -551,7 +409,7 @@ def genEpubStruct(path, chapterNames): chapter = False for afile in filenames: filename = getImageFileName(afile) - if filename is not None and not '-kcc-hq' in filename[0]: + if not '-kcc-hq' in filename[0]: filelist.append(buildHTML(dirpath, afile, os.path.join(dirpath, afile))) if not chapter: chapterlist.append((dirpath.replace('Images', 'Text'), filelist[-1][1])) @@ -559,7 +417,7 @@ def genEpubStruct(path, chapterNames): if cover is None: cover = os.path.join(os.path.join(path, 'OEBPS', 'Images'), 'cover' + getImageFileName(filelist[-1][1])[1]) - image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover) + image.Cover(os.path.join(filelist[-1][0], filelist[-1][1]), cover, options, tomeNumber) buildNCX(path, options.title, chapterlist, chapterNames) # Ensure we're sorting files alphabetically convert = lambda text: int(text) if text.isdigit() else text @@ -568,6 +426,131 @@ def genEpubStruct(path, chapterNames): buildOPF(path, options.title, filelist, cover) +def imgOptimization(img, opt, hqImage=None): + if not img.fill: + img.getImageFill(opt.webtoon) + if not opt.webtoon: + img.cropWhiteSpace() + if opt.cutpagenumbers and not opt.webtoon: + img.cutPageNumber() + img.optimizeImage(opt.gamma) + if hqImage: + img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, 0) + img.calculateBorder(hqImage, True) + else: + img.resizeImage(opt.upscale, opt.stretch, opt.bordersColor, opt.quality) + if opt.panelview: + if opt.quality == 0: + img.calculateBorder(img) + elif opt.quality == 1: + img.calculateBorder(img, True) + if opt.forcepng and not opt.forcecolor: + img.quantizeImage() + + +def imgDirectoryProcessing(path): + global workerPool, workerOutput + workerPool = Pool() + workerOutput = [] + options.imgIndex = {} + options.imgPurgeIndex = [] + work = [] + pagenumber = 0 + for (dirpath, dirnames, filenames) in os.walk(path): + for afile in filenames: + pagenumber += 1 + work.append([afile, dirpath, options]) + if GUI: + GUI.progressBarTick.emit(str(pagenumber)) + if len(work) > 0: + for i in work: + workerPool.apply_async(func=imgFileProcessing, args=(i, ), callback=imgFileProcessingTick) + workerPool.close() + workerPool.join() + if GUI and not GUI.conversionAlive: + rmtree(os.path.join(path, '..', '..'), True) + raise UserWarning("Conversion interrupted.") + if len(workerOutput) > 0: + rmtree(os.path.join(path, '..', '..'), True) + raise RuntimeError("One of workers crashed. Cause: " + workerOutput[0]) + for file in options.imgPurgeIndex: + if os.path.isfile(file): + os.remove(file) + else: + rmtree(os.path.join(path, '..', '..'), True) + raise UserWarning("Source directory is empty.") + + +def imgFileProcessingTick(output): + if isinstance(output, str): + workerOutput.append(output) + workerPool.terminate() + else: + for page in output: + if page is not None: + if isinstance(page, str): + options.imgPurgeIndex.append(page) + else: + options.imgIndex[page[0]] = page[1] + if GUI: + GUI.progressBarTick.emit('tick') + if not GUI.conversionAlive: + workerPool.terminate() + + +def imgFileProcessing(work): + try: + afile = work[0] + dirpath = work[1] + opt = work[2] + output = [] + img = image.ComicPage(os.path.join(dirpath, afile), opt.profileData) + if opt.quality == 2: + wipe = False + else: + wipe = True + if opt.nosplitrotate: + splitter = None + else: + splitter = img.splitPage(dirpath, opt.righttoleft, opt.rotate) + if splitter is not None: + img0 = image.ComicPage(splitter[0], opt.profileData) + imgOptimization(img0, opt) + output.append(img0.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + img1 = image.ComicPage(splitter[1], opt.profileData) + imgOptimization(img1, opt) + output.append(img1.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + if wipe: + output.append(img0.origFileName) + output.append(img1.origFileName) + if opt.quality == 2: + img0b = image.ComicPage(splitter[0], opt.profileData, img0.fill) + imgOptimization(img0b, opt, img0) + output.append(img0b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + img1b = image.ComicPage(splitter[1], opt.profileData, img1.fill) + imgOptimization(img1b, opt, img1) + output.append(img1b.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + output.append(img0.origFileName) + output.append(img1.origFileName) + output.append(img.origFileName) + else: + imgOptimization(img, opt) + output.append(img.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + if wipe: + output.append(img.origFileName) + if opt.quality == 2: + img2 = image.ComicPage(os.path.join(dirpath, afile), opt.profileData, img.fill) + if img.rotated: + img2.image = img2.image.rotate(90) + img2.rotated = True + imgOptimization(img2, opt, img) + output.append(img2.saveToDir(dirpath, opt.forcepng, opt.forcecolor)) + output.append(img.origFileName) + return output + except Exception: + return str(sys.exc_info()[1]) + + def getWorkFolder(afile): if len(afile) > 240: raise UserWarning("Path is too long.") @@ -579,7 +562,7 @@ def getWorkFolder(afile): if len(fullPath) > 240: raise UserWarning("Path is too long.") copytree(afile, fullPath) - sanitizeTreeBeforeConversion(fullPath) + sanitizePermissions(fullPath) return workdir except OSError: rmtree(workdir, True) @@ -609,9 +592,36 @@ def getWorkFolder(afile): return path -def checkComicInfo(path, originalPath): +def getOutputFilename(srcpath, wantedname, ext, tomeNumber): + if srcpath[-1] == os.path.sep: + srcpath = srcpath[:-1] + if not ext.startswith('.'): + ext = '.' + ext + if wantedname is not None: + if wantedname.endswith(ext): + filename = os.path.abspath(wantedname) + elif os.path.isdir(srcpath): + filename = os.path.join(os.path.abspath(options.output), os.path.basename(srcpath) + ext) + else: + filename = os.path.join(os.path.abspath(options.output), + os.path.basename(os.path.splitext(srcpath)[0]) + ext) + elif os.path.isdir(srcpath): + filename = srcpath + tomeNumber + ext + else: + filename = os.path.splitext(srcpath)[0] + tomeNumber + ext + if os.path.isfile(filename): + counter = 0 + basename = os.path.splitext(filename)[0] + while os.path.isfile(basename + '_kcc' + str(counter) + ext): + counter += 1 + filename = basename + '_kcc' + str(counter) + ext + return filename + + +def getComicInfo(path, originalPath): xmlPath = os.path.join(path, 'ComicInfo.xml') options.authors = ['KCC'] + options.remoteCovers = {} titleSuffix = '' if options.title == 'defaulttitle': defaultTitle = True @@ -657,48 +667,81 @@ def checkComicInfo(path, originalPath): options.authors.sort() else: options.authors = ['KCC'] + if len(xml.getElementsByTagName('ScanInformation')) != 0: + coverId = xml.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue + coverId = compile('(MCD\\()(\\d+)(\\))').search(coverId) + if coverId: + options.remoteCovers = getCoversFromMCB(coverId.group(2)) os.remove(xmlPath) -def slugify(value): - value = slugifyExt(value) - value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value)) - return value +def getCoversFromMCB(mangaID): + covers = {} + try: + jsonRaw = urlopen(Request('http://manga.joentjuh.nl/json/series/' + mangaID + '/', + headers={'User-Agent': 'KindleComicConverter/' + __version__})) + jsonData = loads(jsonRaw.readall().decode('utf-8')) + for volume in jsonData['volumes']: + covers[int(volume['volume'])] = volume['releases'][0]['files']['front']['url'] + except Exception: + return {} + return covers + + +def getDirectorySize(start_path='.'): + total_size = 0 + for dirpath, dirnames, filenames in os.walk(start_path): + for f in filenames: + fp = os.path.join(dirpath, f) + total_size += os.path.getsize(fp) + return total_size def sanitizeTree(filetree): chapterNames = {} for root, dirs, files in os.walk(filetree, False): for name in files: - if name.startswith('.') or name.lower() == 'thumbs.db': - os.remove(os.path.join(root, name)) - else: - splitname = os.path.splitext(name) - slugified = slugify(splitname[0]) - while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\ - != slugified.upper(): - slugified += "A" - newKey = os.path.join(root, slugified + splitname[1]) - key = os.path.join(root, name) - if key != newKey: - os.replace(key, newKey) + splitname = os.path.splitext(name) + slugified = slugify(splitname[0]) + while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\ + != slugified.upper(): + slugified += "A" + newKey = os.path.join(root, slugified + splitname[1]) + key = os.path.join(root, name) + if key != newKey: + os.replace(key, newKey) for name in dirs: - if name.startswith('.'): - os.remove(os.path.join(root, name)) - else: - tmpName = name - slugified = slugify(name) - while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper(): - slugified += "A" - chapterNames[slugified] = tmpName - newKey = os.path.join(root, slugified) - key = os.path.join(root, name) - if key != newKey: - os.replace(key, newKey) + tmpName = name + slugified = slugify(name) + while os.path.exists(os.path.join(root, slugified)) and name.upper() != slugified.upper(): + slugified += "A" + chapterNames[slugified] = tmpName + newKey = os.path.join(root, slugified) + key = os.path.join(root, name) + if key != newKey: + os.replace(key, newKey) return chapterNames -def sanitizeTreeBeforeConversion(filetree): +def sanitizeTreeKobo(filetree): + pageNumber = 0 + for root, dirs, files in os.walk(filetree): + files.sort() + dirs.sort() + for name in files: + splitname = os.path.splitext(name) + slugified = str(pageNumber).zfill(5) + pageNumber += 1 + while os.path.exists(os.path.join(root, slugified + splitname[1])) and splitname[0].upper()\ + != slugified.upper(): + slugified += "A" + newKey = os.path.join(root, slugified + splitname[1]) + key = os.path.join(root, name) + if key != newKey: + os.replace(key, newKey) + + +def sanitizePermissions(filetree): for root, dirs, files in os.walk(filetree, False): for name in files: os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD) @@ -706,23 +749,66 @@ def sanitizeTreeBeforeConversion(filetree): os.chmod(os.path.join(root, name), S_IWRITE | S_IREAD | S_IEXEC) -def getDirectorySize(start_path='.'): - total_size = 0 - for dirpath, dirnames, filenames in os.walk(start_path): - for f in filenames: - fp = os.path.join(dirpath, f) - total_size += os.path.getsize(fp) - return total_size - - -def createNewTome(): - tomePathRoot = mkdtemp('', 'KCC-TMP-') - tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images') - os.makedirs(tomePath) - return tomePath, tomePathRoot +#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 + else: + if filesNumber > 0: + print('\nWARNING: Automatic output splitting failed.') + if GUI: + GUI.addMessage.emit('Automatic output splitting failed. <a href=' + '"https://github.com/ciromattia/kcc/wiki' + '/Automatic-output-splitting">' + 'More details.</a>', 'warning', False) + GUI.addMessage.emit('', '', False) + return [path] + detectedSubSubdirectories = False + detectedFilesInSubdirectories = False + for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1): + if root != os.path.join(path, 'OEBPS', 'Images'): + if len(dirs) != 0: + detectedSubSubdirectories = True + elif len(dirs) == 0 and detectedSubSubdirectories: + print('\nWARNING: Automatic output splitting failed.') + if GUI: + GUI.addMessage.emit('Automatic output splitting failed. <a href=' + '"https://github.com/ciromattia/kcc/wiki' + '/Automatic-output-splitting">' + 'More details.</a>', 'warning', False) + GUI.addMessage.emit('', '', False) + return [path] + if len(files) != 0: + detectedFilesInSubdirectories = True + if detectedSubSubdirectories: + # Two levels of subdirectories + mode = 2 + else: + # One level of subdirectories + mode = 1 + if detectedFilesInSubdirectories and detectedSubSubdirectories: + print('\nWARNING: Automatic output splitting failed.') + if GUI: + GUI.addMessage.emit('Automatic output splitting failed. <a href=' + '"https://github.com/ciromattia/kcc/wiki' + '/Automatic-output-splitting">' + 'More details.</a>', 'warning', False) + GUI.addMessage.emit('', '', False) + return [path] + # Split directories + splitter = splitProcess(os.path.join(path, 'OEBPS', 'Images'), mode) + path = [path] + for tome in splitter: + path.append(tome) + return path -def splitDirectory(path, mode): +def splitProcess(path, mode): output = [] currentSize = 0 currentTarget = path @@ -783,65 +869,6 @@ def splitDirectory(path, mode): return output -#noinspection PyUnboundLocalVariable -def preSplitDirectory(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 - else: - if filesNumber > 0: - print('\nWARNING: Automatic output splitting failed.') - if GUI: - GUI.addMessage.emit('Automatic output splitting failed. <a href=' - '"https://github.com/ciromattia/kcc/wiki' - '/Automatic-output-splitting">' - 'More details.</a>', 'warning', False) - GUI.addMessage.emit('', '', False) - return [path] - detectedSubSubdirectories = False - detectedFilesInSubdirectories = False - for root, dirs, files in walkLevel(os.path.join(path, 'OEBPS', 'Images'), 1): - if root != os.path.join(path, 'OEBPS', 'Images'): - if len(dirs) != 0: - detectedSubSubdirectories = True - elif len(dirs) == 0 and detectedSubSubdirectories: - print('\nWARNING: Automatic output splitting failed.') - if GUI: - GUI.addMessage.emit('Automatic output splitting failed. <a href=' - '"https://github.com/ciromattia/kcc/wiki' - '/Automatic-output-splitting">' - 'More details.</a>', 'warning', False) - GUI.addMessage.emit('', '', False) - return [path] - if len(files) != 0: - detectedFilesInSubdirectories = True - if detectedSubSubdirectories: - # Two levels of subdirectories - mode = 2 - else: - # One level of subdirectories - mode = 1 - if detectedFilesInSubdirectories and detectedSubSubdirectories: - print('\nWARNING: Automatic output splitting failed.') - if GUI: - GUI.addMessage.emit('Automatic output splitting failed. <a href=' - '"https://github.com/ciromattia/kcc/wiki' - '/Automatic-output-splitting">' - 'More details.</a>', 'warning', False) - GUI.addMessage.emit('', '', False) - return [path] - # Split directories - splitter = splitDirectory(os.path.join(path, 'OEBPS', 'Images'), mode) - path = [path] - for tome in splitter: - path.append(tome) - return path - - def detectCorruption(tmpPath, orgPath): for root, dirs, files in os.walk(tmpPath, False): for name in files: @@ -859,6 +886,50 @@ def detectCorruption(tmpPath, orgPath): except Exception: rmtree(os.path.join(tmpPath, '..', '..'), True) raise RuntimeError('Image file %s is corrupted.' % pathOrg) + else: + os.remove(os.path.join(root, name)) + + +def detectMargins(path): + if options.imgproc: + for flag in options.imgIndex[path]: + if "Margins-" in flag: + flag = flag.split('-') + xl = flag[1] + yu = flag[2] + xr = flag[3] + yd = flag[4] + if xl != "0": + xl = "-" + str(float(xl)/100) + "%" + else: + xl = "0%" + if xr != "0": + xr = "-" + str(float(xr)/100) + "%" + else: + xr = "0%" + if yu != "0": + yu = "-" + str(float(yu)/100) + "%" + else: + yu = "0%" + if yd != "0": + yd = "-" + str(float(yd)/100) + "%" + else: + yd = "0%" + return xl, yu, xr, yd + return '0%', '0%', '0%', '0%' + + +def createNewTome(): + tomePathRoot = mkdtemp('', 'KCC-TMP-') + tomePath = os.path.join(tomePathRoot, 'OEBPS', 'Images') + os.makedirs(tomePath) + return tomePath, tomePathRoot + + +def slugify(value): + value = slugifyExt(value) + value = sub(r'0*([0-9]{4,})', r'\1', sub(r'([0-9]+)', r'0000\1', value)) + return value def makeZIP(zipFilename, baseDir, isEPUB=False): @@ -876,15 +947,6 @@ def makeZIP(zipFilename, baseDir, isEPUB=False): return zipFilename -def Copyright(): - print(('comic2ebook v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) - - -def Usage(): - print("Generates EPUB/CBZ comic ebook from a bunch of images.") - parser.print_help() - - def makeParser(): """Create and return an option parser set up with kcc's options.""" psr = OptionParser(usage="Usage: kcc-c2e [options] comic_file|comic_folder", add_help_option=False) @@ -953,57 +1015,94 @@ def makeParser(): return psr -def main(argv=None, qtGUI=None): - global parser, options, GUI - parser = makeParser() - options, args = parser.parse_args(argv) - checkOptions() - if qtGUI: - GUI = qtGUI - GUI.progressBarTick.emit('1') +def checkOptions(): + global options + options.panelview = True + options.bordersColor = None + if options.white_borders: + options.bordersColor = "white" + if options.black_borders: + options.bordersColor = "black" + # Disabling grayscale conversion for Kindle Fire family. + if options.profile == 'KF' or options.profile == 'KFHD' or options.profile == 'KFHD8' or options.profile == 'KFHDX'\ + or options.profile == 'KFHDX8' or options.forcecolor: + options.forcecolor = True else: - GUI = None - if len(args) != 1: - parser.print_help() - return - outputPath = makeBook(args[0], qtGUI=qtGUI) - return outputPath + options.forcecolor = False + # Older Kindle don't need higher resolution files due lack of Panel View. + if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX': + options.quality = 0 + options.panelview = False + # Webtoon mode mandatory options + if options.webtoon: + options.nosplitrotate = True + options.quality = 0 + options.panelview = False + # Disable all Kindle features for other e-readers + if options.profile == 'OTHER': + options.panelview = False + options.quality = 0 + if 'Ko' in options.profile: + options.panelview = False + # Kobo models can't use ultra quality mode + if options.quality == 2: + options.quality = 1 + # Kindle for Android profile require target resolution. + if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0): + print("ERROR: Kindle for Android profile require --customwidth and --customheight options!") + sys.exit(1) + # CBZ files on Kindle DX/DXG support higher resolution + if options.profile == 'KDX' and options.cbzoutput: + options.customheight = 1200 + # Ultra mode don't work with CBZ format + if options.quality == 2 and options.cbzoutput: + options.quality = 1 + # Override profile data + if options.customwidth != 0 or options.customheight != 0: + X = image.ProfileData.Profiles[options.profile][1][0] + Y = image.ProfileData.Profiles[options.profile][1][1] + if options.customwidth != 0: + X = options.customwidth + 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["Custom"] = newProfile + options.profile = "Custom" + options.profileData = image.ProfileData.Profiles[options.profile] def makeBook(source, qtGUI=None): """Generates EPUB/CBZ comic ebook from a bunch of images.""" global GUI GUI = qtGUI + if GUI: + GUI.progressBarTick.emit('1') path = getWorkFolder(source) print("\nChecking images...") + getComicInfo(os.path.join(path, "OEBPS", "Images"), source) detectCorruption(os.path.join(path, "OEBPS", "Images"), source) - checkComicInfo(os.path.join(path, "OEBPS", "Images"), source) - if options.webtoon: if options.customheight > 0: comic2panel.main(['-y ' + str(options.customheight), '-i', '-m', path], qtGUI) else: comic2panel.main(['-y ' + str(image.ProfileData.Profiles[options.profile][1][1]), '-i', '-m', path], qtGUI) - if options.imgproc: print("\nProcessing images...") if GUI: GUI.progressBarTick.emit('Processing images') - dirImgProcess(os.path.join(path, "OEBPS", "Images")) - + imgDirectoryProcessing(os.path.join(path, "OEBPS", "Images")) if GUI: GUI.progressBarTick.emit('1') - chapterNames = sanitizeTree(os.path.join(path, 'OEBPS', 'Images')) - + if 'Ko' in options.profile and options.cbzoutput: + sanitizeTreeKobo(os.path.join(path, 'OEBPS', 'Images')) if options.batchsplit: - tomes = preSplitDirectory(path) + tomes = splitDirectory(path) else: tomes = [path] - filepath = [] tomeNumber = 0 - if GUI: if options.cbzoutput: GUI.progressBarTick.emit('Compressing CBZ files') @@ -1011,14 +1110,11 @@ def makeBook(source, qtGUI=None): GUI.progressBarTick.emit('Compressing EPUB files') GUI.progressBarTick.emit(str(len(tomes) + 1)) GUI.progressBarTick.emit('tick') - options.baseTitle = options.title - for tome in tomes: if len(tomes) > 1: tomeNumber += 1 options.title = options.baseTitle + ' [' + str(tomeNumber) + '/' + str(len(tomes)) + ']' - if options.cbzoutput: # if CBZ output wanted, compress all images and return filepath print("\nCreating CBZ file...") @@ -1029,97 +1125,15 @@ def makeBook(source, qtGUI=None): makeZIP(tome + '_comic', os.path.join(tome, "OEBPS", "Images")) else: print("\nCreating EPUB structure...") - genEpubStruct(tome, chapterNames) + buildEPUB(tome, chapterNames, tomeNumber) # actually zip the ePub if len(tomes) > 1: filepath.append(getOutputFilename(source, options.output, '.epub', ' ' + str(tomeNumber))) else: filepath.append(getOutputFilename(source, options.output, '.epub', '')) makeZIP(tome + '_comic', tome, True) - move(tome + '_comic.zip', filepath[-1]) rmtree(tome, True) - if GUI: GUI.progressBarTick.emit('tick') - return filepath - - -def getOutputFilename(srcpath, wantedname, ext, tomeNumber): - if srcpath[-1] == os.path.sep: - srcpath = srcpath[:-1] - if not ext.startswith('.'): - ext = '.' + ext - if wantedname is not None: - if wantedname.endswith(ext): - filename = os.path.abspath(wantedname) - elif os.path.isdir(srcpath): - filename = os.path.join(os.path.abspath(options.output), os.path.basename(srcpath) + ext) - else: - filename = os.path.join(os.path.abspath(options.output), - os.path.basename(os.path.splitext(srcpath)[0]) + ext) - elif os.path.isdir(srcpath): - filename = srcpath + tomeNumber + ext - else: - filename = os.path.splitext(srcpath)[0] + tomeNumber + ext - if os.path.isfile(filename): - counter = 0 - basename = os.path.splitext(filename)[0] - while os.path.isfile(basename + '_kcc' + str(counter) + ext): - counter += 1 - filename = basename + '_kcc' + str(counter) + ext - return filename - - -def checkOptions(): - global options - options.panelview = True - options.bordersColor = None - if options.white_borders: - options.bordersColor = "white" - if options.black_borders: - options.bordersColor = "black" - # Disabling grayscale conversion for Kindle Fire family. - if options.profile == 'KF' or options.profile == 'KFHD' or options.profile == 'KFHD8' or options.profile == 'KFHDX'\ - or options.profile == 'KFHDX8' or options.forcecolor: - options.forcecolor = True - else: - options.forcecolor = False - # Older Kindle don't need higher resolution files due lack of Panel View. - if options.profile == 'K1' or options.profile == 'K2' or options.profile == 'KDX': - options.quality = 0 - options.panelview = False - # Webtoon mode mandatory options - if options.webtoon: - options.nosplitrotate = True - options.quality = 0 - options.panelview = False - # Disable all Kindle features for other e-readers - if options.profile == 'OTHER': - options.panelview = False - options.quality = 0 - if 'Ko' in options.profile: - options.panelview = False - # Kobo models can't use ultra quality mode - if options.quality == 2: - options.quality = 1 - # Kindle for Android profile require target resolution. - if options.profile == 'KFA' and (options.customwidth == 0 or options.customheight == 0): - print("ERROR: Kindle for Android profile require --customwidth and --customheight options!") - sys.exit(1) - # CBZ files on Kindle DX/DXG support higher resolution - if options.profile == 'KDX' and options.cbzoutput: - options.customheight = 1200 - # Override profile data - if options.customwidth != 0 or options.customheight != 0: - X = image.ProfileData.Profiles[options.profile][1][0] - Y = image.ProfileData.Profiles[options.profile][1][1] - if options.customwidth != 0: - X = options.customwidth - if options.customheight != 0: - Y = options.customheight - newProfile = ("Custom", (X, Y), image.ProfileData.Palette16, image.ProfileData.Profiles[options.profile][3], - (int(X*1.5), int(Y*1.5))) - image.ProfileData.Profiles["Custom"] = newProfile - options.profile = "Custom" - options.profileData = image.ProfileData.Profiles[options.profile] + return filepath \ No newline at end of file diff --git a/kcc/comic2panel.py b/kcc/comic2panel.py index 3a36777..d506cac 100644 --- a/kcc/comic2panel.py +++ b/kcc/comic2panel.py @@ -18,7 +18,7 @@ # PERFORMANCE OF THIS SOFTWARE. # -__version__ = '4.1' +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' @@ -36,7 +36,7 @@ except ImportError: QtCore = None -def mergeDirectory_tick(output): +def mergeDirectoryTick(output): if output: mergeWorkerOutput.append(output) mergeWorkerPool.terminate() @@ -108,7 +108,7 @@ def sanitizePanelSize(panel, opt): return newPanels -def splitImage_tick(output): +def splitImageTick(output): if output: splitWorkerOutput.append(output) splitWorkerPool.terminate() @@ -207,10 +207,6 @@ def splitImage(work): return str(sys.exc_info()[1]) -def Copyright(): - print(('comic2panel v%(__version__)s. Written by Ciro Mattia Gonano and Pawel Jastrzebski.' % globals())) - - def main(argv=None, qtGUI=None): global options, GUI, splitWorkerPool, splitWorkerOutput, mergeWorkerPool, mergeWorkerOutput parser = OptionParser(usage="Usage: kcc-c2p [options] comic_folder", add_help_option=False) @@ -261,7 +257,7 @@ def main(argv=None, qtGUI=None): GUI.progressBarTick.emit('Combining images') GUI.progressBarTick.emit(str(directoryNumer)) for i in mergeWork: - mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectory_tick) + mergeWorkerPool.apply_async(func=mergeDirectory, args=(i, ), callback=mergeDirectoryTick) mergeWorkerPool.close() mergeWorkerPool.join() if GUI and not GUI.conversionAlive: @@ -284,7 +280,7 @@ def main(argv=None, qtGUI=None): GUI.progressBarTick.emit('tick') if len(work) > 0: for i in work: - splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImage_tick) + splitWorkerPool.apply_async(func=splitImage, args=(i, ), callback=splitImageTick) splitWorkerPool.close() splitWorkerPool.join() if GUI and not GUI.conversionAlive: diff --git a/kcc/image.py b/kcc/image.py index 5b54bbd..f8abfe6 100755 --- a/kcc/image.py +++ b/kcc/image.py @@ -16,11 +16,14 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. +__version__ = '4.2' __license__ = 'ISC' __copyright__ = '2012-2014, Ciro Mattia Gonano <ciromattia@gmail.com>, Pawel Jastrzebski <pawelj@iosphe.re>' __docformat__ = 'restructuredtext en' import os +from io import BytesIO +from urllib.request import Request, urlopen from functools import reduce from PIL import Image, ImageOps, ImageStat, ImageChops from .shared import md5Checksum @@ -472,14 +475,37 @@ class ComicPage: class Cover: - def __init__(self, source, target): + def __init__(self, source, target, opt, tomeNumber): + self.options = opt self.source = source self.target = target - self.image = Image.open(source) + if tomeNumber == 0: + self.tomeNumber = 1 + else: + self.tomeNumber = tomeNumber + if self.tomeNumber in self.options.remoteCovers: + try: + source = urlopen(Request(self.options.remoteCovers[self.tomeNumber], + headers={'User-Agent': 'KindleComicConverter/' + __version__})).read() + self.image = Image.open(BytesIO(source)) + self.processExternal() + except Exception: + self.image = Image.open(source) + self.processInternal() + else: + self.image = Image.open(source) + self.processInternal() + + def processInternal(self): self.image = self.image.convert('RGB') - self.process() + self.image = self.trim() self.save() + def processExternal(self): + self.image = self.image.convert('RGB') + self.image.thumbnail(self.options.profileData[1], Image.ANTIALIAS) + self.save(True) + def trim(self): bg = Image.new(self.image.mode, self.image.size, self.image.getpixel((0, 0))) diff = ImageChops.difference(self.image, bg) @@ -490,12 +516,13 @@ class Cover: else: return self.image - def process(self): - self.image = self.trim() - - def save(self): + def save(self, external=False): + if external: + source = self.options.remoteCovers[self.tomeNumber].split('/')[-1] + else: + source = self.source try: - if os.path.splitext(self.source)[1].lower() == '.png': + if os.path.splitext(source)[1].lower() == '.png': self.image.save(self.target, "PNG", optimize=1) else: self.image.save(self.target, "JPEG", optimize=1) diff --git a/setup.py b/setup.py index af87fa2..5f90bec 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ if version_info[0] != 3: exit(1) NAME = "KindleComicConverter" -VERSION = "4.1" +VERSION = "4.2" MAIN = "kcc.py" if platform == "darwin": diff --git a/setup.sh b/setup.sh index 0f9d0d2..bd69758 100755 --- a/setup.sh +++ b/setup.sh @@ -1,7 +1,7 @@ #!/bin/bash # Linux Python package build script -VERSION="4.1" +VERSION="4.2" cp kcc.py __main__.py zip kcc.zip __main__.py kcc/*.py |