From 6eb05b3a8fb9ca98a642cfadbb77c6bb1aa11848 Mon Sep 17 00:00:00 2001 From: Carlos Rosa Date: Wed, 4 Apr 2018 10:12:30 -0300 Subject: Allow for multiple file arguments on startup --- kindlecomicconverter/startup.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kindlecomicconverter/startup.py b/kindlecomicconverter/startup.py index deb6313..158f520 100644 --- a/kindlecomicconverter/startup.py +++ b/kindlecomicconverter/startup.py @@ -30,15 +30,15 @@ def start(): os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1" KCCAplication = KCC_gui.QApplicationMessaging(sys.argv) if KCCAplication.isRunning(): - if len(sys.argv) > 1: - KCCAplication.sendMessage(sys.argv[1]) + for i in range (1, len(sys.argv)): + KCCAplication.sendMessage(sys.argv[i]) else: KCCAplication.sendMessage('ARISE') else: KCCWindow = KCC_gui.QMainWindowKCC() KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow) - if len(sys.argv) > 1: - KCCUI.handleMessage(sys.argv[1]) + for i in range (1, len(sys.argv)): + KCCUI.handleMessage(sys.argv[i]) sys.exit(KCCAplication.exec_()) -- cgit 1.4.1 From 1c615ffc20dcf9d30aba76b7a28374bc3ee85ac2 Mon Sep 17 00:00:00 2001 From: Carlos Rosa Date: Wed, 4 Apr 2018 11:04:31 -0300 Subject: Make format checking more straightforward --- kindlecomicconverter/KCC_gui.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 7959b40..48b3dd5 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -812,16 +812,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if self.needClean: self.needClean = False GUI.jobList.clear() + formats = ['.cbz', '.zip', '.pdf'] if self.UnRAR: + formats.extend(['.cbr', '.rar']) if self.sevenza: - formats = ['.cbz', '.cbr', '.cb7', '.zip', '.rar', '.7z', '.pdf'] - else: - formats = ['.cbz', '.cbr', '.zip', '.rar', '.pdf'] - else: - if self.sevenza: - formats = ['.cbz', '.cb7', '.zip', '.7z', '.pdf'] - else: - formats = ['.cbz', '.zip', '.pdf'] + formats.extend(['.cb7', '.7z']) if os.path.isdir(message): GUI.jobList.addItem(message) GUI.jobList.scrollToBottom() -- cgit 1.4.1 From 2591b53a0905944263c39a22a0816024b2370c68 Mon Sep 17 00:00:00 2001 From: Carlos Rosa Date: Wed, 4 Apr 2018 11:05:13 -0300 Subject: Make file type error more informative (display file name) --- kindlecomicconverter/KCC_gui.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 48b3dd5..d3e6f10 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -815,7 +815,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): formats = ['.cbz', '.zip', '.pdf'] if self.UnRAR: formats.extend(['.cbr', '.rar']) - if self.sevenza: + if self.sevenza: formats.extend(['.cb7', '.7z']) if os.path.isdir(message): GUI.jobList.addItem(message) @@ -826,7 +826,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): GUI.jobList.addItem(message) GUI.jobList.scrollToBottom() else: - self.addMessage('This file type is unsupported!', 'error') + self.addMessage('Unsupported file type for ' + message, 'error') def dragAndDrop(self, e): e.accept() -- cgit 1.4.1 From a63a46a7415cf0e756dde2ed4ec3d676f89cf31e Mon Sep 17 00:00:00 2001 From: Hugo Date: Wed, 18 Apr 2018 11:42:56 +0300 Subject: Use more standard __version__ rather than PILLOW_VERSION --- kindlecomicconverter/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index 56f07d6..39c3613 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -145,7 +145,7 @@ def dependencyCheck(level): except ImportError: missing.append('python-slugify 1.2.1+') try: - from PIL import PILLOW_VERSION as pillowVersion + from PIL import __version__ as pillowVersion if StrictVersion('4.0.0') > StrictVersion(pillowVersion): missing.append('Pillow 4.0.0+') except ImportError: -- cgit 1.4.1 From ef4a91e44d03506e24e6255e09540226b33d5791 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Sun, 8 Jul 2018 08:42:34 +0200 Subject: WebP support (close #263) --- kindlecomicconverter/comic2ebook.py | 3 ++- kindlecomicconverter/shared.py | 9 +++++---- requirements.txt | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index b560825..e118b7e 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -790,7 +790,8 @@ def splitDirectory(path): 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'): + if f.endswith('.jpg') or f.endswith('.jpeg') or f.endswith('.png') or f.endswith('.gif') or \ + f.endswith('.webp'): newLevel = os.path.join(root, f).replace(os.path.join(path, 'OEBPS', 'Images'), '').count(os.sep) if level != -1 and level != newLevel: level = 0 diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index 39c3613..bdfd3e7 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -50,7 +50,8 @@ class HTMLStripper(HTMLParser): def getImageFileName(imgfile): name, ext = os.path.splitext(imgfile) ext = ext.lower() - if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif'): + if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif' and + ext != '.webp'): return None return [name, ext] @@ -146,10 +147,10 @@ def dependencyCheck(level): missing.append('python-slugify 1.2.1+') try: from PIL import __version__ as pillowVersion - if StrictVersion('4.0.0') > StrictVersion(pillowVersion): - missing.append('Pillow 4.0.0+') + if StrictVersion('5.2.0') > StrictVersion(pillowVersion): + missing.append('Pillow 5.2.0+') except ImportError: - missing.append('Pillow 4.0.0+') + missing.append('Pillow 5.2.0+') if len(missing) > 0: print('ERROR: ' + ', '.join(missing) + ' is not installed!') exit(1) diff --git a/requirements.txt b/requirements.txt index 0478a95..7b2f16c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ PyQt5>=5.6.0 -Pillow>=4.0.0 +Pillow>=5.2.0 psutil>=5.0.0 python-slugify>=1.2.1 raven>=6.0.0 \ No newline at end of file diff --git a/setup.py b/setup.py index f7d71e0..26e5ec0 100755 --- a/setup.py +++ b/setup.py @@ -93,7 +93,7 @@ setuptools.setup( packages=['kindlecomicconverter'], install_requires=[ 'PyQt5>=5.6.0', - 'Pillow>=4.0.0', + 'Pillow>=5.2.0', 'psutil>=5.0.0', 'python-slugify>=1.2.1', 'raven>=6.0.0', -- cgit 1.4.1 From 6792c2d3660f6d0293833a3ecc65bb0c6c5df8bc Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Mon, 9 Jul 2018 08:30:12 +0200 Subject: Bump MAX_IMAGE_PIXELS (close #273) --- kindlecomicconverter/image.py | 1 + 1 file changed, 1 insertion(+) diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 5f195a5..9608482 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -101,6 +101,7 @@ class ProfileData: class ComicPageParser: def __init__(self, source, options): + Image.MAX_IMAGE_PIXELS = int(2048 * 2048 * 2048 // 4 // 3) self.opt = options self.source = source self.size = self.opt.profileData[1] -- cgit 1.4.1 From 7904662f25997ba1466c9f21c564f1f3f066fb9f Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Tue, 10 Jul 2018 08:09:04 +0200 Subject: Let 7-Zip handle all archive operations --- README.md | 1 - kcc.iss | 4 +- kindlecomicconverter/KCC_gui.py | 77 +- kindlecomicconverter/cbxarchive.py | 89 -- kindlecomicconverter/comic2ebook.py | 40 +- kindlecomicconverter/comicarchive.py | 81 ++ kindlecomicconverter/metadata.py | 71 +- kindlecomicconverter/rarfile.py | 1990 ---------------------------------- kindlecomicconverter/shared.py | 21 - kindlecomicconverter/startup.py | 4 +- other/osx/7z | Bin 0 -> 684784 bytes other/osx/7z.so | Bin 0 -> 2381120 bytes other/osx/7za | Bin 1122372 -> 0 bytes other/osx/unrar | Bin 266404 -> 0 bytes other/windows/7z.dll | Bin 0 -> 1677824 bytes other/windows/7z.exe | Bin 0 -> 461824 bytes other/windows/7za.exe | Bin 643072 -> 0 bytes other/windows/Additional-LICENSE.txt | 99 +- other/windows/UnRAR.exe | Bin 315384 -> 0 bytes setup.py | 9 +- 20 files changed, 195 insertions(+), 2291 deletions(-) delete mode 100644 kindlecomicconverter/cbxarchive.py create mode 100644 kindlecomicconverter/comicarchive.py delete mode 100644 kindlecomicconverter/rarfile.py create mode 100644 other/osx/7z create mode 100644 other/osx/7z.so delete mode 100755 other/osx/7za delete mode 100755 other/osx/unrar create mode 100644 other/windows/7z.dll create mode 100644 other/windows/7z.exe delete mode 100644 other/windows/7za.exe delete mode 100644 other/windows/UnRAR.exe diff --git a/README.md b/README.md index b406bb8..85e20bf 100644 --- a/README.md +++ b/README.md @@ -160,7 +160,6 @@ This script born as a cross-platform alternative to `KindleComicParser` by **Dc5 The app relies and includes the following scripts: - `DualMetaFix` script by **K. Hendricks**. Released with GPL-3 License. - - `rarfile.py` script © 2005-2014 **Marko Kreen** . Released with ISC License. - `image.py` class from **Alex Yatskov**'s [Mangle](https://github.com/FooSoft/mangle/) with subsequent [proDOOMman](https://github.com/proDOOMman/Mangle)'s and [Birua](https://github.com/Birua/Mangle)'s patches. - Icon is by **Nikolay Verin** ([http://ncrow.deviantart.com/](http://ncrow.deviantart.com/)) and released under [CC BY-NC-SA 3.0](http://creativecommons.org/licenses/by-nc-sa/3.0/) License. diff --git a/kcc.iss b/kcc.iss index 28a8d41..6142c6d 100644 --- a/kcc.iss +++ b/kcc.iss @@ -47,8 +47,8 @@ Name: "CB7association"; Description: "CB7"; GroupDescription: "File associations Source: "dist\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion solidbreak Source: "other\windows\Additional-LICENSE.txt"; DestDir: "{app}"; Flags: ignoreversion -Source: "other\windows\UnRAR.exe"; DestDir: "{app}"; Flags: ignoreversion -Source: "other\windows\7za.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "other\windows\7z.exe"; DestDir: "{app}"; Flags: ignoreversion +Source: "other\windows\7z.dll"; DestDir: "{app}"; Flags: ignoreversion [Icons] Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index d3e6f10..d46353c 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -173,7 +173,7 @@ class VersionThread(QtCore.QThread): move(path[0], path[0] + '.exe') MW.hideProgressBar.emit() MW.modeConvert.emit(1) - Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) + Popen(path[0] + '.exe /SP- /silent /noicons', stdout=PIPE, stderr=STDOUT, shell=True) MW.forceShutdown.emit() except Exception: MW.addMessage.emit('Failed to download the update!', 'warning', False) @@ -238,6 +238,7 @@ class WorkerThread(QtCore.QThread): MW.addTrayMessage.emit('Conversion interrupted.', 'Critical') MW.modeConvert.emit(1) + # noinspection PyUnboundLocalVariable def run(self): MW.modeConvert.emit(0) @@ -477,20 +478,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if self.needClean: self.needClean = False GUI.jobList.clear() - if self.UnRAR: - if self.sevenza: - fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)') - else: - fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cbr *.zip *.rar *.pdf)') + if self.sevenzip: + fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, + 'Comic (*.cbz *.cbr *.cb7 *.zip *.rar *.7z *.pdf)') else: - if self.sevenza: - fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cb7 *.zip *.7z *.pdf)') - else: - fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.zip *.pdf)') + fnames = QtWidgets.QFileDialog.getOpenFileNames(MW, 'Select file', self.lastPath, 'Comic (*.pdf)') for fname in fnames[0]: if fname != '': if sys.platform.startswith('win'): @@ -509,20 +501,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow): sname = sname.replace('/', '\\') self.lastPath = os.path.abspath(sname) else: - if self.UnRAR: - if self.sevenza: - fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cbr *.cb7)') - else: - fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cbr)') + if self.sevenzip: + fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, + 'Comic (*.cbz *.cbr *.cb7)') else: - if self.sevenza: - fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, - 'Comic (*.cbz *.cb7)') - else: - fname = QtWidgets.QFileDialog.getOpenFileName(MW, 'Select file', self.lastPath, - 'Comic (*.cbz)') + fname = [''] + self.showDialog("Editor is disabled due to a lack of 7z.", 'error') if fname[0] != '': if sys.platform.startswith('win'): sname = fname[0].replace('/', '\\') @@ -812,11 +796,9 @@ class KCCGUI(KCC_ui.Ui_mainWindow): if self.needClean: self.needClean = False GUI.jobList.clear() - formats = ['.cbz', '.zip', '.pdf'] - if self.UnRAR: - formats.extend(['.cbr', '.rar']) - if self.sevenza: - formats.extend(['.cb7', '.7z']) + formats = ['.pdf'] + if self.sevenzip: + formats.extend(['.cb7', '.7z', '.cbz', '.zip', '.cbr', '.rar']) if os.path.isdir(message): GUI.jobList.addItem(message) GUI.jobList.scrollToBottom() @@ -852,10 +834,11 @@ class KCCGUI(KCC_ui.Ui_mainWindow): os.chmod('/usr/local/bin/kindlegen', 0o755) except Exception: pass - kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - if kindleGenExitCode.wait() == 0: + kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) + kindleGenExitCode.communicate() + if kindleGenExitCode.returncode == 0: self.kindleGen = True - versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) + versionCheck = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) for line in versionCheck.stdout: line = line.decode("utf-8") if 'Amazon kindlegen' in line: @@ -1002,22 +985,14 @@ class KCCGUI(KCC_ui.Ui_mainWindow): self.addMessage('Since you are a new user of KCC please see few ' 'important tips.', 'info') - rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - rarExitCode = rarExitCode.wait() - if rarExitCode == 0 or rarExitCode == 1 or rarExitCode == 7: - self.UnRAR = True - else: - self.UnRAR = False - self.addMessage('Cannot find UnRAR!' - ' Processing of CBR/RAR files will be disabled.', 'warning') - sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - sevenzaExitCode = sevenzaExitCode.wait() - if sevenzaExitCode == 0 or sevenzaExitCode == 7: - self.sevenza = True + process = Popen('7z', stdout=PIPE, stderr=STDOUT, shell=True) + process.communicate() + if process.returncode == 0 or process.returncode == 7: + self.sevenzip = True else: - self.sevenza = False - self.addMessage('Cannot find 7za!' - ' Processing of CB7/7Z files will be disabled.', 'warning') + self.sevenzip = False + self.addMessage('Cannot find 7z!' + ' Processing of archives will be disabled.', 'warning') self.detectKindleGen(True) APP.messageFromOtherInstance.connect(self.handleMessage) @@ -1098,7 +1073,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): def loadData(self, file): self.parser = metadata.MetadataParser(file) - if self.parser.compressor == 'rar': + if self.parser.format == 'RAR': self.editorWidget.setEnabled(False) self.okButton.setEnabled(False) self.statusLabel.setText('CBR metadata are read-only.') diff --git a/kindlecomicconverter/cbxarchive.py b/kindlecomicconverter/cbxarchive.py deleted file mode 100644 index 94545ae..0000000 --- a/kindlecomicconverter/cbxarchive.py +++ /dev/null @@ -1,89 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski -# -# Permission to use, copy, modify, and/or distribute this software for -# any purpose with or without fee is hereby granted, provided that the -# above copyright notice and this permission notice appear in all -# copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL -# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE -# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL -# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA -# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE. -# - -import os -from zipfile import is_zipfile, ZipFile -from subprocess import STDOUT, PIPE -from psutil import Popen -from shutil import move -from . import rarfile -from .shared import check7ZFile as is_7zfile - - -class CBxArchive: - def __init__(self, fname): - self.fname = fname - if is_zipfile(fname): - self.compressor = 'zip' - elif rarfile.is_rarfile(fname): - self.compressor = 'rar' - elif is_7zfile(fname): - self.compressor = '7z' - else: - self.compressor = None - - def isCbxFile(self): - return self.compressor is not None - - def extractCBZ(self, targetdir): - cbzFile = ZipFile(self.fname) - filelist = [] - for f in cbzFile.namelist(): - if f.startswith('__MACOSX') or f.endswith('.DS_Store') or f.endswith('humbs.db'): - pass - elif f.endswith('/'): - os.makedirs(os.path.join(targetdir, f), exist_ok=True) - else: - filelist.append(f) - cbzFile.extractall(targetdir, filelist) - - def extractCBR(self, targetdir): - cbrFile = rarfile.RarFile(self.fname) - cbrFile.extractall(targetdir) - for root, _, filenames in os.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)) - - def extractCB7(self, targetdir): - output = Popen('7za x "' + self.fname + '" -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + - targetdir + '"', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - extracted = False - for line in output.stdout: - if b"Everything is Ok" in line: - extracted = True - if not extracted: - raise OSError - - def extract(self, targetdir): - if self.compressor == 'rar': - self.extractCBR(targetdir) - elif self.compressor == 'zip': - self.extractCBZ(targetdir) - elif self.compressor == '7z': - self.extractCB7(targetdir) - adir = os.listdir(targetdir) - if 'ComicInfo.xml' in adir: - adir.remove('ComicInfo.xml') - if len(adir) == 1 and os.path.isdir(os.path.join(targetdir, adir[0])): - for f in os.listdir(os.path.join(targetdir, adir[0])): - move(os.path.join(targetdir, adir[0], f), targetdir) - os.rmdir(os.path.join(targetdir, adir[0])) - return targetdir diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index e118b7e..37e39d7 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -45,7 +45,7 @@ except ImportError: from .shared import md5Checksum, getImageFileName, walkSort, walkLevel, sanitizeTrace from . import comic2panel from . import image -from . import cbxarchive +from . import comicarchive from . import pdfjpgextract from . import dualmetafix from . import metadata @@ -597,16 +597,12 @@ def getWorkFolder(afile): raise UserWarning("Failed to extract images from PDF file.") else: workdir = mkdtemp('', 'KCC-') - cbx = cbxarchive.CBxArchive(afile) - if cbx.isCbxFile(): - try: - path = cbx.extract(workdir) - except Exception: - rmtree(workdir, True) - raise UserWarning("Failed to extract archive.") - else: + try: + cbx = comicarchive.ComicArchive(afile) + path = cbx.extract(workdir) + except OSError as e: rmtree(workdir, True) - raise UserWarning("Failed to detect archive format.") + raise UserWarning(e.strerror) else: raise UserWarning("Failed to open source file/directory.") sanitizePermissions(path) @@ -1054,21 +1050,17 @@ def checkOptions(): def checkTools(source): source = source.upper() - if source.endswith('.CBR') or source.endswith('.RAR'): - rarExitCode = Popen('unrar', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - rarExitCode = rarExitCode.wait() - if rarExitCode != 0 and rarExitCode != 1 and rarExitCode != 7: - print('ERROR: UnRAR is missing!') - exit(1) - elif source.endswith('.CB7') or source.endswith('.7Z'): - sevenzaExitCode = Popen('7za', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - sevenzaExitCode = sevenzaExitCode.wait() - if sevenzaExitCode != 0 and sevenzaExitCode != 7: - print('ERROR: 7za is missing!') + if source.endswith('.CB7') or source.endswith('.7Z') or source.endswith('.RAR') or source.endswith('.CBR') or \ + source.endswith('.ZIP') or source.endswith('.CBZ'): + process = Popen('7z', stdout=PIPE, stderr=STDOUT, shell=True) + process.communicate() + if process.returncode != 0 and process.returncode != 7: + print('ERROR: 7z is missing!') exit(1) if options.format == 'MOBI': - kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - if kindleGenExitCode.wait() != 0: + kindleGenExitCode = Popen('kindlegen -locale en', stdout=PIPE, stderr=STDOUT, shell=True) + kindleGenExitCode.communicate() + if kindleGenExitCode.returncode != 0: print('ERROR: KindleGen is missing!') exit(1) @@ -1215,7 +1207,7 @@ def makeMOBIWorker(item): try: if os.path.getsize(item) < 629145600: output = Popen('kindlegen -dont_append_source -locale en "' + item + '"', - stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) + stdout=PIPE, stderr=STDOUT, shell=True) for line in output.stdout: line = line.decode('utf-8') # ERROR: Generic error diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py new file mode 100644 index 0000000..df9029c --- /dev/null +++ b/kindlecomicconverter/comicarchive.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2012-2014 Ciro Mattia Gonano +# Copyright (c) 2013-2018 Pawel Jastrzebski +# +# Permission to use, copy, modify, and/or distribute this software for +# any purpose with or without fee is hereby granted, provided that the +# above copyright notice and this permission notice appear in all +# copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL +# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE +# AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL +# DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# + +import os +from psutil import Popen +from shutil import move +from subprocess import STDOUT, PIPE +from xml.dom.minidom import parseString +from xml.parsers.expat import ExpatError + + +class ComicArchive: + def __init__(self, filepath): + self.filepath = filepath + self.type = None + if not os.path.isfile(self.filepath): + raise OSError('File not found.') + process = Popen('7z l -y -p1 "' + self.filepath + '"', stderr=STDOUT, stdout=PIPE, shell=True) + for line in process.stdout: + if b'Type =' in line: + self.type = line.rstrip().decode().split(' = ')[1].upper() + break + process.communicate() + if process.returncode != 0: + raise OSError('Archive is corrupted or encrypted.') + elif self.type not in ['7Z', 'RAR', 'ZIP']: + raise OSError('Unsupported archive format.') + + def extract(self, targetdir): + if not os.path.isdir(targetdir): + raise OSError('Target directory don\'t exist.') + process = Popen('7z x -y -xr!__MACOSX -xr!.DS_Store -xr!thumbs.db -xr!Thumbs.db -o"' + targetdir + '" "' + + self.filepath + '"', stdout=PIPE, stderr=STDOUT, shell=True) + process.communicate() + if process.returncode != 0: + raise OSError('Failed to extract archive.') + tdir = os.listdir(targetdir) + if 'ComicInfo.xml' in tdir: + tdir.remove('ComicInfo.xml') + if len(tdir) == 1 and os.path.isdir(os.path.join(targetdir, tdir[0])): + for f in os.listdir(os.path.join(targetdir, tdir[0])): + move(os.path.join(targetdir, tdir[0], f), targetdir) + os.rmdir(os.path.join(targetdir, tdir[0])) + return targetdir + + def addFile(self, sourcefile): + if self.type == 'RAR': + raise NotImplementedError + process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"', + stdout=PIPE, stderr=STDOUT, shell=True) + process.communicate() + if process.returncode != 0: + raise OSError('Failed to add the file.') + + def extractMetadata(self): + process = Popen('7z x -y -so "' + self.filepath + '" ComicInfo.xml', + stdout=PIPE, stderr=STDOUT, shell=True) + xml = process.communicate() + if process.returncode != 0: + raise OSError('Failed to extract archive.') + try: + return parseString(xml[0]) + except ExpatError: + return None diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py index da16718..df87340 100644 --- a/kindlecomicconverter/metadata.py +++ b/kindlecomicconverter/metadata.py @@ -19,13 +19,11 @@ import os from xml.dom.minidom import parse, Document from re import compile -from zipfile import is_zipfile, ZipFile, ZIP_DEFLATED from subprocess import STDOUT, PIPE from psutil import Popen from tempfile import mkdtemp from shutil import rmtree -from .shared import removeFromZIP, check7ZFile as is_7zfile -from . import rarfile +from . import comicarchive class MetadataParser: @@ -42,47 +40,19 @@ class MetadataParser: 'MUid': '', 'Bookmarks': []} self.rawdata = None - self.compressor = None + self.format = None if self.source.endswith('.xml') and os.path.exists(self.source): self.rawdata = parse(self.source) self.parseXML() elif not self.source.endswith('.xml'): - if is_zipfile(self.source): - self.compressor = 'zip' - with ZipFile(self.source) as zip_file: - for member in zip_file.namelist(): - if member != 'ComicInfo.xml': - continue - with zip_file.open(member) as xml_file: - self.rawdata = parse(xml_file) - elif rarfile.is_rarfile(self.source): - self.compressor = 'rar' - with rarfile.RarFile(self.source) as rar_file: - for member in rar_file.namelist(): - if member != 'ComicInfo.xml': - continue - with rar_file.open(member) as xml_file: - self.rawdata = parse(xml_file) - elif is_7zfile(self.source): - self.compressor = '7z' - workdir = mkdtemp('', 'KCC-') - tmpXML = os.path.join(workdir, 'ComicInfo.xml') - output = Popen('7za e "' + self.source + '" ComicInfo.xml -o"' + workdir + '"', - stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - extracted = False - for line in output.stdout: - if b"Everything is Ok" in line or b"No files to process" in line: - extracted = True - if not extracted: - rmtree(workdir) - raise OSError('Failed to extract 7ZIP file.') - if os.path.isfile(tmpXML): - self.rawdata = parse(tmpXML) - rmtree(workdir) - else: - raise OSError('Failed to detect archive format.') - if self.rawdata: - self.parseXML() + try: + cbx = comicarchive.ComicArchive(self.source) + self.rawdata = cbx.extractMetadata() + self.format = cbx.type + except OSError as e: + raise UserWarning(e.strerror) + if self.rawdata: + self.parseXML() def parseXML(self): if len(self.rawdata.getElementsByTagName('Series')) != 0: @@ -154,20 +124,9 @@ class MetadataParser: tmpXML = os.path.join(workdir, 'ComicInfo.xml') with open(tmpXML, 'w', encoding='utf-8') as f: self.rawdata.writexml(f, encoding='utf-8') - if is_zipfile(self.source): - removeFromZIP(self.source, 'ComicInfo.xml') - with ZipFile(self.source, mode='a', compression=ZIP_DEFLATED) as zip_file: - zip_file.write(tmpXML, arcname=tmpXML.split(os.sep)[-1]) - elif rarfile.is_rarfile(self.source): - raise NotImplementedError - elif is_7zfile(self.source): - output = Popen('7za a "' + self.source + '" "' + tmpXML + '"', - stdout=PIPE, stderr=STDOUT, stdin=PIPE, shell=True) - extracted = False - for line in output.stdout: - if b"Everything is Ok" in line: - extracted = True - if not extracted: - rmtree(workdir) - raise OSError('Failed to modify 7ZIP file.') + try: + cbx = comicarchive.ComicArchive(self.source) + cbx.addFile(tmpXML) + except OSError as e: + raise UserWarning(e.strerror) rmtree(workdir) diff --git a/kindlecomicconverter/rarfile.py b/kindlecomicconverter/rarfile.py deleted file mode 100644 index afb19a7..0000000 --- a/kindlecomicconverter/rarfile.py +++ /dev/null @@ -1,1990 +0,0 @@ -# rarfile.py -# -# Copyright (c) 2005-2014 Marko Kreen -# -# Permission to use, copy, modify, and/or distribute this software for any -# purpose with or without fee is hereby granted, provided that the above -# copyright notice and this permission notice appear in all copies. -# -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -r"""RAR archive reader. - -This is Python module for Rar archive reading. The interface -is made as :mod:`zipfile`-like as possible. - -Basic logic: - - Parse archive structure with Python. - - Extract non-compressed files with Python - - Extract compressed files with unrar. - - Optionally write compressed data to temp file to speed up unrar, - otherwise it needs to scan whole archive on each execution. - -Example:: - - import rarfile - - rf = rarfile.RarFile('myarchive.rar') - for f in rf.infolist(): - print f.filename, f.file_size - if f.filename == 'README': - print(rf.read(f)) - -Archive files can also be accessed via file-like object returned -by :meth:`RarFile.open`:: - - import rarfile - - with rarfile.RarFile('archive.rar') as rf: - with rf.open('README') as f: - for ln in f: - print(ln.strip()) - -There are few module-level parameters to tune behaviour, -here they are with defaults, and reason to change it:: - - import rarfile - - # Set to full path of unrar.exe if it is not in PATH - rarfile.UNRAR_TOOL = "unrar" - - # Set to 0 if you don't look at comments and want to - # avoid wasting time for parsing them - rarfile.NEED_COMMENTS = 1 - - # Set up to 1 if you don't want to deal with decoding comments - # from unknown encoding. rarfile will try couple of common - # encodings in sequence. - rarfile.UNICODE_COMMENTS = 0 - - # Set to 1 if you prefer timestamps to be datetime objects - # instead tuples - rarfile.USE_DATETIME = 0 - - # Set to '/' to be more compatible with zipfile - rarfile.PATH_SEP = '\\' - -For more details, refer to source. - -""" - -__version__ = '2.7-kcc' - -# export only interesting items -__all__ = ['is_rarfile', 'RarInfo', 'RarFile', 'RarExtFile'] - -## -## Imports and compat - support both Python 2.x and 3.x -## - -import sys, os, struct, errno -from struct import pack, unpack -from binascii import crc32 -from tempfile import mkstemp -from subprocess import Popen, PIPE, STDOUT -from datetime import datetime - -# only needed for encryped headers -try: - from Crypto.Cipher import AES - try: - from hashlib import sha1 - except ImportError: - from sha import new as sha1 - _have_crypto = 1 -except ImportError: - _have_crypto = 0 - -# compat with 2.x -if sys.hexversion < 0x3000000: - # prefer 3.x behaviour - range = xrange - # py2.6 has broken bytes() - def bytes(s, enc): - return str(s) -else: - unicode = str - -# see if compat bytearray() is needed -try: - bytearray -except NameError: - import array - class bytearray: - def __init__(self, val = ''): - self.arr = array.array('B', val) - self.append = self.arr.append - self.__getitem__ = self.arr.__getitem__ - self.__len__ = self.arr.__len__ - def decode(self, *args): - return self.arr.tostring().decode(*args) - -# Optimized .readinto() requires memoryview -try: - memoryview - have_memoryview = 1 -except NameError: - have_memoryview = 0 - -# Struct() for older python -try: - from struct import Struct -except ImportError: - class Struct: - def __init__(self, fmt): - self.format = fmt - self.size = struct.calcsize(fmt) - def unpack(self, buf): - return unpack(self.format, buf) - def unpack_from(self, buf, ofs = 0): - return unpack(self.format, buf[ofs : ofs + self.size]) - def pack(self, *args): - return pack(self.format, *args) - -# file object superclass -try: - from io import RawIOBase -except ImportError: - class RawIOBase(object): - def close(self): - pass - - -## -## Module configuration. Can be tuned after importing. -## - -#: default fallback charset -DEFAULT_CHARSET = "windows-1252" - -#: list of encodings to try, with fallback to DEFAULT_CHARSET if none succeed -TRY_ENCODINGS = ('utf8', 'utf-16le') - -#: 'unrar', 'rar' or full path to either one -UNRAR_TOOL = "unrar" - -#: Command line args to use for opening file for reading. -OPEN_ARGS = ('p', '-inul') - -#: Command line args to use for extracting file to disk. -EXTRACT_ARGS = ('x', '-y', '-idq') - -#: args for testrar() -TEST_ARGS = ('t', '-idq') - -# -# Allow use of tool that is not compatible with unrar. -# -# By default use 'bsdtar' which is 'tar' program that -# sits on top of libarchive. -# -# Problems with libarchive RAR backend: -# - Does not support solid archives. -# - Does not support password-protected archives. -# - -ALT_TOOL = 'bsdtar' -ALT_OPEN_ARGS = ('-x', '--to-stdout', '-f') -ALT_EXTRACT_ARGS = ('-x', '-f') -ALT_TEST_ARGS = ('-t', '-f') -ALT_CHECK_ARGS = ('--help',) - -#: whether to speed up decompression by using tmp archive -USE_EXTRACT_HACK = 0 - -#: limit the filesize for tmp archive usage -HACK_SIZE_LIMIT = 20*1024*1024 - -#: whether to parse file/archive comments. -NEED_COMMENTS = 1 - -#: whether to convert comments to unicode strings -UNICODE_COMMENTS = 0 - -#: Convert RAR time tuple into datetime() object -USE_DATETIME = 0 - -#: Separator for path name components. RAR internally uses '\\'. -#: Use '/' to be similar with zipfile. -PATH_SEP = '\\' - -## -## rar constants -## - -# block types -RAR_BLOCK_MARK = 0x72 # r -RAR_BLOCK_MAIN = 0x73 # s -RAR_BLOCK_FILE = 0x74 # t -RAR_BLOCK_OLD_COMMENT = 0x75 # u -RAR_BLOCK_OLD_EXTRA = 0x76 # v -RAR_BLOCK_OLD_SUB = 0x77 # w -RAR_BLOCK_OLD_RECOVERY = 0x78 # x -RAR_BLOCK_OLD_AUTH = 0x79 # y -RAR_BLOCK_SUB = 0x7a # z -RAR_BLOCK_ENDARC = 0x7b # { - -# flags for RAR_BLOCK_MAIN -RAR_MAIN_VOLUME = 0x0001 -RAR_MAIN_COMMENT = 0x0002 -RAR_MAIN_LOCK = 0x0004 -RAR_MAIN_SOLID = 0x0008 -RAR_MAIN_NEWNUMBERING = 0x0010 -RAR_MAIN_AUTH = 0x0020 -RAR_MAIN_RECOVERY = 0x0040 -RAR_MAIN_PASSWORD = 0x0080 -RAR_MAIN_FIRSTVOLUME = 0x0100 -RAR_MAIN_ENCRYPTVER = 0x0200 - -# flags for RAR_BLOCK_FILE -RAR_FILE_SPLIT_BEFORE = 0x0001 -RAR_FILE_SPLIT_AFTER = 0x0002 -RAR_FILE_PASSWORD = 0x0004 -RAR_FILE_COMMENT = 0x0008 -RAR_FILE_SOLID = 0x0010 -RAR_FILE_DICTMASK = 0x00e0 -RAR_FILE_DICT64 = 0x0000 -RAR_FILE_DICT128 = 0x0020 -RAR_FILE_DICT256 = 0x0040 -RAR_FILE_DICT512 = 0x0060 -RAR_FILE_DICT1024 = 0x0080 -RAR_FILE_DICT2048 = 0x00a0 -RAR_FILE_DICT4096 = 0x00c0 -RAR_FILE_DIRECTORY = 0x00e0 -RAR_FILE_LARGE = 0x0100 -RAR_FILE_UNICODE = 0x0200 -RAR_FILE_SALT = 0x0400 -RAR_FILE_VERSION = 0x0800 -RAR_FILE_EXTTIME = 0x1000 -RAR_FILE_EXTFLAGS = 0x2000 - -# flags for RAR_BLOCK_ENDARC -RAR_ENDARC_NEXT_VOLUME = 0x0001 -RAR_ENDARC_DATACRC = 0x0002 -RAR_ENDARC_REVSPACE = 0x0004 -RAR_ENDARC_VOLNR = 0x0008 - -# flags common to all blocks -RAR_SKIP_IF_UNKNOWN = 0x4000 -RAR_LONG_BLOCK = 0x8000 - -# Host OS types -RAR_OS_MSDOS = 0 -RAR_OS_OS2 = 1 -RAR_OS_WIN32 = 2 -RAR_OS_UNIX = 3 -RAR_OS_MACOS = 4 -RAR_OS_BEOS = 5 - -# Compression methods - '0'..'5' -RAR_M0 = 0x30 -RAR_M1 = 0x31 -RAR_M2 = 0x32 -RAR_M3 = 0x33 -RAR_M4 = 0x34 -RAR_M5 = 0x35 - -## -## internal constants -## - -RAR_ID = bytes("Rar!\x1a\x07\x00", 'ascii') -RAR5_ID = bytes("Rar!\x1a\x07\x01", 'ascii') -ZERO = bytes("\0", 'ascii') -EMPTY = bytes("", 'ascii') - -S_BLK_HDR = Struct(' HACK_SIZE_LIMIT: - use_hack = 0 - elif not USE_EXTRACT_HACK: - use_hack = 0 - - # now extract - if inf.compress_type == RAR_M0 and (inf.flags & RAR_FILE_PASSWORD) == 0: - return self._open_clear(inf) - elif use_hack: - return self._open_hack(inf, psw) - else: - return self._open_unrar(self.rarfile, inf, psw) - - def read(self, fname, psw = None): - """Return uncompressed data for archive entry. - - For longer files using :meth:`RarFile.open` may be better idea. - - Parameters: - - fname - filename or RarInfo instance - psw - password to use for extracting. - """ - - f = self.open(fname, 'r', psw) - try: - return f.read() - finally: - f.close() - - def close(self): - """Release open resources.""" - pass - - def printdir(self): - """Print archive file list to stdout.""" - for f in self._info_list: - print(f.filename) - - def extract(self, member, path=None, pwd=None): - """Extract single file into current directory. - - Parameters: - - member - filename or :class:`RarInfo` instance - path - optional destination path - pwd - optional password to use - """ - if isinstance(member, RarInfo): - fname = member.filename - else: - fname = member - self._extract([fname], path, pwd) - - def extractall(self, path=None, members=None, pwd=None): - """Extract all files into current directory. - - Parameters: - - path - optional destination path - members - optional filename or :class:`RarInfo` instance list to extract - pwd - optional password to use - """ - fnlist = [] - if members is not None: - for m in members: - if isinstance(m, RarInfo): - fnlist.append(m.filename) - else: - fnlist.append(m) - self._extract(fnlist, path, pwd) - - def testrar(self): - """Let 'unrar' test the archive. - """ - cmd = [UNRAR_TOOL] + list(TEST_ARGS) - add_password_arg(cmd, self._password) - cmd.append(self.rarfile) - p = custom_popen(cmd) - output = p.communicate()[0] - check_returncode(p, output) - - def strerror(self): - """Return error string if parsing failed, - or None if no problems. - """ - return self._parse_error - - ## - ## private methods - ## - - def _set_error(self, msg, *args): - if args: - msg = msg % args - self._parse_error = msg - if self._strict: - raise BadRarFile(msg) - - # store entry - def _process_entry(self, item): - if item.type == RAR_BLOCK_FILE: - # use only first part - if (item.flags & RAR_FILE_SPLIT_BEFORE) == 0: - self._info_map[item.filename] = item - self._info_list.append(item) - # remember if any items require password - if item.needs_password(): - self._needs_password = True - elif len(self._info_list) > 0: - # final crc is in last block - old = self._info_list[-1] - old.CRC = item.CRC - old.compress_size += item.compress_size - - # parse new-style comment - if item.type == RAR_BLOCK_SUB and item.filename == 'CMT': - if not NEED_COMMENTS: - pass - elif item.flags & (RAR_FILE_SPLIT_BEFORE | RAR_FILE_SPLIT_AFTER): - pass - elif item.flags & RAR_FILE_SOLID: - # file comment - cmt = self._read_comment_v3(item, self._password) - if len(self._info_list) > 0: - old = self._info_list[-1] - old.comment = cmt - else: - # archive comment - cmt = self._read_comment_v3(item, self._password) - self.comment = cmt - - if self._info_callback: - self._info_callback(item) - - # read rar - def _parse(self): - self._fd = None - try: - self._parse_real() - finally: - if self._fd: - self._fd.close() - self._fd = None - - def _parse_real(self): - fd = XFile(self.rarfile) - self._fd = fd - id = fd.read(len(RAR_ID)) - if id != RAR_ID and id != RAR5_ID: - raise NotRarFile("Not a Rar archive: "+self.rarfile) - - volume = 0 # first vol (.rar) is 0 - more_vols = 0 - endarc = 0 - volfile = self.rarfile - self._vol_list = [self.rarfile] - while 1: - if endarc: - h = None # don't read past ENDARC - else: - h = self._parse_header(fd) - if not h: - if more_vols: - volume += 1 - fd.close() - try: - volfile = self._next_volname(volfile) - fd = XFile(volfile) - except IOError: - self._set_error("Cannot open next volume: %s", volfile) - break - self._fd = fd - more_vols = 0 - endarc = 0 - self._vol_list.append(volfile) - continue - break - h.volume = volume - h.volume_file = volfile - - if h.type == RAR_BLOCK_MAIN and not self._main: - self._main = h - if h.flags & RAR_MAIN_NEWNUMBERING: - # RAR 2.x does not set FIRSTVOLUME, - # so check it only if NEWNUMBERING is used - if (h.flags & RAR_MAIN_FIRSTVOLUME) == 0: - raise NeedFirstVolume("Need to start from first volume") - if h.flags & RAR_MAIN_PASSWORD: - self._needs_password = True - if not self._password: - self._main = None - break - elif h.type == RAR_BLOCK_ENDARC: - more_vols = h.flags & RAR_ENDARC_NEXT_VOLUME - endarc = 1 - elif h.type == RAR_BLOCK_FILE: - # RAR 2.x does not write RAR_BLOCK_ENDARC - if h.flags & RAR_FILE_SPLIT_AFTER: - more_vols = 1 - # RAR 2.x does not set RAR_MAIN_FIRSTVOLUME - if volume == 0 and h.flags & RAR_FILE_SPLIT_BEFORE: - raise NeedFirstVolume("Need to start from first volume") - - # store it - self._process_entry(h) - - # go to next header - if h.add_size > 0: - fd.seek(h.file_offset + h.add_size, 0) - - # AES encrypted headers - _last_aes_key = (None, None, None) # (salt, key, iv) - def _decrypt_header(self, fd): - if not _have_crypto: - raise NoCrypto('Cannot parse encrypted headers - no crypto') - salt = fd.read(8) - if self._last_aes_key[0] == salt: - key, iv = self._last_aes_key[1:] - else: - key, iv = rar3_s2k(self._password, salt) - self._last_aes_key = (salt, key, iv) - return HeaderDecrypt(fd, key, iv) - - # read single header - def _parse_header(self, fd): - try: - # handle encrypted headers - if self._main and self._main.flags & RAR_MAIN_PASSWORD: - if not self._password: - return - fd = self._decrypt_header(fd) - - # now read actual header - return self._parse_block_header(fd) - except struct.error: - self._set_error('Broken header in RAR file') - return None - - # common header - def _parse_block_header(self, fd): - h = RarInfo() - h.header_offset = fd.tell() - h.comment = None - - # read and parse base header - buf = fd.read(S_BLK_HDR.size) - if not buf: - return None - t = S_BLK_HDR.unpack_from(buf) - h.header_crc, h.type, h.flags, h.header_size = t - h.header_base = S_BLK_HDR.size - pos = S_BLK_HDR.size - - # read full header - if h.header_size > S_BLK_HDR.size: - h.header_data = buf + fd.read(h.header_size - S_BLK_HDR.size) - else: - h.header_data = buf - h.file_offset = fd.tell() - - # unexpected EOF? - if len(h.header_data) != h.header_size: - self._set_error('Unexpected EOF when reading header') - return None - - # block has data assiciated with it? - if h.flags & RAR_LONG_BLOCK: - h.add_size = S_LONG.unpack_from(h.header_data, pos)[0] - else: - h.add_size = 0 - - # parse interesting ones, decide header boundaries for crc - if h.type == RAR_BLOCK_MARK: - return h - elif h.type == RAR_BLOCK_MAIN: - h.header_base += 6 - if h.flags & RAR_MAIN_ENCRYPTVER: - h.header_base += 1 - if h.flags & RAR_MAIN_COMMENT: - self._parse_subblocks(h, h.header_base) - self.comment = h.comment - elif h.type == RAR_BLOCK_FILE: - self._parse_file_header(h, pos) - elif h.type == RAR_BLOCK_SUB: - self._parse_file_header(h, pos) - h.header_base = h.header_size - elif h.type == RAR_BLOCK_OLD_AUTH: - h.header_base += 8 - elif h.type == RAR_BLOCK_OLD_EXTRA: - h.header_base += 7 - else: - h.header_base = h.header_size - - # check crc - if h.type == RAR_BLOCK_OLD_SUB: - crcdat = h.header_data[2:] + fd.read(h.add_size) - else: - crcdat = h.header_data[2:h.header_base] - - calc_crc = crc32(crcdat) & 0xFFFF - - # return good header - if h.header_crc == calc_crc: - return h - - # header parsing failed. - self._set_error('Header CRC error (%02x): exp=%x got=%x (xlen = %d)', - h.type, h.header_crc, calc_crc, len(crcdat)) - - # instead panicing, send eof - return None - - # read file-specific header - def _parse_file_header(self, h, pos): - fld = S_FILE_HDR.unpack_from(h.header_data, pos) - h.compress_size = fld[0] - h.file_size = fld[1] - h.host_os = fld[2] - h.CRC = fld[3] - h.date_time = parse_dos_time(fld[4]) - h.extract_version = fld[5] - h.compress_type = fld[6] - h.name_size = fld[7] - h.mode = fld[8] - pos += S_FILE_HDR.size - - if h.flags & RAR_FILE_LARGE: - h1 = S_LONG.unpack_from(h.header_data, pos)[0] - h2 = S_LONG.unpack_from(h.header_data, pos + 4)[0] - h.compress_size |= h1 << 32 - h.file_size |= h2 << 32 - pos += 8 - h.add_size = h.compress_size - - name = h.header_data[pos : pos + h.name_size ] - pos += h.name_size - if h.flags & RAR_FILE_UNICODE: - nul = name.find(ZERO) - h.orig_filename = name[:nul] - u = UnicodeFilename(h.orig_filename, name[nul + 1 : ]) - h.filename = u.decode() - - # if parsing failed fall back to simple name - if u.failed: - h.filename = self._decode(h.orig_filename) - else: - h.orig_filename = name - h.filename = self._decode(name) - - # change separator, if requested - if PATH_SEP != '\\': - h.filename = h.filename.replace('\\', PATH_SEP) - - if h.flags & RAR_FILE_SALT: - h.salt = h.header_data[pos : pos + 8] - pos += 8 - else: - h.salt = None - - # optional extended time stamps - if h.flags & RAR_FILE_EXTTIME: - pos = self._parse_ext_time(h, pos) - else: - h.mtime = h.atime = h.ctime = h.arctime = None - - # base header end - h.header_base = pos - - if h.flags & RAR_FILE_COMMENT: - self._parse_subblocks(h, pos) - - # convert timestamps - if USE_DATETIME: - h.date_time = to_datetime(h.date_time) - h.mtime = to_datetime(h.mtime) - h.atime = to_datetime(h.atime) - h.ctime = to_datetime(h.ctime) - h.arctime = to_datetime(h.arctime) - - # .mtime is .date_time with more precision - if h.mtime: - if USE_DATETIME: - h.date_time = h.mtime - else: - # keep seconds int - h.date_time = h.mtime[:5] + (int(h.mtime[5]),) - - return pos - - # find old-style comment subblock - def _parse_subblocks(self, h, pos): - hdata = h.header_data - while pos < len(hdata): - # ordinary block header - t = S_BLK_HDR.unpack_from(hdata, pos) - scrc, stype, sflags, slen = t - pos_next = pos + slen - pos += S_BLK_HDR.size - - # corrupt header - if pos_next < pos: - break - - # followed by block-specific header - if stype == RAR_BLOCK_OLD_COMMENT and pos + S_COMMENT_HDR.size <= pos_next: - declen, ver, meth, crc = S_COMMENT_HDR.unpack_from(hdata, pos) - pos += S_COMMENT_HDR.size - data = hdata[pos : pos_next] - cmt = rar_decompress(ver, meth, data, declen, sflags, - crc, self._password) - if not self._crc_check: - h.comment = self._decode_comment(cmt) - elif crc32(cmt) & 0xFFFF == crc: - h.comment = self._decode_comment(cmt) - - pos = pos_next - - def _parse_ext_time(self, h, pos): - data = h.header_data - - # flags and rest of data can be missing - flags = 0 - if pos + 2 <= len(data): - flags = S_SHORT.unpack_from(data, pos)[0] - pos += 2 - - h.mtime, pos = self._parse_xtime(flags >> 3*4, data, pos, h.date_time) - h.ctime, pos = self._parse_xtime(flags >> 2*4, data, pos) - h.atime, pos = self._parse_xtime(flags >> 1*4, data, pos) - h.arctime, pos = self._parse_xtime(flags >> 0*4, data, pos) - return pos - - def _parse_xtime(self, flag, data, pos, dostime = None): - unit = 10000000.0 # 100 ns units - if flag & 8: - if not dostime: - t = S_LONG.unpack_from(data, pos)[0] - dostime = parse_dos_time(t) - pos += 4 - rem = 0 - cnt = flag & 3 - for i in range(cnt): - b = S_BYTE.unpack_from(data, pos)[0] - rem = (b << 16) | (rem >> 8) - pos += 1 - sec = dostime[5] + rem / unit - if flag & 4: - sec += 1 - dostime = dostime[:5] + (sec,) - return dostime, pos - - # given current vol name, construct next one - def _next_volname(self, volfile): - if is_filelike(volfile): - raise IOError("Working on single FD") - if self._main.flags & RAR_MAIN_NEWNUMBERING: - return self._next_newvol(volfile) - return self._next_oldvol(volfile) - - # new-style next volume - def _next_newvol(self, volfile): - i = len(volfile) - 1 - while i >= 0: - if volfile[i] >= '0' and volfile[i] <= '9': - return self._inc_volname(volfile, i) - i -= 1 - raise BadRarName("Cannot construct volume name: "+volfile) - - # old-style next volume - def _next_oldvol(self, volfile): - # rar -> r00 - if volfile[-4:].lower() == '.rar': - return volfile[:-2] + '00' - return self._inc_volname(volfile, len(volfile) - 1) - - # increase digits with carry, otherwise just increment char - def _inc_volname(self, volfile, i): - fn = list(volfile) - while i >= 0: - if fn[i] != '9': - fn[i] = chr(ord(fn[i]) + 1) - break - fn[i] = '0' - i -= 1 - return ''.join(fn) - - def _open_clear(self, inf): - return DirectReader(self, inf) - - # put file compressed data into temporary .rar archive, and run - # unrar on that, thus avoiding unrar going over whole archive - def _open_hack(self, inf, psw = None): - BSIZE = 32*1024 - - size = inf.compress_size + inf.header_size - rf = XFile(inf.volume_file, 0) - rf.seek(inf.header_offset) - - tmpfd, tmpname = mkstemp(suffix='.rar') - tmpf = os.fdopen(tmpfd, "wb") - - try: - # create main header: crc, type, flags, size, res1, res2 - mh = S_BLK_HDR.pack(0x90CF, 0x73, 0, 13) + ZERO * (2+4) - tmpf.write(RAR_ID + mh) - while size > 0: - if size > BSIZE: - buf = rf.read(BSIZE) - else: - buf = rf.read(size) - if not buf: - raise BadRarFile('read failed: ' + inf.filename) - tmpf.write(buf) - size -= len(buf) - tmpf.close() - rf.close() - except: - rf.close() - tmpf.close() - os.unlink(tmpname) - raise - - return self._open_unrar(tmpname, inf, psw, tmpname) - - def _read_comment_v3(self, inf, psw=None): - - # read data - rf = XFile(inf.volume_file) - rf.seek(inf.file_offset) - data = rf.read(inf.compress_size) - rf.close() - - # decompress - cmt = rar_decompress(inf.extract_version, inf.compress_type, data, - inf.file_size, inf.flags, inf.CRC, psw, inf.salt) - - # check crc - if self._crc_check: - crc = crc32(cmt) - if crc < 0: - crc += (long(1) << 32) - if crc != inf.CRC: - return None - - return self._decode_comment(cmt) - - # extract using unrar - def _open_unrar(self, rarfile, inf, psw = None, tmpfile = None): - if is_filelike(rarfile): - raise ValueError("Cannot use unrar directly on memory buffer") - cmd = [UNRAR_TOOL] + list(OPEN_ARGS) - add_password_arg(cmd, psw) - cmd.append(rarfile) - - # not giving filename avoids encoding related problems - if not tmpfile: - fn = inf.filename - if PATH_SEP != os.sep: - fn = fn.replace(PATH_SEP, os.sep) - cmd.append(fn) - - # read from unrar pipe - return PipeReader(self, inf, cmd, tmpfile) - - def _decode(self, val): - for c in TRY_ENCODINGS: - try: - return val.decode(c) - except UnicodeError: - pass - return val.decode(self._charset, 'replace') - - def _decode_comment(self, val): - if UNICODE_COMMENTS: - return self._decode(val) - return val - - # call unrar to extract a file - def _extract(self, fnlist, path=None, psw=None): - cmd = [UNRAR_TOOL] + list(EXTRACT_ARGS) - - # pasoword - psw = psw or self._password - add_password_arg(cmd, psw) - - # rar file - cmd.append(self.rarfile) - - # file list - for fn in fnlist: - if os.sep != PATH_SEP: - fn = fn.replace(PATH_SEP, os.sep) - cmd.append(fn) - - # destination path - if path is not None: - cmd.append(path + os.sep) - - # call - p = custom_popen(cmd) - output = p.communicate()[0] - check_returncode(p, output) - -## -## Utility classes -## - -class UnicodeFilename: - """Handle unicode filename decompression""" - - def __init__(self, name, encdata): - self.std_name = bytearray(name) - self.encdata = bytearray(encdata) - self.pos = self.encpos = 0 - self.buf = bytearray() - self.failed = 0 - - def enc_byte(self): - try: - c = self.encdata[self.encpos] - self.encpos += 1 - return c - except IndexError: - self.failed = 1 - return 0 - - def std_byte(self): - try: - return self.std_name[self.pos] - except IndexError: - self.failed = 1 - return ord('?') - - def put(self, lo, hi): - self.buf.append(lo) - self.buf.append(hi) - self.pos += 1 - - def decode(self): - hi = self.enc_byte() - flagbits = 0 - while self.encpos < len(self.encdata): - if flagbits == 0: - flags = self.enc_byte() - flagbits = 8 - flagbits -= 2 - t = (flags >> flagbits) & 3 - if t == 0: - self.put(self.enc_byte(), 0) - elif t == 1: - self.put(self.enc_byte(), hi) - elif t == 2: - self.put(self.enc_byte(), self.enc_byte()) - else: - n = self.enc_byte() - if n & 0x80: - c = self.enc_byte() - for i in range((n & 0x7f) + 2): - lo = (self.std_byte() + c) & 0xFF - self.put(lo, hi) - else: - for i in range(n + 2): - self.put(self.std_byte(), 0) - return self.buf.decode("utf-16le", "replace") - - -class RarExtFile(RawIOBase): - """Base class for file-like object that :meth:`RarFile.open` returns. - - Provides public methods and common crc checking. - - Behaviour: - - no short reads - .read() and .readinfo() read as much as requested. - - no internal buffer, use io.BufferedReader for that. - - If :mod:`io` module is available (Python 2.6+, 3.x), then this calls - will inherit from :class:`io.RawIOBase` class. This makes line-based - access available: :meth:`RarExtFile.readline` and ``for ln in f``. - """ - - #: Filename of the archive entry - name = None - - def __init__(self, rf, inf): - RawIOBase.__init__(self) - - # standard io.* properties - self.name = inf.filename - self.mode = 'rb' - - self.rf = rf - self.inf = inf - self.crc_check = rf._crc_check - self.fd = None - self.CRC = 0 - self.remain = 0 - self.returncode = 0 - - self._open() - - def _open(self): - if self.fd: - self.fd.close() - self.fd = None - self.CRC = 0 - self.remain = self.inf.file_size - - def read(self, cnt = None): - """Read all or specified amount of data from archive entry.""" - - # sanitize cnt - if cnt is None or cnt < 0: - cnt = self.remain - elif cnt > self.remain: - cnt = self.remain - if cnt == 0: - return EMPTY - - # actual read - data = self._read(cnt) - if data: - self.CRC = crc32(data, self.CRC) - self.remain -= len(data) - if len(data) != cnt: - raise BadRarFile("Failed the read enough data") - - # done? - if not data or self.remain == 0: - #self.close() - self._check() - return data - - def _check(self): - """Check final CRC.""" - if not self.crc_check: - return - if self.returncode: - check_returncode(self, '') - if self.remain != 0: - raise BadRarFile("Failed the read enough data") - crc = self.CRC - if crc < 0: - crc += (long(1) << 32) - if crc != self.inf.CRC: - raise BadRarFile("Corrupt file - CRC check failed: " + self.inf.filename) - - def _read(self, cnt): - """Actual read that gets sanitized cnt.""" - - def close(self): - """Close open resources.""" - - RawIOBase.close(self) - - if self.fd: - self.fd.close() - self.fd = None - - def __del__(self): - """Hook delete to make sure tempfile is removed.""" - self.close() - - def readinto(self, buf): - """Zero-copy read directly into buffer. - - Returns bytes read. - """ - - data = self.read(len(buf)) - n = len(data) - try: - buf[:n] = data - except TypeError: - import array - if not isinstance(buf, array.array): - raise - buf[:n] = array.array(buf.typecode, data) - return n - - def tell(self): - """Return current reading position in uncompressed data.""" - return self.inf.file_size - self.remain - - def seek(self, ofs, whence = 0): - """Seek in data. - - On uncompressed files, the seeking works by actual - seeks so it's fast. On compresses files its slow - - forward seeking happends by reading ahead, - backwards by re-opening and decompressing from the start. - """ - - # disable crc check when seeking - self.crc_check = 0 - - fsize = self.inf.file_size - cur_ofs = self.tell() - - if whence == 0: # seek from beginning of file - new_ofs = ofs - elif whence == 1: # seek from current position - new_ofs = cur_ofs + ofs - elif whence == 2: # seek from end of file - new_ofs = fsize + ofs - else: - raise ValueError('Invalid value for whence') - - # sanity check - if new_ofs < 0: - new_ofs = 0 - elif new_ofs > fsize: - new_ofs = fsize - - # do the actual seek - if new_ofs >= cur_ofs: - self._skip(new_ofs - cur_ofs) - else: - # process old data ? - #self._skip(fsize - cur_ofs) - # reopen and seek - self._open() - self._skip(new_ofs) - return self.tell() - - def _skip(self, cnt): - """Read and discard data""" - while cnt > 0: - if cnt > 8192: - buf = self.read(8192) - else: - buf = self.read(cnt) - if not buf: - break - cnt -= len(buf) - - def readable(self): - """Returns True""" - return True - - def writable(self): - """Returns False. - - Writing is not supported.""" - return False - - def seekable(self): - """Returns True. - - Seeking is supported, although it's slow on compressed files. - """ - return True - - def readall(self): - """Read all remaining data""" - # avoid RawIOBase default impl - return self.read() - - -class PipeReader(RarExtFile): - """Read data from pipe, handle tempfile cleanup.""" - - def __init__(self, rf, inf, cmd, tempfile=None): - self.cmd = cmd - self.proc = None - self.tempfile = tempfile - RarExtFile.__init__(self, rf, inf) - - def _close_proc(self): - if not self.proc: - return - if self.proc.stdout: - self.proc.stdout.close() - if self.proc.stdin: - self.proc.stdin.close() - if self.proc.stderr: - self.proc.stderr.close() - self.proc.wait() - self.returncode = self.proc.returncode - self.proc = None - - def _open(self): - RarExtFile._open(self) - - # stop old process - self._close_proc() - - # launch new process - self.returncode = 0 - self.proc = custom_popen(self.cmd) - self.fd = self.proc.stdout - - # avoid situation where unrar waits on stdin - if self.proc.stdin: - self.proc.stdin.close() - - def _read(self, cnt): - """Read from pipe.""" - - # normal read is usually enough - data = self.fd.read(cnt) - if len(data) == cnt or not data: - return data - - # short read, try looping - buf = [data] - cnt -= len(data) - while cnt > 0: - data = self.fd.read(cnt) - if not data: - break - cnt -= len(data) - buf.append(data) - return EMPTY.join(buf) - - def close(self): - """Close open resources.""" - - self._close_proc() - RarExtFile.close(self) - - if self.tempfile: - try: - os.unlink(self.tempfile) - except OSError: - pass - self.tempfile = None - - if have_memoryview: - def readinto(self, buf): - """Zero-copy read directly into buffer.""" - cnt = len(buf) - if cnt > self.remain: - cnt = self.remain - vbuf = memoryview(buf) - res = got = 0 - while got < cnt: - res = self.fd.readinto(vbuf[got : cnt]) - if not res: - break - if self.crc_check: - self.CRC = crc32(vbuf[got : got + res], self.CRC) - self.remain -= res - got += res - return got - - -class DirectReader(RarExtFile): - """Read uncompressed data directly from archive.""" - - def _open(self): - RarExtFile._open(self) - - self.volfile = self.inf.volume_file - self.fd = XFile(self.volfile, 0) - self.fd.seek(self.inf.header_offset, 0) - self.cur = self.rf._parse_header(self.fd) - self.cur_avail = self.cur.add_size - - def _skip(self, cnt): - """RAR Seek, skipping through rar files to get to correct position - """ - - while cnt > 0: - # next vol needed? - if self.cur_avail == 0: - if not self._open_next(): - break - - # fd is in read pos, do the read - if cnt > self.cur_avail: - cnt -= self.cur_avail - self.remain -= self.cur_avail - self.cur_avail = 0 - else: - self.fd.seek(cnt, 1) - self.cur_avail -= cnt - self.remain -= cnt - cnt = 0 - - def _read(self, cnt): - """Read from potentially multi-volume archive.""" - - buf = [] - while cnt > 0: - # next vol needed? - if self.cur_avail == 0: - if not self._open_next(): - break - - # fd is in read pos, do the read - if cnt > self.cur_avail: - data = self.fd.read(self.cur_avail) - else: - data = self.fd.read(cnt) - if not data: - break - - # got some data - cnt -= len(data) - self.cur_avail -= len(data) - buf.append(data) - - if len(buf) == 1: - return buf[0] - return EMPTY.join(buf) - - def _open_next(self): - """Proceed to next volume.""" - - # is the file split over archives? - if (self.cur.flags & RAR_FILE_SPLIT_AFTER) == 0: - return False - - if self.fd: - self.fd.close() - self.fd = None - - # open next part - self.volfile = self.rf._next_volname(self.volfile) - fd = open(self.volfile, "rb", 0) - self.fd = fd - - # loop until first file header - while 1: - cur = self.rf._parse_header(fd) - if not cur: - raise BadRarFile("Unexpected EOF") - if cur.type in (RAR_BLOCK_MARK, RAR_BLOCK_MAIN): - if cur.add_size: - fd.seek(cur.add_size, 1) - continue - if cur.orig_filename != self.inf.orig_filename: - raise BadRarFile("Did not found file entry") - self.cur = cur - self.cur_avail = cur.add_size - return True - - if have_memoryview: - def readinto(self, buf): - """Zero-copy read directly into buffer.""" - got = 0 - vbuf = memoryview(buf) - while got < len(buf): - # next vol needed? - if self.cur_avail == 0: - if not self._open_next(): - break - - # lenght for next read - cnt = len(buf) - got - if cnt > self.cur_avail: - cnt = self.cur_avail - - # read into temp view - res = self.fd.readinto(vbuf[got : got + cnt]) - if not res: - break - if self.crc_check: - self.CRC = crc32(vbuf[got : got + res], self.CRC) - self.cur_avail -= res - self.remain -= res - got += res - return got - - -class HeaderDecrypt: - """File-like object that decrypts from another file""" - def __init__(self, f, key, iv): - self.f = f - self.ciph = AES.new(key, AES.MODE_CBC, iv) - self.buf = EMPTY - - def tell(self): - return self.f.tell() - - def read(self, cnt=None): - if cnt > 8*1024: - raise BadRarFile('Bad count to header decrypt - wrong password?') - - # consume old data - if cnt <= len(self.buf): - res = self.buf[:cnt] - self.buf = self.buf[cnt:] - return res - res = self.buf - self.buf = EMPTY - cnt -= len(res) - - # decrypt new data - BLK = self.ciph.block_size - while cnt > 0: - enc = self.f.read(BLK) - if len(enc) < BLK: - break - dec = self.ciph.decrypt(enc) - if cnt >= len(dec): - res += dec - cnt -= len(dec) - else: - res += dec[:cnt] - self.buf = dec[cnt:] - cnt = 0 - - return res - -# handle (filename|filelike) object -class XFile(object): - __slots__ = ('_fd', '_need_close') - def __init__(self, xfile, bufsize = 1024): - if is_filelike(xfile): - self._need_close = False - self._fd = xfile - self._fd.seek(0) - else: - self._need_close = True - self._fd = open(xfile, 'rb', bufsize) - def read(self, n=None): - return self._fd.read(n) - def tell(self): - return self._fd.tell() - def seek(self, ofs, whence=0): - return self._fd.seek(ofs, whence) - def readinto(self, dst): - return self._fd.readinto(dst) - def close(self): - if self._need_close: - self._fd.close() - def __enter__(self): - return self - def __exit__(self, typ, val, tb): - self.close() - -## -## Utility functions -## - -def is_filelike(obj): - if isinstance(obj, str) or isinstance(obj, unicode): - return False - res = True - for a in ('read', 'tell', 'seek'): - res = res and hasattr(obj, a) - if not res: - raise ValueError("Invalid object passed as file") - return True - -def rar3_s2k(psw, salt): - """String-to-key hash for RAR3.""" - - seed = psw.encode('utf-16le') + salt - iv = EMPTY - h = sha1() - for i in range(16): - for j in range(0x4000): - cnt = S_LONG.pack(i*0x4000 + j) - h.update(seed + cnt[:3]) - if j == 0: - iv += h.digest()[19:20] - key_be = h.digest()[:16] - key_le = pack("LLLL", key_be)) - return key_le, iv - -def rar_decompress(vers, meth, data, declen=0, flags=0, crc=0, psw=None, salt=None): - """Decompress blob of compressed data. - - Used for data with non-standard header - eg. comments. - """ - - # already uncompressed? - if meth == RAR_M0 and (flags & RAR_FILE_PASSWORD) == 0: - return data - - # take only necessary flags - flags = flags & (RAR_FILE_PASSWORD | RAR_FILE_SALT | RAR_FILE_DICTMASK) - flags |= RAR_LONG_BLOCK - - # file header - fname = bytes('data', 'ascii') - date = 0 - mode = 0x20 - fhdr = S_FILE_HDR.pack(len(data), declen, RAR_OS_MSDOS, crc, - date, vers, meth, len(fname), mode) - fhdr += fname - if flags & RAR_FILE_SALT: - if not salt: - return EMPTY - fhdr += salt - - # full header - hlen = S_BLK_HDR.size + len(fhdr) - hdr = S_BLK_HDR.pack(0, RAR_BLOCK_FILE, flags, hlen) + fhdr - hcrc = crc32(hdr[2:]) & 0xFFFF - hdr = S_BLK_HDR.pack(hcrc, RAR_BLOCK_FILE, flags, hlen) + fhdr - - # archive main header - mh = S_BLK_HDR.pack(0x90CF, RAR_BLOCK_MAIN, 0, 13) + ZERO * (2+4) - - # decompress via temp rar - tmpfd, tmpname = mkstemp(suffix='.rar') - tmpf = os.fdopen(tmpfd, "wb") - try: - tmpf.write(RAR_ID + mh + hdr + data) - tmpf.close() - - cmd = [UNRAR_TOOL] + list(OPEN_ARGS) - add_password_arg(cmd, psw, (flags & RAR_FILE_PASSWORD)) - cmd.append(tmpname) - - p = custom_popen(cmd) - return p.communicate()[0] - finally: - tmpf.close() - os.unlink(tmpname) - -def to_datetime(t): - """Convert 6-part time tuple into datetime object.""" - - if t is None: - return None - - # extract values - year, mon, day, h, m, xs = t - s = int(xs) - us = int(1000000 * (xs - s)) - - # assume the values are valid - try: - return datetime(year, mon, day, h, m, s, us) - except ValueError: - pass - - # sanitize invalid values - MDAY = (0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) - if mon < 1: mon = 1 - if mon > 12: mon = 12 - if day < 1: day = 1 - if day > MDAY[mon]: day = MDAY[mon] - if h > 23: h = 23 - if m > 59: m = 59 - if s > 59: s = 59 - if mon == 2 and day == 29: - try: - return datetime(year, mon, day, h, m, s, us) - except ValueError: - day = 28 - return datetime(year, mon, day, h, m, s, us) - -def parse_dos_time(stamp): - """Parse standard 32-bit DOS timestamp.""" - - sec = stamp & 0x1F; stamp = stamp >> 5 - min = stamp & 0x3F; stamp = stamp >> 6 - hr = stamp & 0x1F; stamp = stamp >> 5 - day = stamp & 0x1F; stamp = stamp >> 5 - mon = stamp & 0x0F; stamp = stamp >> 4 - yr = (stamp & 0x7F) + 1980 - return (yr, mon, day, hr, min, sec * 2) - -def custom_popen(cmd): - """Disconnect cmd from parent fds, read only from stdout.""" - - # needed for py2exe - creationflags = 0 - if sys.platform == 'win32': - creationflags = 0x08000000 # CREATE_NO_WINDOW - - # run command - try: - p = Popen(cmd, bufsize = 0, - stdout = PIPE, stdin = PIPE, stderr = STDOUT, - creationflags = creationflags) - except OSError: - ex = sys.exc_info()[1] - if ex.errno == errno.ENOENT: - raise RarCannotExec("Unrar not installed? (rarfile.UNRAR_TOOL=%r)" % UNRAR_TOOL) - raise - return p - -def custom_check(cmd, ignore_retcode=False): - """Run command, collect output, raise error if needed.""" - p = custom_popen(cmd) - out, err = p.communicate() - if p.returncode and not ignore_retcode: - raise RarExecError("Check-run failed") - return out - -def add_password_arg(cmd, psw, required=False): - """Append password switch to commandline.""" - if UNRAR_TOOL == ALT_TOOL: - return - if psw is not None: - cmd.append('-p' + psw) - else: - cmd.append('-p-') - -def check_returncode(p, out): - """Raise exception according to unrar exit code""" - - code = p.returncode - if code == 0: - return - if code == 9: - return - - # map return code to exception class - errmap = [None, - RarWarning, RarFatalError, RarCRCError, RarLockedArchiveError, - RarWriteError, RarOpenError, RarUserError, RarMemoryError, - RarCreateError, RarNoFilesError] # codes from rar.txt - if UNRAR_TOOL == ALT_TOOL: - errmap = [None] - if code > 0 and code < len(errmap): - exc = errmap[code] - elif code == 255: - exc = RarUserBreak - elif code < 0: - exc = RarSignalExit - else: - exc = RarUnknownError - - # format message - if out: - msg = "%s [%d]: %s" % (exc.__doc__, p.returncode, out) - else: - msg = "%s [%d]" % (exc.__doc__, p.returncode) - - raise exc(msg) - -# -# Check if unrar works -# - -try: - # does UNRAR_TOOL work? - custom_check([UNRAR_TOOL], True) -except RarCannotExec: - try: - # does ALT_TOOL work? - custom_check([ALT_TOOL] + list(ALT_CHECK_ARGS), True) - # replace config - UNRAR_TOOL = ALT_TOOL - OPEN_ARGS = ALT_OPEN_ARGS - EXTRACT_ARGS = ALT_EXTRACT_ARGS - TEST_ARGS = ALT_TEST_ARGS - except RarCannotExec: - # no usable tool, only uncompressed archives work - pass - diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index bdfd3e7..32f656c 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -24,7 +24,6 @@ from html.parser import HTMLParser from distutils.version import StrictVersion from shutil import rmtree, copy from tempfile import mkdtemp -from zipfile import ZipFile, ZIP_DEFLATED from re import split from traceback import format_tb @@ -87,26 +86,6 @@ def md5Checksum(fpath): return m.hexdigest() -def check7ZFile(fpath): - with open(fpath, 'rb') as fh: - header = fh.read(6) - return header == b"7z\xbc\xaf'\x1c" - - -def removeFromZIP(zipfname, *filenames): - tempdir = mkdtemp('', 'KCC-') - try: - tempname = os.path.join(tempdir, 'KCC.zip') - with ZipFile(zipfname, 'r') as zipread: - with ZipFile(tempname, 'w', compression=ZIP_DEFLATED) as zipwrite: - for item in zipread.infolist(): - if item.filename not in filenames: - zipwrite.writestr(item, zipread.read(item.filename)) - copy(tempname, zipfname) - finally: - rmtree(tempdir, True) - - def sanitizeTrace(traceback): return ''.join(format_tb(traceback))\ .replace('C:/projects/kcc/', '') \ diff --git a/kindlecomicconverter/startup.py b/kindlecomicconverter/startup.py index 158f520..4af160e 100644 --- a/kindlecomicconverter/startup.py +++ b/kindlecomicconverter/startup.py @@ -30,14 +30,14 @@ def start(): os.environ['QT_AUTO_SCREEN_SCALE_FACTOR'] = "1" KCCAplication = KCC_gui.QApplicationMessaging(sys.argv) if KCCAplication.isRunning(): - for i in range (1, len(sys.argv)): + for i in range(1, len(sys.argv)): KCCAplication.sendMessage(sys.argv[i]) else: KCCAplication.sendMessage('ARISE') else: KCCWindow = KCC_gui.QMainWindowKCC() KCCUI = KCC_gui.KCCGUI(KCCAplication, KCCWindow) - for i in range (1, len(sys.argv)): + for i in range(1, len(sys.argv)): KCCUI.handleMessage(sys.argv[i]) sys.exit(KCCAplication.exec_()) diff --git a/other/osx/7z b/other/osx/7z new file mode 100644 index 0000000..7e7b90b Binary files /dev/null and b/other/osx/7z differ diff --git a/other/osx/7z.so b/other/osx/7z.so new file mode 100644 index 0000000..80eb496 Binary files /dev/null and b/other/osx/7z.so differ diff --git a/other/osx/7za b/other/osx/7za deleted file mode 100755 index f92fa21..0000000 Binary files a/other/osx/7za and /dev/null differ diff --git a/other/osx/unrar b/other/osx/unrar deleted file mode 100755 index 8ca581d..0000000 Binary files a/other/osx/unrar and /dev/null differ diff --git a/other/windows/7z.dll b/other/windows/7z.dll new file mode 100644 index 0000000..be29515 Binary files /dev/null and b/other/windows/7z.dll differ diff --git a/other/windows/7z.exe b/other/windows/7z.exe new file mode 100644 index 0000000..337d4b0 Binary files /dev/null and b/other/windows/7z.exe differ diff --git a/other/windows/7za.exe b/other/windows/7za.exe deleted file mode 100644 index fc8a0bd..0000000 Binary files a/other/windows/7za.exe and /dev/null differ diff --git a/other/windows/Additional-LICENSE.txt b/other/windows/Additional-LICENSE.txt index 891d453..8817fbc 100644 --- a/other/windows/Additional-LICENSE.txt +++ b/other/windows/Additional-LICENSE.txt @@ -1,56 +1,22 @@ - ****** ***** ****** UnRAR - free utility for RAR archives - ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ****** ******* ****** License for use and distribution of - ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - ** ** ** ** ** ** FREEWARE version - ~~~~~~~~~~~~~~~~ - - The UnRAR utility is freeware. This means: - - 1. All copyrights to RAR and the utility UnRAR are exclusively - owned by the author - Alexander Roshal. - - 2. The UnRAR utility may be freely distributed. It is allowed - to distribute UnRAR inside of other software packages. - - 3. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS". - NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT - YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, - DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING - OR MISUSING THIS SOFTWARE. - - 4. Neither RAR binary code, WinRAR binary code, UnRAR source or UnRAR - binary code may be used or reverse engineered to re-create the RAR - compression algorithm, which is proprietary, without written - permission of the author. - - 5. If you don't agree with terms of the license you must remove - UnRAR files from your storage devices and cease to use the - utility. - - Thank you for your interest in RAR and UnRAR. - - - Alexander L. Roshal - 7-Zip ~~~~~ License for use and distribution ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - 7-Zip Copyright (C) 1999-2012 Igor Pavlov. + 7-Zip Copyright (C) 1999-2018 Igor Pavlov. - Licenses for files are: + The licenses for files are: - 1) 7z.dll: GNU LGPL + unRAR restriction - 2) All other files: GNU LGPL + 1) 7z.dll: + - The "GNU LGPL" as main license for most of the code + - The "GNU LGPL" with "unRAR license restriction" for some code + - The "BSD 3-clause License" for some code + 2) All other files: the "GNU LGPL". - The GNU LGPL + unRAR restriction means that you must follow both - GNU LGPL rules and unRAR restriction rules. + Redistributions in binary form must reproduce related license information from this file. - - Note: - You can use 7-Zip on any computer, including a computer in a commercial + Note: + You can use 7-Zip on any computer, including a computer in a commercial organization. You don't need to register or pay for 7-Zip. @@ -67,21 +33,54 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. - You can receive a copy of the GNU Lesser General Public License from + You can receive a copy of the GNU Lesser General Public License from http://www.gnu.org/ - unRAR restriction - ----------------- - The decompression engine for RAR archives was developed using source + + BSD 3-clause License + -------------------- + + The "BSD 3-clause License" is used for the code in 7z.dll that implements LZFSE data decompression. + That code was derived from the code in the "LZFSE compression library" developed by Apple Inc, + that also uses the "BSD 3-clause License": + + ---- + Copyright (c) 2015-2016, Apple Inc. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder(s) nor the names of any contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ---- + + + + + unRAR license restriction + ------------------------- + + The decompression engine for RAR archives was developed using source code of unRAR program. All copyrights to original unRAR code are owned by Alexander Roshal. The license for original unRAR code has the following restriction: - The unRAR sources cannot be used to re-create the RAR compression algorithm, - which is proprietary. Distribution of modified unRAR sources in separate form + The unRAR sources cannot be used to re-create the RAR compression algorithm, + which is proprietary. Distribution of modified unRAR sources in separate form or as a part of other software is permitted, provided that it is clearly stated in the documentation and source comments that the code may not be used to develop a RAR (WinRAR) compatible archiver. diff --git a/other/windows/UnRAR.exe b/other/windows/UnRAR.exe deleted file mode 100644 index 28840c1..0000000 Binary files a/other/windows/UnRAR.exe and /dev/null differ diff --git a/setup.py b/setup.py index 26e5ec0..aad9a8d 100755 --- a/setup.py +++ b/setup.py @@ -37,13 +37,12 @@ class BuildBinaryCommand(distutils.cmd.Command): VERSION = __version__ if sys.platform == 'darwin': os.system('pyinstaller -y -F -i icons/comic2ebook.icns -n "Kindle Comic Converter" -w -s kcc.py') - shutil.copy('other/osx/7za', 'dist/Kindle Comic Converter.app/Contents/Resources') - shutil.copy('other/osx/unrar', 'dist/Kindle Comic Converter.app/Contents/Resources') + shutil.copy('other/osx/7z', 'dist/Kindle Comic Converter.app/Contents/Resources') + shutil.copy('other/osx/7z.so', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/osx/Info.plist', 'dist/Kindle Comic Converter.app/Contents') shutil.copy('LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources') shutil.copy('other/windows/Additional-LICENSE.txt', 'dist/Kindle Comic Converter.app/Contents/Resources') - os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/unrar', 0o777) - os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7za', 0o777) + os.chmod('dist/Kindle Comic Converter.app/Contents/Resources/7z', 0o777) os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') exit(0) elif sys.platform == 'win32': @@ -65,7 +64,7 @@ class BuildBinaryCommand(distutils.cmd.Command): 'readers.\nThis app allows you to transform your PNG, JPG, GIF, ' 'CBZ, CBR and CB7 files\ninto EPUB or MOBI format e-books.")" ' '--url "https://kcc.iosphe.re/" --deb-priority "optional" --vendor "" ' - '--category "graphics" -d "unrar | unrar-free" -d "p7zip-full" -d "libc6" usr') + '--category "graphics" -d "p7zip-full" -d "p7zip-rar" -d "libc6" usr') exit(0) -- cgit 1.4.1 From 93e6b514661bb9ac845740c81e07e5967252fd7b Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Tue, 10 Jul 2018 08:41:57 +0200 Subject: Expanded output of corruption error (close #272) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 37e39d7..5cabb1f 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -869,7 +869,7 @@ def detectCorruption(tmppath, orgpath): if 'decoder' in str(err) and 'not available' in str(err): raise RuntimeError('Pillow was compiled without JPG and/or PNG decoder.') else: - raise RuntimeError('Image file %s is corrupted.' % pathOrg) + raise RuntimeError('Image file %s is corrupted. Error: %s' % (pathOrg, str(err))) else: os.remove(os.path.join(root, name)) if alreadyProcessed: -- cgit 1.4.1 From a2ffd259b8d5c20b131faa7be0b81fd87096fd9b Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Tue, 10 Jul 2018 09:00:13 +0200 Subject: Code cleanup --- .travis.yml | 2 +- README.md | 6 +++--- appveyor.yml | 4 ++-- kindlecomicconverter/dualmetafix.py | 1 + kindlecomicconverter/metadata.py | 2 -- kindlecomicconverter/shared.py | 22 ++++++++++------------ 6 files changed, 17 insertions(+), 20 deletions(-) diff --git a/.travis.yml b/.travis.yml index 977b0df..eba8cca 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,7 @@ before_install: install: - pip3 install -r requirements.txt -- pip3 install certifi https://github.com/bjones1/pyinstaller/archive/pyqt5_fix.zip +- pip3 install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip - npm install -g appdmg script: python3 setup.py build_binary diff --git a/README.md b/README.md index 85e20bf..68c5311 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,9 @@ sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven ## INPUT FORMATS **KCC** can understand and convert, at the moment, the following input types: - Folders containing: PNG, JPG or GIF files -- CBZ, ZIP -- CBR, RAR *(With `unrar` executable)* -- CB7, 7Z *(With `7za` executable)* +- CBZ, ZIP *(With `7z` executable)* +- CBR, RAR *(With `7z` executable)* +- CB7, 7Z *(With `7z` executable)* - PDF *(Only extracting JPG images)* ## USAGE diff --git a/appveyor.yml b/appveyor.yml index 2eb446c..b603c3c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,11 +1,11 @@ environment: - PYTHON: "C:\\Python36-x64" + PYTHON: "C:\\Python37-x64" install: - set PATH="%PYTHON%\\Scripts";"C:\\Program Files (x86)\\Inno Setup 5";%PATH% - "%PYTHON%\\python.exe -m pip install --upgrade pip setuptools wheel" - "%PYTHON%\\python.exe -m pip install -r requirements.txt" - - "%PYTHON%\\python.exe -m pip install certifi https://github.com/bjones1/pyinstaller/archive/pyqt5_fix.zip" + - "%PYTHON%\\python.exe -m pip install certifi https://github.com/pyinstaller/pyinstaller/archive/develop.zip" - nuget install secure-file -ExcludeVersion - secure-file\tools\secure-file -decrypt other\windows\sentry.py.enc -out kindlecomicconverter\sentry.py -secret %ENCRYPTION% diff --git a/kindlecomicconverter/dualmetafix.py b/kindlecomicconverter/dualmetafix.py index e6be255..5dfb2d7 100644 --- a/kindlecomicconverter/dualmetafix.py +++ b/kindlecomicconverter/dualmetafix.py @@ -24,6 +24,7 @@ import shutil class DualMetaFixException(Exception): pass + # palm database offset constants number_of_pdb_records = 76 first_pdb_record = 78 diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py index df87340..62c7442 100644 --- a/kindlecomicconverter/metadata.py +++ b/kindlecomicconverter/metadata.py @@ -19,8 +19,6 @@ import os from xml.dom.minidom import parse, Document from re import compile -from subprocess import STDOUT, PIPE -from psutil import Popen from tempfile import mkdtemp from shutil import rmtree from . import comicarchive diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index 32f656c..4918a90 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -22,8 +22,6 @@ import os from hashlib import md5 from html.parser import HTMLParser from distutils.version import StrictVersion -from shutil import rmtree, copy -from tempfile import mkdtemp from re import split from traceback import format_tb @@ -49,8 +47,7 @@ class HTMLStripper(HTMLParser): def getImageFileName(imgfile): name, ext = os.path.splitext(imgfile) ext = ext.lower() - if name.startswith('.') or (ext != '.png' and ext != '.jpg' and ext != '.jpeg' and ext != '.gif' and - ext != '.webp'): + if name.startswith('.') or ext not in ['.png', '.jpg', '.jpeg', '.gif', '.webp']: return None return [name, ext] @@ -88,16 +85,17 @@ def md5Checksum(fpath): def sanitizeTrace(traceback): return ''.join(format_tb(traceback))\ - .replace('C:/projects/kcc/', '') \ - .replace('c:/projects/kcc/', '') \ - .replace('C:/python36-x64/', '')\ - .replace('c:/python36-x64/', '')\ - .replace('C:\\projects\\kcc\\', '') \ - .replace('c:\\projects\\kcc\\', '') \ - .replace('C:\\python36-x64\\', '')\ - .replace('c:\\python36-x64\\', '') + .replace('C:/projects/kcc/', '')\ + .replace('c:/projects/kcc/', '')\ + .replace('C:/python37-x64/', '')\ + .replace('c:/python37-x64/', '')\ + .replace('C:\\projects\\kcc\\', '')\ + .replace('c:\\projects\\kcc\\', '')\ + .replace('C:\\python37-x64\\', '')\ + .replace('c:\\python37-x64\\', '') +# noinspection PyUnresolvedReferences def dependencyCheck(level): missing = [] if level > 2: -- cgit 1.4.1 From a7ea795df532066ee7eb9a7288ec30caa971d9be Mon Sep 17 00:00:00 2001 From: MurphyTsai Date: Thu, 3 Jan 2019 14:52:28 +0800 Subject: support kobo forma. --- kindlecomicconverter/KCC_gui.py | 3 +++ kindlecomicconverter/comic2ebook.py | 2 +- kindlecomicconverter/image.py | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index d46353c..635d628 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -934,6 +934,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'DefaultUpscale': True, 'Label': 'KoAH2O'}, "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'Label': 'KoAO'}, + "Kobo Forma": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, + 'DefaultUpscale': True, 'Label': 'KoF'}, "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1, 'DefaultUpscale': False, 'Label': 'OTHER'}, "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0, @@ -954,6 +956,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kindle", "Separator", "Kobo Aura ONE", + "Kobo Forma", "Kobo Aura H2O", "Kobo Aura HD", "Kobo Aura", diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 5cabb1f..78342f9 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -929,7 +929,7 @@ def makeParser(): mainOptions.add_option("-p", "--profile", action="store", dest="profile", default="KV", help="Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KV, KO, KoMT, KoG," - " KoGHD, KoA, KoAHD, KoAH2O, KoAO) [Default=KV]") + " KoGHD, KoA, KoAHD, KoAH2O, KoAO, KoF) [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("-q", "--hq", action="store_true", dest="hq", default=False, diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 9608482..09e79f1 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -94,6 +94,7 @@ class ProfileData: '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), + 'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8), 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8), 'OTHER': ("Other", (0, 0), Palette16, 1.8), } -- cgit 1.4.1 From 8f8d0d68a35d7360ae418bdf84633b5348ee194b Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 27 Feb 2019 13:49:47 +0100 Subject: Fixed possible glob issues --- kindlecomicconverter/comic2ebook.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 78342f9..93cf580 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -22,7 +22,7 @@ import os import sys from time import strftime, gmtime from copy import copy -from glob import glob +from glob import glob, escape from json import loads from urllib.request import Request, urlopen from re import sub @@ -37,7 +37,7 @@ from slugify import slugify as slugifyExt from PIL import Image from subprocess import STDOUT, PIPE from psutil import Popen, virtual_memory, disk_usage -from html import escape +from html import escape as hescape try: from PyQt5 import QtCore except ImportError: @@ -61,7 +61,7 @@ def main(argv=None): parser.print_help() return 0 if sys.platform.startswith('win'): - sources = set([source for arg in args for source in glob(arg)]) + sources = set([source for arg in args for source in glob(escape(arg))]) else: sources = set(args) if len(sources) == 0: @@ -112,7 +112,7 @@ def buildHTML(path, imgfile, imgfilepath): "\n", "\n", "\n", - "", escape(filename[0]), "\n", + "", hescape(filename[0]), "\n", "\n", "\n" @@ -210,7 +210,7 @@ def buildNCX(dstdir, title, chapters, chapternames): "\n", "\n", "\n", - "", escape(title), "\n", + "", hescape(title), "\n", "\n"]) for chapter in chapters: folder = chapter[0].replace(os.path.join(dstdir, 'OEBPS'), '').lstrip('/').lstrip('\\\\') @@ -222,7 +222,7 @@ def buildNCX(dstdir, title, chapters, chapternames): elif os.path.basename(folder) != "Text": title = chapternames[os.path.basename(folder)] f.write("" + - escape(title) + "\n") f.write("\n") f.close() @@ -235,7 +235,7 @@ def buildNAV(dstdir, title, chapters, chapternames): "\n", "\n", "\n", - "" + escape(title) + "\n", + "" + hescape(title) + "\n", "\n", "\n", "\n", @@ -248,7 +248,7 @@ def buildNAV(dstdir, title, chapters, chapternames): title = chapternames[chapter[1]] elif os.path.basename(folder) != "Text": title = chapternames[os.path.basename(folder)] - f.write("
  • " + escape(title) + "
  • \n") + f.write("
  • " + hescape(title) + "
  • \n") f.writelines(["\n", "\n", "\n\n") f.close() @@ -669,7 +669,7 @@ def getComicInfo(path, originalpath): options.authors = [] if defaultTitle: if xml.data['Series']: - options.title = escape(xml.data['Series']) + options.title = hescape(xml.data['Series']) if xml.data['Volume']: titleSuffix += ' V' + xml.data['Volume'].zfill(2) if xml.data['Number']: @@ -677,7 +677,7 @@ def getComicInfo(path, originalpath): options.title += titleSuffix for field in ['Writers', 'Pencillers', 'Inkers', 'Colorists']: for person in xml.data[field]: - options.authors.append(escape(person)) + options.authors.append(hescape(person)) if len(options.authors) > 0: options.authors = list(set(options.authors)) options.authors.sort() @@ -688,7 +688,7 @@ def getComicInfo(path, originalpath): if xml.data['Bookmarks']: options.chapters = xml.data['Bookmarks'] if xml.data['Summary']: - options.summary = escape(xml.data['Summary']) + options.summary = hescape(xml.data['Summary']) os.remove(xmlPath) -- cgit 1.4.1 From 2070a977aefe9da8ceb2ab1d0e96f30f1d7050b6 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 10:56:44 +0100 Subject: Updated build enviroment --- .travis.yml | 4 ++-- README.md | 2 +- other/osx/Info.plist | 2 +- requirements.txt | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index eba8cca..27745c0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,11 @@ matrix: include: - os: osx language: generic - osx_image: xcode6.4 + osx_image: xcode9.2 before_install: - brew update -- brew install python3 +- brew upgrade python3 - brew uninstall node - travis_wait 30 brew install node@6 - brew link node@6 --force --overwrite diff --git a/README.md b/README.md index 68c5311..0f8c30f 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,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/) (64-bit only)** -- **[macOS](http://kcc.iosphe.re/OSX/) (10.10+)** +- **[macOS](http://kcc.iosphe.re/OSX/) (10.12+)** - **Linux:** - [Ubuntu 17.10](http://kcc.iosphe.re/LinuxArtful/) - [Ubuntu 16.04 / Debian 9](http://kcc.iosphe.re/LinuxXenial/) diff --git a/other/osx/Info.plist b/other/osx/Info.plist index 4a9fa48..0b30fed 100644 --- a/other/osx/Info.plist +++ b/other/osx/Info.plist @@ -55,7 +55,7 @@ LSHasLocalizedDisplayName LSMinimumSystemVersion - 10.10.0 + 10.12.0 NSAppleScriptEnabled NSHumanReadableCopyright diff --git a/requirements.txt b/requirements.txt index 7b2f16c..355db43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ PyQt5>=5.6.0 Pillow>=5.2.0 psutil>=5.0.0 -python-slugify>=1.2.1 +python-slugify>=1.2.1,<3.0.0 raven>=6.0.0 \ No newline at end of file -- cgit 1.4.1 From 332d3d455efae384ece0b58f30ec433f5be9494e Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 15:47:18 +0100 Subject: Version bump --- CHANGELOG.md | 2 ++ kcc.iss | 2 +- kindlecomicconverter/KCC_gui.py | 2 +- kindlecomicconverter/__init__.py | 4 ++-- kindlecomicconverter/comic2ebook.py | 2 +- kindlecomicconverter/comic2panel.py | 2 +- kindlecomicconverter/comicarchive.py | 2 +- kindlecomicconverter/dualmetafix.py | 2 +- kindlecomicconverter/image.py | 2 +- kindlecomicconverter/kindle.py | 2 +- kindlecomicconverter/metadata.py | 2 +- kindlecomicconverter/pdfjpgextract.py | 2 +- kindlecomicconverter/shared.py | 2 +- kindlecomicconverter/startup.py | 2 +- other/osx/Info.plist | 6 +++--- 15 files changed, 19 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce0bfe..d59bf27 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ # CHANGELOG +#### 5.5.0: + #### 5.4.5: * Fixed EPUB output for non-Kindle devices diff --git a/kcc.iss b/kcc.iss index 6142c6d..5caf5d6 100644 --- a/kcc.iss +++ b/kcc.iss @@ -1,5 +1,5 @@ #define MyAppName "Kindle Comic Converter" -#define MyAppVersion "5.4.5" +#define MyAppVersion "5.5.0" #define MyAppPublisher "Ciro Mattia Gonano, Paweł Jastrzębski" #define MyAppURL "http://kcc.iosphe.re/" #define MyAppExeName "KCC.exe" diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 635d628..f7c0776 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/__init__.py b/kindlecomicconverter/__init__.py index 2ccec97..fe7588f 100644 --- a/kindlecomicconverter/__init__.py +++ b/kindlecomicconverter/__init__.py @@ -1,4 +1,4 @@ -__version__ = '5.4.5' +__version__ = '5.5.0' __license__ = 'ISC' -__copyright__ = '2012-2018, Ciro Mattia Gonano , Pawel Jastrzebski ' +__copyright__ = '2012-2019, Ciro Mattia Gonano , Pawel Jastrzebski ' __docformat__ = 'restructuredtext en' diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 93cf580..6e5de47 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/comic2panel.py b/kindlecomicconverter/comic2panel.py index c3d9e1c..cd5d87a 100644 --- a/kindlecomicconverter/comic2panel.py +++ b/kindlecomicconverter/comic2panel.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py index df9029c..214ae8d 100644 --- a/kindlecomicconverter/comicarchive.py +++ b/kindlecomicconverter/comicarchive.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/dualmetafix.py b/kindlecomicconverter/dualmetafix.py index 5dfb2d7..c05dc85 100644 --- a/kindlecomicconverter/dualmetafix.py +++ b/kindlecomicconverter/dualmetafix.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Based on initial version of DualMetaFix. Copyright (C) 2013 Kevin Hendricks -# Changes for KCC Copyright (C) 2014-2018 Pawel Jastrzebski +# Changes for KCC Copyright (C) 2014-2019 Pawel Jastrzebski # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 09e79f1..8ff95a7 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -4,7 +4,7 @@ # Copyright (C) 2011 Stanislav (proDOOMman) Kosolapov # Copyright (c) 2016 Alberto Planas # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by diff --git a/kindlecomicconverter/kindle.py b/kindlecomicconverter/kindle.py index f9595f6..9943590 100644 --- a/kindlecomicconverter/kindle.py +++ b/kindlecomicconverter/kindle.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py index 62c7442..2189a6a 100644 --- a/kindlecomicconverter/metadata.py +++ b/kindlecomicconverter/metadata.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/pdfjpgextract.py b/kindlecomicconverter/pdfjpgextract.py index 258b1b4..9a24771 100644 --- a/kindlecomicconverter/pdfjpgextract.py +++ b/kindlecomicconverter/pdfjpgextract.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Based upon the code snippet by Ned Batchelder # (http://nedbatchelder.com/blog/200712/extracting_jpgs_from_pdfs.html) diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index 4918a90..d2a358f 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/startup.py b/kindlecomicconverter/startup.py index 4af160e..c92579d 100644 --- a/kindlecomicconverter/startup.py +++ b/kindlecomicconverter/startup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/other/osx/Info.plist b/other/osx/Info.plist index 0b30fed..2cf43f1 100644 --- a/other/osx/Info.plist +++ b/other/osx/Info.plist @@ -30,7 +30,7 @@ CFBundleExecutable MacOS/Kindle Comic Converter CFBundleGetInfoString - KindleComicConverter 5.4.5, written 2012-2018 by Ciro Mattia Gonano and Pawel Jastrzebski + KindleComicConverter 5.5.0, written 2012-2019 by Ciro Mattia Gonano and Pawel Jastrzebski CFBundleIconFile comic2ebook.icns CFBundleIdentifier @@ -42,11 +42,11 @@ CFBundlePackageType APPL CFBundleShortVersionString - 5.4.5 + 5.5.0 CFBundleSignature ???? CFBundleVersion - 5.4.5 + 5.5.0 LSEnvironment PATH -- cgit 1.4.1 From 7f719a22ad7c986dd074f15b08cbd621568e787c Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 15:50:40 +0100 Subject: Added PW4 profile (close #293) --- LICENSE.txt | 2 +- README.md | 6 +++--- kcc-c2e.py | 2 +- kcc-c2p.py | 2 +- kcc.iss | 2 +- kcc.py | 2 +- kindlecomicconverter/KCC_gui.py | 6 +++--- kindlecomicconverter/image.py | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index a060b54..a509747 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ ISC LICENSE Copyright (c) 2012-2014 Ciro Mattia Gonano -Copyright (c) 2013-2018 Paweł Jastrzębski +Copyright (c) 2013-2019 Paweł Jastrzębski Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the diff --git a/README.md b/README.md index 0f8c30f..bda3dd2 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,7 @@ Options: -p PROFILE, --profile=PROFILE Device profile (Available options: K1, K2, K34, K578, KDX, KPW, KV, KO, KoMT, KoG, KoGHD, KoA, KoAHD, KoAH2O, - KoAO) [Default=KV] + KoAO, KoF) [Default=KV] -m, --manga-style Manga style (right-to-left reading and splitting) -q, --hq Try to increase the quality of magnification -2, --two-panel Display two not four panels in Panel View mode @@ -165,7 +165,7 @@ The app relies and includes the following scripts: ## SAMPLE FILES CREATED BY KCC * [Kindle Oasis 2](http://kcc.iosphe.re/Samples/Ubunchu!-KO.mobi) -* [Kindle Paperwhite 3 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi) +* [Kindle Paperwhite 3 / 4 / Voyage / Oasis](http://kcc.iosphe.re/Samples/Ubunchu!-KV.mobi) * [Kindle Paperwhite 1 / 2](http://kcc.iosphe.re/Samples/Ubunchu!-KPW.mobi) * [Kindle](http://kcc.iosphe.re/Samples/Ubunchu!-K578.mobi) * [Kobo Aura](http://kcc.iosphe.re/Samples/Ubunchu-KoA.kepub.epub) @@ -183,5 +183,5 @@ The app relies and includes the following scripts: Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues). ## COPYRIGHT -Copyright (c) 2012-2018 Ciro Mattia Gonano and Paweł Jastrzębski. +Copyright (c) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski. **KCC** is released under ISC LICENSE; see LICENSE.txt for further details. diff --git a/kcc-c2e.py b/kcc-c2e.py index 2400560..d2188a7 100755 --- a/kcc-c2e.py +++ b/kcc-c2e.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kcc-c2p.py b/kcc-c2p.py index 4d5b1e1..b7c9892 100755 --- a/kcc-c2p.py +++ b/kcc-c2p.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kcc.iss b/kcc.iss index 5caf5d6..ac4e92f 100644 --- a/kcc.iss +++ b/kcc.iss @@ -12,7 +12,7 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} -AppCopyright=Copyright (C) 2012-2018 Ciro Mattia Gonano and Paweł Jastrzębski +AppCopyright=Copyright (C) 2012-2019 Ciro Mattia Gonano and Paweł Jastrzębski ArchitecturesAllowed=x64 DefaultDirName={pf}\{#MyAppName} DefaultGroupName={#MyAppName} diff --git a/kcc.py b/kcc.py index 347a4ac..b05e8c1 100755 --- a/kcc.py +++ b/kcc.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # Copyright (c) 2012-2014 Ciro Mattia Gonano -# Copyright (c) 2013-2018 Pawel Jastrzebski +# Copyright (c) 2013-2019 Pawel Jastrzebski # # Permission to use, copy, modify, and/or distribute this software for # any purpose with or without fee is hereby granted, provided that the diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index f7c0776..91910be 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -912,8 +912,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): 'DefaultUpscale': True, 'Label': 'KV'}, "Kindle Voyage": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': True, 'Label': 'KV'}, - "Kindle PW 3": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, - 'DefaultUpscale': True, 'Label': 'KV'}, + "Kindle PW 3/4": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, + 'DefaultUpscale': True, 'Label': 'KV'}, "Kindle PW 1/2": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, 'DefaultUpscale': False, 'Label': 'KPW'}, "Kindle": {'PVOptions': True, 'ForceExpert': False, 'DefaultFormat': 0, @@ -951,7 +951,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kindle Oasis 2", "Kindle Oasis", "Kindle Voyage", - "Kindle PW 3", + "Kindle PW 3/4", "Kindle PW 1/2", "Kindle", "Separator", diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 8ff95a7..6d44b4f 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -86,7 +86,7 @@ class ProfileData: 'K578': ("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), + 'KV': ("Kindle Paperwhite 3/4/Voyage/Oasis", (1072, 1448), Palette16, 1.8), 'KO': ("Kindle Oasis 2", (1264, 1680), Palette16, 1.8), 'KoMT': ("Kobo Mini/Touch", (600, 800), Palette16, 1.8), 'KoG': ("Kobo Glo", (768, 1024), Palette16, 1.8), -- cgit 1.4.1 From c07a9657ef19e3f422424ece4e0c1c41eb092746 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 16:16:26 +0100 Subject: Removed MCD support --- gui/MetaEditor.ui | 13 ------------- kindlecomicconverter/KCC_gui.py | 18 +++++++----------- kindlecomicconverter/KCC_ui_editor.py | 8 -------- kindlecomicconverter/comic2ebook.py | 19 ------------------- kindlecomicconverter/image.py | 14 +------------- kindlecomicconverter/metadata.py | 13 ++----------- setup.py | 2 +- 7 files changed, 11 insertions(+), 76 deletions(-) diff --git a/gui/MetaEditor.ui b/gui/MetaEditor.ui index 8f1a991..a8691ff 100644 --- a/gui/MetaEditor.ui +++ b/gui/MetaEditor.ui @@ -112,19 +112,6 @@ - - - - <html><head/><body><p><a href="https://github.com/ciromattia/kcc/wiki/Manga-Cover-Database-support"><span style=" text-decoration: underline; color:#0000ff;">MUid:</span></a></p></body></html> - - - true - - - - - - diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 91910be..bce06ed 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -24,6 +24,7 @@ from urllib.request import urlopen, urlretrieve, Request from time import sleep from shutil import move from subprocess import STDOUT, PIPE +# noinspection PyUnresolvedReferences from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork from xml.dom.minidom import parse from xml.sax.saxutils import escape @@ -887,6 +888,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): self.targetDirectory = '' self.sentry = Client(release=__version__) if sys.platform.startswith('win'): + # noinspection PyUnresolvedReferences from psutil import BELOW_NORMAL_PRIORITY_CLASS self.p = Process(os.getpid()) self.p.nice(BELOW_NORMAL_PRIORITY_CLASS) @@ -935,7 +937,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kobo Aura ONE": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, 'DefaultUpscale': True, 'Label': 'KoAO'}, "Kobo Forma": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 1, - 'DefaultUpscale': True, 'Label': 'KoF'}, + 'DefaultUpscale': True, 'Label': 'KoF'}, "Other": {'PVOptions': False, 'ForceExpert': True, 'DefaultFormat': 1, 'DefaultUpscale': False, 'Label': 'OTHER'}, "Kindle 1": {'PVOptions': False, 'ForceExpert': False, 'DefaultFormat': 0, @@ -1084,11 +1086,8 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): self.editorWidget.setEnabled(True) self.okButton.setEnabled(True) self.statusLabel.setText('Separate authors with a comma.') - for field in (self.seriesLine, self.volumeLine, self.numberLine, self.muidLine): - if field.objectName() == 'muidLine': - field.setText(self.parser.data['MUid']) - else: - field.setText(self.parser.data[field.objectName().capitalize()[:-4]]) + for field in (self.seriesLine, self.volumeLine, self.numberLine): + field.setText(self.parser.data[field.objectName().capitalize()[:-4]]) for field in (self.writerLine, self.pencillerLine, self.inkerLine, self.coloristLine): field.setText(', '.join(self.parser.data[field.objectName().capitalize()[:-4] + 's'])) if self.seriesLine.text() == '': @@ -1098,12 +1097,9 @@ class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): self.seriesLine.setText(file.split('\\')[-1].split('/')[-1].split('.')[0]) def saveData(self): - for field in (self.volumeLine, self.numberLine, self.muidLine): + for field in (self.volumeLine, self.numberLine): if field.text().isnumeric() or self.cleanData(field.text()) == '': - if field.objectName() == 'muidLine': - self.parser.data['MUid'] = self.cleanData(field.text()) - else: - self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text()) + self.parser.data[field.objectName().capitalize()[:-4]] = self.cleanData(field.text()) else: self.statusLabel.setText(field.objectName().capitalize()[:-4] + ' field must be a number.') break diff --git a/kindlecomicconverter/KCC_ui_editor.py b/kindlecomicconverter/KCC_ui_editor.py index 56dc07d..5deba91 100644 --- a/kindlecomicconverter/KCC_ui_editor.py +++ b/kindlecomicconverter/KCC_ui_editor.py @@ -66,13 +66,6 @@ class Ui_editorDialog(object): self.coloristLine = QtWidgets.QLineEdit(self.editorWidget) self.coloristLine.setObjectName("coloristLine") self.gridLayout.addWidget(self.coloristLine, 6, 1, 1, 1) - self.label_8 = QtWidgets.QLabel(self.editorWidget) - self.label_8.setOpenExternalLinks(True) - self.label_8.setObjectName("label_8") - self.gridLayout.addWidget(self.label_8, 7, 0, 1, 1) - self.muidLine = QtWidgets.QLineEdit(self.editorWidget) - self.muidLine.setObjectName("muidLine") - self.gridLayout.addWidget(self.muidLine, 7, 1, 1, 1) self.verticalLayout.addWidget(self.editorWidget) self.optionWidget = QtWidgets.QWidget(editorDialog) self.optionWidget.setObjectName("optionWidget") @@ -117,7 +110,6 @@ class Ui_editorDialog(object): self.label_5.setText(_translate("editorDialog", "Penciller:")) self.label_6.setText(_translate("editorDialog", "Inker:")) self.label_7.setText(_translate("editorDialog", "Colorist:")) - self.label_8.setText(_translate("editorDialog", "

    MUid:

    ")) self.okButton.setText(_translate("editorDialog", "Save")) self.cancelButton.setText(_translate("editorDialog", "Cancel")) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 6e5de47..90c9872 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -23,8 +23,6 @@ import sys from time import strftime, gmtime from copy import copy from glob import glob, escape -from json import loads -from urllib.request import Request, urlopen from re import sub from stat import S_IWRITE, S_IREAD, S_IEXEC from zipfile import ZipFile, ZIP_STORED, ZIP_DEFLATED @@ -648,7 +646,6 @@ def getOutputFilename(srcpath, wantedname, ext, tomenumber): def getComicInfo(path, originalpath): xmlPath = os.path.join(path, 'ComicInfo.xml') options.authors = ['KCC'] - options.remoteCovers = {} options.chapters = [] options.summary = '' titleSuffix = '' @@ -683,8 +680,6 @@ def getComicInfo(path, originalpath): options.authors.sort() else: options.authors = ['KCC'] - if xml.data['MUid']: - options.remoteCovers = getCoversFromMCB(xml.data['MUid']) if xml.data['Bookmarks']: options.chapters = xml.data['Bookmarks'] if xml.data['Summary']: @@ -692,20 +687,6 @@ def getComicInfo(path, originalpath): os.remove(xmlPath) -def getCoversFromMCB(mangaid): - covers = {} - try: - jsonRaw = urlopen(Request('http://mcd.iosphe.re/api/v1/series/' + mangaid + '/', - headers={'User-Agent': 'KindleComicConverter/' + __version__})) - jsonData = loads(jsonRaw.read().decode('utf-8')) - for volume in jsonData['Covers']['a']: - if volume['Side'] == 'front': - covers[int(volume['Volume'])] = volume['Raw'] - except Exception: - return {} - return covers - - def getDirectorySize(start_path='.'): total_size = 0 for dirpath, _, filenames in os.walk(start_path): diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 6d44b4f..0c26bf8 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -20,12 +20,8 @@ # along with this program. If not, see . import os -from io import BytesIO -from urllib.request import Request, urlopen -from urllib.parse import quote from PIL import Image, ImageOps, ImageStat, ImageChops, ImageFilter from .shared import md5Checksum -from . import __version__ class ProfileData: @@ -347,15 +343,7 @@ class Cover: self.tomeid = 1 else: self.tomeid = tomeid - if self.tomeid in self.options.remoteCovers: - try: - source = urlopen(Request(quote(self.options.remoteCovers[self.tomeid]).replace('%3A', ':', 1), - headers={'User-Agent': 'KindleComicConverter/' + __version__})).read() - self.image = Image.open(BytesIO(source)) - except Exception: - self.image = Image.open(source) - else: - self.image = Image.open(source) + self.image = Image.open(source) self.process() def process(self): diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py index 2189a6a..e93ca27 100644 --- a/kindlecomicconverter/metadata.py +++ b/kindlecomicconverter/metadata.py @@ -18,7 +18,6 @@ import os from xml.dom.minidom import parse, Document -from re import compile from tempfile import mkdtemp from shutil import rmtree from . import comicarchive @@ -35,7 +34,6 @@ class MetadataParser: 'Inkers': [], 'Colorists': [], 'Summary': '', - 'MUid': '', 'Bookmarks': []} self.rawdata = None self.format = None @@ -67,11 +65,6 @@ class MetadataParser: self.data[field + 's'].append(person) self.data[field + 's'] = list(set(self.data[field + 's'])) self.data[field + 's'].sort() - if len(self.rawdata.getElementsByTagName('ScanInformation')) != 0: - coverId = compile('(MCD\\()(\\d+)(\\))')\ - .search(self.rawdata.getElementsByTagName('ScanInformation')[0].firstChild.nodeValue) - if coverId: - self.data['MUid'] = coverId.group(2) if len(self.rawdata.getElementsByTagName('Page')) != 0: for page in self.rawdata.getElementsByTagName('Page'): if 'Bookmark' in page.attributes and 'Image' in page.attributes: @@ -84,8 +77,7 @@ class MetadataParser: for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], - ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']], - ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']): + ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]): if self.rawdata.getElementsByTagName(row[0]): node = self.rawdata.getElementsByTagName(row[0])[0] if row[1]: @@ -106,8 +98,7 @@ class MetadataParser: for row in (['Series', self.data['Series']], ['Volume', self.data['Volume']], ['Number', self.data['Number']], ['Writer', ', '.join(self.data['Writers'])], ['Penciller', ', '.join(self.data['Pencillers'])], ['Inker', ', '.join(self.data['Inkers'])], - ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']], - ['ScanInformation', 'MCD(' + self.data['MUid'] + ')' if self.data['MUid'] else '']): + ['Colorist', ', '.join(self.data['Colorists'])], ['Summary', self.data['Summary']]): if row[1]: main = doc.createElement(row[0]) root.appendChild(main) diff --git a/setup.py b/setup.py index aad9a8d..182be17 100755 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ class BuildBinaryCommand(distutils.cmd.Command): os.system('appdmg kcc.json dist/KindleComicConverter_osx_' + VERSION + '.dmg') exit(0) elif sys.platform == 'win32': - os.system('pyinstaller -y -F -i icons\comic2ebook.ico -n KCC -w --noupx kcc.py') + os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC -w --noupx kcc.py') exit(0) else: os.system('pyinstaller -y -F kcc.py') -- cgit 1.4.1 From 3ecb2ba87793028f6d0bbfece1cd82f54a762b42 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 16:37:26 +0100 Subject: Fixed metadata encoding (close #281) --- kindlecomicconverter/comic2ebook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kindlecomicconverter/comic2ebook.py b/kindlecomicconverter/comic2ebook.py index 90c9872..7800414 100755 --- a/kindlecomicconverter/comic2ebook.py +++ b/kindlecomicconverter/comic2ebook.py @@ -276,7 +276,7 @@ def buildOPF(dstdir, title, filelist, cover=None): "xmlns=\"http://www.idpf.org/2007/opf\">\n", "\n", - "", title, "\n", + "", hescape(title), "\n", "en-US\n", "urn:uuid:", options.uuid, "\n", "KindleComicConverter-" + __version__ + "\n"]) -- cgit 1.4.1 From 409f077c3e88cb7a1cbd84ef252c8d18c8a5e9fb Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 19:46:06 +0100 Subject: Bye, bye DEBs --- README.md | 20 ++++++++------------ kindlecomicconverter/KCC_gui.py | 2 +- kindlecomicconverter/image.py | 2 +- other/linux/kindlecomicconverter | 4 ---- other/linux/kindlecomicconverter.desktop | 11 ----------- other/linux/sentry.py.enc | Bin 176 -> 0 bytes setup.py | 18 +----------------- 7 files changed, 11 insertions(+), 46 deletions(-) delete mode 100644 other/linux/kindlecomicconverter delete mode 100644 other/linux/kindlecomicconverter.desktop delete mode 100644 other/linux/sentry.py.enc diff --git a/README.md b/README.md index bda3dd2..32836a4 100644 --- a/README.md +++ b/README.md @@ -29,10 +29,7 @@ If you find **KCC** valuable you can consider donating to the authors: You can find the latest released binary at the following links: - **[Windows](http://kcc.iosphe.re/Windows/) (64-bit only)** - **[macOS](http://kcc.iosphe.re/OSX/) (10.12+)** -- **Linux:** - - [Ubuntu 17.10](http://kcc.iosphe.re/LinuxArtful/) - - [Ubuntu 16.04 / Debian 9](http://kcc.iosphe.re/LinuxXenial/) - - [Ubuntu 14.04 / Debian 8](http://kcc.iosphe.re/LinuxTrusty/) +- **Linux:** Currently unavailable. ## PYPI **KCC** is also available on PyPI. @@ -51,18 +48,17 @@ Following software is required to run Linux version of **KCC** and/or bare sourc On Debian based distributions these two commands should install all needed dependencies: ``` -sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full unrar +sudo apt-get install python3 python3-dev python3-pip libpng-dev libjpeg-dev p7zip-full sudo pip3 install --upgrade pillow python-slugify psutil pyqt5 raven ``` ### Optional dependencies - [KindleGen](http://www.amazon.com/gp/feature.html?ie=UTF8&docId=1000765211) v2.9+ in a directory reachable by your _PATH_ or in _KCC_ directory *(For MOBI generation)* -- [UnRAR](http://www.rarlab.com/download.htm) *(For CBR/RAR support)* -- [7za](http://www.7-zip.org/download.html) *(For 7z/CB7 support)* +- [7z](http://www.7-zip.org/download.html) *(For CBZ/ZIP, CBR/RAR, 7z/CB7 support)* ## INPUT FORMATS **KCC** can understand and convert, at the moment, the following input types: -- Folders containing: PNG, JPG or GIF files +- Folders containing: PNG, JPG, GIF or WebP files - CBZ, ZIP *(With `7z` executable)* - CBR, RAR *(With `7z` executable)* - CB7, 7Z *(With `7z` executable)* @@ -172,12 +168,12 @@ The app relies and includes the following scripts: * [Kobo Aura HD](http://kcc.iosphe.re/Samples/Ubunchu-KoAHD.kepub.epub) * [Kobo Aura H2O](http://kcc.iosphe.re/Samples/Ubunchu-KoAH2O.kepub.epub) * [Kobo Aura ONE](http://kcc.iosphe.re/Samples/Ubunchu-KoAO.kepub.epub) +* [Kobo Forma](http://kcc.iosphe.re/Samples/Ubunchu-KoF.kepub.epub) ## PRIVACY -**KCC** is initiating internet connections in three cases: -* During startup - Version check -* When MCD metadata are used - Cover download -* When error occurs - Automatic reporting +**KCC** is initiating internet connections in two cases: +* During startup - Version check. +* When error occurs - Automatic reporting on Windows and MacOS. ## KNOWN ISSUES Please check [wiki page](https://github.com/ciromattia/kcc/wiki/Known-issues). diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index bce06ed..7dcfbe6 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -957,8 +957,8 @@ class KCCGUI(KCC_ui.Ui_mainWindow): "Kindle PW 1/2", "Kindle", "Separator", - "Kobo Aura ONE", "Kobo Forma", + "Kobo Aura ONE", "Kobo Aura H2O", "Kobo Aura HD", "Kobo Aura", diff --git a/kindlecomicconverter/image.py b/kindlecomicconverter/image.py index 0c26bf8..118ef3c 100755 --- a/kindlecomicconverter/image.py +++ b/kindlecomicconverter/image.py @@ -90,8 +90,8 @@ class ProfileData: '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), - 'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8), 'KoAO': ("Kobo Aura ONE", (1404, 1872), Palette16, 1.8), + 'KoF': ("Kobo Forma", (1440, 1920), Palette16, 1.8), 'OTHER': ("Other", (0, 0), Palette16, 1.8), } diff --git a/other/linux/kindlecomicconverter b/other/linux/kindlecomicconverter deleted file mode 100644 index c9baf04..0000000 --- a/other/linux/kindlecomicconverter +++ /dev/null @@ -1,4 +0,0 @@ -kindlecomicconverter: binary-without-manpage usr/bin/kcc -kindlecomicconverter: wrong-name-for-changelog-of-native-package usr/share/doc/kindlecomicconverter/changelog.Debian.gz -kindlecomicconverter: file-missing-in-md5sums usr/share/doc/kindlecomicconverter/changelog.Debian.gz -kindlecomicconverter: hardening-no-relro usr/bin/kcc diff --git a/other/linux/kindlecomicconverter.desktop b/other/linux/kindlecomicconverter.desktop deleted file mode 100644 index 306468b..0000000 --- a/other/linux/kindlecomicconverter.desktop +++ /dev/null @@ -1,11 +0,0 @@ -[Desktop Entry] -Type=Application -Version=1.0 -Name=Kindle Comic Converter -GenericName=Kindle Comic Converter -Comment=Comic and Manga converter for e-book readers -Icon=/usr/share/kindlecomicconverter/comic2ebook.png -Exec=/usr/bin/kcc %f -Terminal=false -Categories=Graphics; -MimeType=application/zip;application/x-rar;application/x-7z-compressed; diff --git a/other/linux/sentry.py.enc b/other/linux/sentry.py.enc deleted file mode 100644 index c38a68c..0000000 Binary files a/other/linux/sentry.py.enc and /dev/null differ diff --git a/setup.py b/setup.py index 182be17..7bc5c8d 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ pip/pyinstaller build script for KCC. Install as Python package: python3 setup.py install -Create EXE/APP/DEB: +Create EXE/APP: python3 setup.py build_binary """ @@ -49,22 +49,6 @@ class BuildBinaryCommand(distutils.cmd.Command): os.system('pyinstaller -y -F -i icons\\comic2ebook.ico -n KCC -w --noupx kcc.py') exit(0) else: - os.system('pyinstaller -y -F kcc.py') - os.system('mkdir -p dist/usr/bin dist/usr/share/applications dist/usr/share/doc/kindlecomicconverter ' - 'dist/usr/share/kindlecomicconverter dist/usr/share/lintian/overrides') - os.system('mv dist/kcc dist/usr/bin') - os.system('cp icons/comic2ebook.png dist/usr/share/kindlecomicconverter') - os.system('cp LICENSE.txt dist/usr/share/doc/kindlecomicconverter/copyright') - os.system('cp other/linux/kindlecomicconverter.desktop dist/usr/share/applications') - os.system('cp other/linux/kindlecomicconverter dist/usr/share/lintian/overrides') - os.chdir('dist') - os.system('fpm -f -s dir -t deb -n kindlecomicconverter -v ' + VERSION + - ' -m "Pawel Jastrzebski " --license "ISC" ' - '--description "$(printf "Comic and Manga converter for e-book ' - 'readers.\nThis app allows you to transform your PNG, JPG, GIF, ' - 'CBZ, CBR and CB7 files\ninto EPUB or MOBI format e-books.")" ' - '--url "https://kcc.iosphe.re/" --deb-priority "optional" --vendor "" ' - '--category "graphics" -d "p7zip-full" -d "p7zip-rar" -d "libc6" usr') exit(0) -- cgit 1.4.1 From 535c2c220bfa303fd2b760843d3e5ba86358e356 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Wed, 6 Mar 2019 20:12:11 +0100 Subject: Added RAR5 support --- kindlecomicconverter/KCC_gui.py | 2 +- kindlecomicconverter/comicarchive.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 7dcfbe6..33ef984 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -1078,7 +1078,7 @@ class KCCGUI(KCC_ui.Ui_mainWindow): class KCCGUI_MetaEditor(KCC_ui_editor.Ui_editorDialog): def loadData(self, file): self.parser = metadata.MetadataParser(file) - if self.parser.format == 'RAR': + if self.parser.format in ['RAR', 'RAR5']: self.editorWidget.setEnabled(False) self.okButton.setEnabled(False) self.statusLabel.setText('CBR metadata are read-only.') diff --git a/kindlecomicconverter/comicarchive.py b/kindlecomicconverter/comicarchive.py index 214ae8d..435a99c 100644 --- a/kindlecomicconverter/comicarchive.py +++ b/kindlecomicconverter/comicarchive.py @@ -40,7 +40,7 @@ class ComicArchive: process.communicate() if process.returncode != 0: raise OSError('Archive is corrupted or encrypted.') - elif self.type not in ['7Z', 'RAR', 'ZIP']: + elif self.type not in ['7Z', 'RAR', 'RAR5', 'ZIP']: raise OSError('Unsupported archive format.') def extract(self, targetdir): @@ -61,7 +61,7 @@ class ComicArchive: return targetdir def addFile(self, sourcefile): - if self.type == 'RAR': + if self.type in ['RAR', 'RAR5']: raise NotImplementedError process = Popen('7z a -y "' + self.filepath + '" "' + sourcefile + '"', stdout=PIPE, stderr=STDOUT, shell=True) -- cgit 1.4.1 From cd83b2899c8e7dbd3beea92c696b44d971323977 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Thu, 7 Mar 2019 08:21:56 +0100 Subject: Fixed bookmark parsing (close #229) --- kindlecomicconverter/metadata.py | 1 - 1 file changed, 1 deletion(-) diff --git a/kindlecomicconverter/metadata.py b/kindlecomicconverter/metadata.py index e93ca27..b431be1 100644 --- a/kindlecomicconverter/metadata.py +++ b/kindlecomicconverter/metadata.py @@ -39,7 +39,6 @@ class MetadataParser: self.format = None if self.source.endswith('.xml') and os.path.exists(self.source): self.rawdata = parse(self.source) - self.parseXML() elif not self.source.endswith('.xml'): try: cbx = comicarchive.ComicArchive(self.source) -- cgit 1.4.1 From 4891913b5c4a1bd410b0ccf92cbd08742bfeee00 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Thu, 7 Mar 2019 08:26:31 +0100 Subject: Added additional cleanup --- kindlecomicconverter/KCC_gui.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/kindlecomicconverter/KCC_gui.py b/kindlecomicconverter/KCC_gui.py index 33ef984..8449e37 100644 --- a/kindlecomicconverter/KCC_gui.py +++ b/kindlecomicconverter/KCC_gui.py @@ -22,7 +22,7 @@ import sys from urllib.parse import unquote from urllib.request import urlopen, urlretrieve, Request from time import sleep -from shutil import move +from shutil import move, rmtree from subprocess import STDOUT, PIPE # noinspection PyUnresolvedReferences from PyQt5 import QtGui, QtCore, QtWidgets, QtNetwork @@ -32,7 +32,8 @@ from psutil import Popen, Process from copy import copy from distutils.version import StrictVersion from raven import Client -from .shared import md5Checksum, HTMLStripper, sanitizeTrace +from tempfile import gettempdir +from .shared import md5Checksum, HTMLStripper, sanitizeTrace, walkLevel from . import __version__ from . import comic2ebook from . import metadata @@ -1067,6 +1068,12 @@ class KCCGUI(KCC_ui.Ui_mainWindow): self.versionCheck.start() self.tray.show() + # Cleanup unfisnished conversion + for root, dirs, _ in walkLevel(gettempdir(), 0): + for tempdir in dirs: + if tempdir.startswith('KCC-'): + rmtree(os.path.join(root, tempdir), True) + if self.windowSize != '0x0': x, y = self.windowSize.split('x') MW.resize(int(x), int(y)) -- cgit 1.4.1 From 7120c76025bfbdf3514a2548057e0ea0fdd5cb07 Mon Sep 17 00:00:00 2001 From: Paweł Jastrzębski Date: Thu, 7 Mar 2019 08:38:22 +0100 Subject: Updated changelog --- CHANGELOG.md | 5 +++++ kindlecomicconverter/shared.py | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d59bf27..1db386c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # CHANGELOG #### 5.5.0: +* Added support for WebP format +* Added profiles for Kindle Paperwhite 4 and Kobo Forma +* All archives are now handled by 7z +* Removed MCD support +* Fixed multiple smaller issues #### 5.4.5: * Fixed EPUB output for non-Kindle devices diff --git a/kindlecomicconverter/shared.py b/kindlecomicconverter/shared.py index d2a358f..20308ac 100644 --- a/kindlecomicconverter/shared.py +++ b/kindlecomicconverter/shared.py @@ -87,12 +87,12 @@ def sanitizeTrace(traceback): return ''.join(format_tb(traceback))\ .replace('C:/projects/kcc/', '')\ .replace('c:/projects/kcc/', '')\ - .replace('C:/python37-x64/', '')\ - .replace('c:/python37-x64/', '')\ + .replace('C:/python36-x64/', '')\ + .replace('c:/python36-x64/', '')\ .replace('C:\\projects\\kcc\\', '')\ .replace('c:\\projects\\kcc\\', '')\ - .replace('C:\\python37-x64\\', '')\ - .replace('c:\\python37-x64\\', '') + .replace('C:\\python36-x64\\', '')\ + .replace('c:\\python36-x64\\', '') # noinspection PyUnresolvedReferences -- cgit 1.4.1